C++ - 封装 unordered_set 和 unordered_map - 哈希桶的迭代器实现

news2024/11/26 4:51:57

前言

 unordered_set 和  unordered_map  两个容器的底层是哈希表实现的,此处的封装使用的 上篇博客当中的哈希桶来进行封装,相当于是在 哈希桶之上在套上了 unordered_set 和  unordered_map 。

哈希桶的逻辑实现:

C++ - 开散列的拉链法(哈希桶) 介绍 和 实现-CSDN博客

 在本篇博客当中的思路只是大体介绍,这个封装过程哟点复杂,如果有问题的可以参考下述 博客 对 map 和 set 两个容器的封装,这两个容器是底层实现是 红黑树,在这篇博客当中介绍更为详细,是按照源代码当中的封装逻辑进行的模拟实现:
C++ - map 和 set 的模拟实现上篇 - 红黑树当中的仿函数 - 红黑树的迭代器实现-CSDN博客

C++ - set 和 map 的实现(下篇)- set 和 map 的迭代器实现_chihiro1122的博客-CSDN博客

基础封装 unordered_set 和  unordered_map 

 unordered_set 基础结构:

namespace unordered
{
	template<class K>
	class unordered_set
	{
		// set 当中从 T 当中取出 key 的仿函数
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		bool insert(const K& key)
		{
			return _ht.Insert(key);
		}

	private:
		hash_bucket::hash<K, K, SetKeyOfT> _ht;
	};
}

unordered_map  基础结构:

namespace unordered
{
	template<class K, class V>
	class unordered_map
	{
		// map 当中从 T 当中取出key 的仿函数
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		bool insert(const pair<K, V>& kv)
		{
			return _ht.Insert(kv);
		}

	private:
		hash_bucket::hash<K, pair<K, V>, MapKeyOfT> _ht;
	};
}

上述两个框架的实现逻辑在这里大体说明一下:
在经过  unordered_set 和  unordered_map  包裹直线,原本的 哈希桶在使用之上已经非常麻烦了,所以一般是直接使用 在 哈希桶之上的  unordered_set 和  unordered_map 。

在  unordered_set 和  unordered_map  当中的 insert()函数是直接复用 哈希桶当中的 Insert()函数。

其中的 SetKeyOfT 和 MapKeyOfT 两个内部类是用来实现 在 两个容器当中的 不同取 key 逻辑的。其实 在 set 当中只有key ,是不可以不写的,但是在 map 当中就需要从 pair 当中的first 拿出,所以,为了在 哈希桶当中key 值的实现,实现一份代码在 set 和 map 当中都可以使用的话,就要 让 set 做出牺牲,和 map 一样实现 从 T 当中 取 key 的仿函数。所以你才会看到 在 set 当中创建的 哈希桶,要传入两个 K 作为哈希桶的模版参数。

 而在哈希桶当中,对 需要用 key 值的地方都用 set 和 map 当中实现的 仿函数来调用,对 key 值的取出进行判断,我们用 哈系统当中 Insert()函数来做演示:
 

		bool Insert(const T& data)
		{
			HashFunc hf;
			KeyOfT kot;

			if (find(data))
			{
				return false;
			}

			// 负载因子 到 1 就扩容(每一个桶当中都有数据)
			if (_n == _table.size())
			{
				size_t newsize = _table.size() * 2;
				vector<Node*> newTable;
				newTable.resize(newsize, nullptr);

				for (int i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;  // 保存链表的下一个结点

						// 头插到新表当中
						size_t hashi = hf(kot(cur->_data)) % newTable.size();
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;

						// 向链表后迭代
						cur = next;
					}
				}

				// 交换 两个表在 对象当中的指向,让编译器 帮我们释放旧表的空间
				_table.swap(newTable); 
			}

红黑树的参数模版:
 

	template<class K, class T,class KeyOfT , class HashFunc = DefaultHashFunc<K>>
	class hash
	{
·········
········
········
········

其中的 T 这模版参数,在 set 当中传入的是 K,而在 map 当中传入的是一个 pair,这个pair 当中 存储的是一个结点的 key-value 键值对。而 KeyOfT 模版参数就是上述所说的,在 set 和 map 当中实现不同 取 key 逻辑的 仿函数的类的类型。这里就是要和 set 和 map 都可以使用一个 哈希桶的代码,实现泛型编程。

而 HashFunc 这个模版参数是为了 在哈希当中能以多种 类型 作为 key 值来实现的仿函数。(在上一篇博客对哈希表的介绍当中有具体说明:C++ - 开放地址法的哈希介绍 - 哈希表的仿函数例子_chihiro1122的博客-CSDN博客)

 哈希桶的迭代器实现

 哈希桶的遍历非常的简单,直接按照指针数组的顺序来遍历其中的 哈希桶就行了:
 

 但是,遍历简单,但是对于迭代器当中的  operator++()函数 和 operator--()函数,这两个函数的实现就要推敲一下。

比如 ,当it 迭代器遍历到其中某一个 结点,那么 operator--()如何找到上一个结点;当 it 迭代器遍历到 某一个哈希桶的最后一个结点的时候,operator++()函数如何找到下一个哈希桶的位置。

在 迭代器当中用 key 计算 hash 值,也是需要用 set 和 map 当中的仿函数来调用不同的 key 值取出的方法的。

key 取出来了,还有哈希桶当中的 不同类型的 key 值的计算方式,也需要仿函数去计算。

首先,每一个结点当中的值,都是按照哈希桶的规则插入进去的,我们可以计算出当前这个结点的key值,计算出当前结点是在哪一个桶;这样的话就可以直接从头开始遍历找到当前结点的上一个结点了,也可以找到下一个桶和上一个桶。

 operator++()函数

 在迭代器类当中存储的有当前结点的指针_node,那么当 _node->_next  不为空的时候,就继续遍历 这个哈希桶;为空说明已经遍历到这个哈希桶的最后一个结点了,就要找下一个哈希桶。

怎么找,在上述已经说明了,就是计算当前结点的 key 值,计算当前哈希桶在指针数组的位置,找到下一个 哈希桶的位置。

当找不到下一个桶,

但是,想找到下一个哈希桶的位置,就要有 指针数组,但是指着数组在 哈希桶类 内当中,迭代器是另一个专门的类,如果在迭代器当中取到 这个 指针数组呢?

我们就在 迭代器类当中多一个 成员来存储一个 哈希桶对象的指针。这样就可以找到指针数组了。

		Self& operator++()
		{
			if (_node->_next)
			{
				// 当前桶还没完
				// 就继续遍历当前桶
				_node = _node->_next;
			}
			else
			{
				// 两个仿函数类的实例化
				KeyOfT kot;
				HashFunc hf;

				// 计算当前结点的 hash值
				size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
				// 从下一个位置查找查找下一个不为空的桶
				++hashi;
				while (hashi < _pht->_table.size())
				{
					// 如果遍历到的桶不为空
					if (_pht->_table[hashi])
					{
						// 把桶的第一个元素赋值给 _node
						_node = _pht->_table[hashi];
						return *this;
					}
					else
					{
						// 如果桶为空 继续寻找下一个桶
						++hashi;
					}
				}

				// 走到这说明 后面的桶都为空
				// 或者当前桶就是最后一个桶了
				_node = nullptr;
			}

			return *this;
		}

类模板的有元

  但是 哈希桶当中的 _table 这个 vector 容器是 private 的,在 迭代器类当中不能访问,所以此时我们就要把 迭代器 作为 哈希表的有元出现,届时迭代器才能访问到 哈希表当中的 私有成员。

但是此处不是友元函数,是一个类模板的 有元,类模板的有元要在之前把模版参数加上:


	template<class K, class T,class KeyOfT , class HashFunc = DefaultHashFunc<K>>
	class hash
	{

		// 有元声明
		template<class K, class T, class KeyOfT, class HashFunc>
		friend struct HTIterator;
·················· 
·················· 
    };

哈希桶类当中的 begin()和 end()

找到第一个桶也很简单,和上述 operator++()找下一个桶一样;end ()的话是最后一个结点的下一个位置,也就是说 nullptr,所以说,直接构造一个空的迭代器返回就行了:
 

		iterator begin()
		{
			// 找第一个桶
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				// 当前桶的不为空
				if (cur)
				{
					// 返回需要构造一个迭代器返回
					return iterator(cur, this);
				}
			}

			// 没找到就返回一个 空的迭代器
			return iterator(nullptr, this);
		}

		iterator end()
		{
			// 构造一个空的迭代器返回
			return iterator(nullptr, this);
		}

在 set 和 map 当中的 begin()和 end()也都是套用 哈希桶当中的 begin()和end()了:
 

//unordered_set.h

typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::iterator iterator;

		iterator begin()
		{
			return _ht.begin();
		}

		iterator end()
		{
			return _ht.end();
		}

// unordered_map.h

typedef typename hash_bucket::HashTable<K, pair<K, V>, MapKeyOfT>::iterator iterator;

		iterator begin()
		{
			return _ht.begin();
		}

		iterator end()
		{
			return _ht.end();
		}

哈希桶的迭代器类 和 哈希桶类的相互依赖问题

之前实现的迭代器,都是 本类 当中有一个 迭代器 的 typedef,那么 在本类当中就可以直接按照typedef 当中的模版参数,在需要构造迭代器的地方,按照这个模版参数来构造。也就是说,要想用迭代器,那么 本类就要在前面,这样迭代器才能按照 本类来进行 定义。

但是,我们本次实现的哈希表的 迭代器当中,有哈希表的指针;在哈希表当中还有 迭代器,这是一个相互使用的场景。

当哈希表当中要用迭代器,所以迭代器在 哈希表当中 最前处声明,没问题。但是在 迭代器当中还有哈希表,那么此时,在迭代器当中的哈希表的类型,编译器不认识。

 所以,此时就要把 哈希桶类,在 迭代器上声明一下,因为编译器在遇到类型的时候,只会向上寻找定义,那么我们在迭代器上声明一下,高速编译器哈希表在下面定义了。

 我们把这种方式称为 前置声明

	// 前置声明
	template<class K, class T, class KeyOfT, class HashFunc>
	class HashTable;

	template<class K, class T, class KeyOfT, class HashFunc>
	struct HTIterator
	{

·············
·············
·············

    }

    template<class K, class T,class KeyOfT , class HashFunc = DefaultHashFunc<K>>
	class hash
	{
·············
·············
············· 

    }

前置声明当中不用给模版的缺省参数。

const迭代器

 上述实现之后,虽然迭代器能够跑了,但是还有一些问题,在上述取出的迭代器,可以修改 哈希桶的当中的key值。我们可以用 const 迭代器来解决这个问题,在 以 红黑树为底层实现的 map 和 set 也使用了这样方法,具体可以参考在前言当中给出的两篇博客。

const 迭代器 和  普通迭代器共用的是一个 迭代器的模版。而之所以 const 的迭代器在外部不能修改,实际上也就是在 operator*() 和 operator->()这两个函数在返回值上做了处理,返回一个 const 的 引用 或者 指针,这个引用的对象 或者 指针指向的对象就不能修改了。

template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
	struct HTIterator
	{
		typedef hash_bucket::HashNode<T> Node;

		// 方便下述书写 迭代器的模版参数
		typedef HTIterator<K, T, Ptr, Ref, KeyOfT, HashFunc> Self;

		Node* _node;
		hashtable<K, T, KeyOfT, HashFunc>* _pht;

		HTIterator(Node* node, hashtable<K, T, KeyOfT, HashFunc>* pht)
			:_node(node)
			, _pht(pht)
		{}

		Ref operator*()
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			return &_node->_data;
		}
····································
····································
····································
    }

所以,我们只需要把 T*  T& 作为 普通迭代器operator*() 和 operator->()的返回值;把 const T* ,const  T& 作为 普通迭代器operator*() 和 operator->()的返回值;

因为 我们在 哈希表当中对 iterator 类 的模版参数进行了 typedef ,所以,我们只需要再在哈希表当中 typedef 出一个 const_iterator ,而 iterator 和 const_iterator 的不同就在于 传入给迭代器模版类的 模版参数不同。

template<class K, class T,class KeyOfT , class HashFunc = DefaultHashFunc<K>>
	class hashtable
	{
		typedef HashNode<T> Node;

		// 有元声明
		template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
		friend struct HTIterator;
	public:
		typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> iterator;
		typedef HTIterator<K, T, const T*, const T&, KeyOfT, HashFunc> const_iterator;

····················· 
····················· 
····················· 
		iterator begin()
		{
			// 找第一个桶
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				// 当前桶的不为空
				if (cur)
				{
					// 返回需要构造一个迭代器返回
					return iterator(cur, this);
				}
			}

			// 没找到就返回一个 空的迭代器
			return iterator(nullptr, this);
		}

		iterator end()
		{
			// 构造一个空的迭代器返回
			return iterator(nullptr, this);
		}

		// const 的 begin()和 end()
 		const_iterator begin() const
		{
			// 找第一个桶
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				// 当前桶的不为空
				if (cur)
				{
					// 返回需要构造一个迭代器返回
					return const_iterator(cur, this);
				}
			}

			// 没找到就返回一个 空的迭代器
			return const_iterator(nullptr, this);
		}

		const_iterator end() const
		{
			// 构造一个空的迭代器返回
			return const_iterator(nullptr, this);
		}
    };

 哈希桶迭代器的 const *this 问题

 在哈希桶当中的 const 版本的 begin()和 end()当中,返回的是一个 迭代器 ,此时我们调用了一个 迭代器的构造函数,这个构造函数当中还传入了 当前哈希桶对象 的 this 指针。但是这个指针在 const 版本的begin()和 end()函数当中是被 const 修饰的,但是 在构造函数当中接受 this 指针的 形参不是 const 的,此时就会发生权限的放大,就会报错。

 当然,最简单的方式就是 在构造函数的当中用一个 const 的形参去接受,然后 构造函数对应初始化的成员也应该是 const 的,这样才能正确接受 const 的 this 指针。

虽然在我们之前对 哈希桶当中的实现来看,在迭代器当中我们并没有在迭代器当中使用哈希桶的指针来修改过 哈希桶当中的 _table 这个 vector 等等成员什么的,只是从 _table 当中读数据,所以是对于 const 的指针是没有问题的。

也就是说在 当前实现的 迭代器当中是不会通过 哈希表 指针修改到哈希表的,在迭代器当中是使用 _node 当前迭代器指向的 结点指针来修改 到 哈希表的,所以在当前是没有问题的。

 而且,构造函数只用写一个 const this指针的版本就足够了,因为普通迭代器传过来的 普通 this 指针 转给 const 的形参是完全没有问题的,是权限的缩小,然后初始化给 const 的指针也是没有问题

 修改之后的代码:

	template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
	struct HTIterator
	{
·········
		const hashtable<K, T, KeyOfT, HashFunc>* _pht;

		HTIterator(Node* node, const hashtable<K, T, KeyOfT, HashFunc>* pht)
			:_node(node)
			, _pht(pht)
		{}
·········
    }

 库当中实现不是按照上述方式实现的,他是 把 iteator 和 const_iterator 两个迭代器都实现了一遍
 

 而且,在const_iterator 当中,不仅仅是 哈希桶指针是 const 的,结点的指针也是 const 的。

哈希桶迭代器完整代码

 

// 前置声明
	template<class K, class T, class KeyOfT, class HashFunc>
	class hashtable;

	// 在哈希表 的 iterator        template<K, T, T* , T& , KeyOfT , HashFunc>
	// 在哈希表 的 const_iterator  template<K, T, const T* , const T& , KeyOfT , HashFunc>
	template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
	struct HTIterator
	{
		typedef hash_bucket::HashNode<T> Node;

		// 方便下述书写 迭代器的模版参数
		typedef HTIterator<K, T, Ptr, Ref, KeyOfT, HashFunc> Self;

		Node* _node;
		hashtable<K, T, KeyOfT, HashFunc>* _pht;

		HTIterator(Node* node, hashtable<K, T, KeyOfT, HashFunc>* pht)
			:_node(node)
			, _pht(pht)
		{}

		Ref operator*()
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			return &_node->_data;
		}

		Self& operator++()
		{
			if (_node->_next)
			{
				// 当前桶还没完
				// 就继续遍历当前桶
				_node = _node->_next;
			}
			else
			{
				// 两个仿函数类的实例化
				KeyOfT kot;
				HashFunc hf;

				// 计算当前结点的 hash值
				size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
				// 从下一个位置查找查找下一个不为空的桶
				++hashi;
				while (hashi < _pht->_table.size())
				{
					// 如果遍历到的桶不为空
					if (_pht->_table[hashi])
					{
						// 把桶的第一个元素赋值给 _node
						_node = _pht->_table[hashi];
						return *this;
					}
					else
					{
						// 如果桶为空 继续寻找下一个桶
						++hashi;
					}
				}

				// 走到这说明 后面的桶都为空
				// 或者当前桶就是最后一个桶了
				_node = nullptr;
			}

			return *this;
		}

		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}
	};


    // 哈希桶当中 begin 和 end 代码部分演示
    template<class K, class T,class KeyOfT , class HashFunc = DefaultHashFunc<K>>
	class hashtable
	{
		typedef HashNode<T> Node;

		// 有元声明
		template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
		friend struct HTIterator;
	public:
		typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> iterator;
		typedef HTIterator<K, T, const T*, const T&, KeyOfT, HashFunc> const_iterator;


		hashtable()
		{
			_table.resize(10, nullptr);
		}

		iterator begin()
		{
			// 找第一个桶
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				// 当前桶的不为空
				if (cur)
				{
					// 返回需要构造一个迭代器返回
					return iterator(cur, this);
				}
			}

			// 没找到就返回一个 空的迭代器
			return iterator(nullptr, this);
		}

		iterator end()
		{
			// 构造一个空的迭代器返回
			return iterator(nullptr, this);
		}

		// const 的 begin()和 end()
 		const_iterator begin() const
		{
			// 找第一个桶
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				// 当前桶的不为空
				if (cur)
				{
					// 返回需要构造一个迭代器返回
					return const_iterator(cur, this);
				}
			}

			// 没找到就返回一个 空的迭代器
			return const_iterator(nullptr, this);
		}

		const_iterator end() const
		{
			// 构造一个空的迭代器返回
			return const_iterator(nullptr, this);
		}
·······················
·······················
·······················
    };

unordered_set 和 unordered_map 当中复用 哈希桶的迭代器

 unordered_set

 set 当中只有 key ,用户是不能对 这个 key 进行修改的,所以,在 unordered_set 当中 ,不管是 iterator 还是 const_iteartor 都是 const_iteartor。想实现这样的功能,直接把 const_iteartor  typedef 出 iterator 和 const_iteartor 就可以实现。

unordered_set 当中就只有 const 版本的 begin()和 end()函数,实现:

	public:
		typedef typename hash_bucket::hashtable<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename hash_bucket::hashtable<K, K, SetKeyOfT>::const_iterator const_iterator;

        // 返回值是 iterator 还是 const_iterator 都一样,都是 const_iterator
		iterator begin() const
		{
			return _ht.begin();
		}

		iterator end() const
		{
			return _ht.end();
		}

 如果只提供const 版本的迭代器的话,不管是 const 对象还是 普通对象都可以调用它,普通对象调用就是 权限的缩小,const 调用就是 权限的平移。

#pragma once
#include"hash.h"

namespace unordered
{
	template<class K>
	class unordered_set
	{
		// set 当中从 T 当中取出 key 的仿函数
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename hash_bucket::hashtable<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename hash_bucket::hashtable<K, K, SetKeyOfT>::const_iterator const_iterator;

		iterator begin() const
		{
			return _ht.begin();
		}

		iterator end() const
		{
			return _ht.end();
		}

		pair<iterator, bool> insert(const K& key)
		{
			pair<typename hash_bucket::hashtable<K, K, SetKeyOfT>::iterator, bool> pit = _ht.Insert(key);
			return pair<iterator, bool>(pit.first, pit.second);
		}

	private:
		hash_bucket::hashtable<K, K, SetKeyOfT> _ht;
	};
}

 

unordered_map

unordered_map  当中,按照 map 和 set 当中一样的性质进行套用和封装,在 unordered_map  当中的哈希桶构造的时候,对 pair 当中的 key 就使用 const 的方式,这样就可以修改到 value 而不修改到 key 了。
 当然,insert()也不能再像直线一样返回一个 bool 值了,得返回一个 迭代器和 bool 值,pair<iterator, bool>。

而且 find()函数也要返回一个 迭代器 ,修改如下:
 

pair<iterator, bool> Insert(const T& data)
		{
			HashFunc hf;
			KeyOfT kot;

			iterator it = find(kot(data));
			if (it != end())
			{
				return make_pair(iterator, false);
			}

			// 负载因子 到 1 就扩容(每一个桶当中都有数据)
			if (_n == _table.size())
			{
				size_t newsize = _table.size() * 2;
				vector<Node*> newTable;
				newTable.resize(newsize, nullptr);

				for (int i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;  // 保存链表的下一个结点

						// 头插到新表当中
						size_t hashi = hf(kot(cur->_data)) % newTable.size();
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;

						// 向链表后迭代
						cur = next;
					}
				}

				// 交换 两个表在 对象当中的指向,让编译器 帮我们释放旧表的空间
				_table.swap(newTable); 
			}

			// 计算hash值
			size_t hashi = hf(data) % _table.size();

			Node* newnode = new Node(data);
			newnode->_next = _table[hashi];
			_table[hashi] = newnode;
			++_n;

			return make_pair(iterator(newnode, this), true);
		}

 

iterator find(const K& key)
		{
			HashFunc hf;
			KeyOfT kot;

			size_t hash = hf(kot(key)) % _table.size();
			Node* cur = _table[hash];
			while (cur)
			{
				// 如果找到就返回 迭代器,不在返回结点的指针
				if (kot(cur->_data) == key)
					return iterator(cur, this);
				
				cur = cur->_next;
			}
			return nullptr;
		}

 在上述修改之后,在 unordered_set 和 unordered_map 当中的 insert()函数也要进行修改,它的返回值也应该是一个 pair<iterator, bool>。但是在上述修改之后就会引发另一个问题,如下所示:

在 set 当中的insert()函数的 pair<iterator, bool> 的iterator 是 const_iterator ; 哈希桶当中的pair<iterator, bool> 的 iterator 就是 iterator。就类似于 vector<int> 和 vector<double> 的关系,是两个模版实例化出的类型,已经不是权限的放大和缩小了,根本就不是一个类型了。vector<int> 和 vector<const int> 两个也是不同的类型。

而 map 中不会,因为map 当中的 iteartor 就是 iterator,const_iterator 就是  const_iterator。

但是前提是 实现了 传入 iterator 就构造 const_iteartor 的const 的构造函数,我们在 map 和 set 当中也就行了说明,他是 一份函数代码,在 iterator 和 const_iteartor 当中可以 实例化出两种函数,在 iteartor 就是 传入 iterator 的拷贝构造函数,在 const_iteartor 就是 传入 iterator 就构造 一个const_iteartor 的构造函数,具体可以参考 map 和 set 的博客。

修改就是 增加一个 拷贝构造函数/const构造函数。

 像上述的修改方式在 list 当中也支持,如果用一个 iterator 取 构造 const_iterator 在 list 当中是支持的:
 

 我们可以看到it2 迭代器是 const 迭代器,但是 it 是 普通 list 对象,那么调用的迭代器的就是 普通迭代器,像上述的方式是支持的。库当中是这样实现的:

 在以前的迭代器实现当中,我们没有写这样的,类似拷贝构造函数一样的 函数,因为以前的迭代器的拷贝就是一个浅拷贝,只需要拷贝迭代器当中的指针就行了,而编译器自动生成的 浅拷贝的拷贝构造函数就已经够用了,不需要我们在写了。而上述写了之后,相当于是把 iterator 的不需要写的浅拷贝的拷贝构造函数写了,把 const_iterator 的构造函数写了,但是在  iterator 当中本来是不用写的,编译器自己写的就够用了,但是需要写一个 传入 iterator 构造 const_iterator 的构造函数,写了这个函数也就相当于是把 iterator 的浅拷贝函数给写了。

而且这个函数的参数类型没有用 self ,而是用的 iterator,如果用 self 那么这个函数不管在哪都是一个拷贝构造函数;但是用的是 iterator,T* 和 T& 是写死的,此处就是绝妙之处。 


 还需要注意的是, 不同的对象但是类模版类型相同,是可以访问到对方的private 对象的,如果是不同累模版类型就不能了:

比如 A<int, double&>  和 A<int, double&> 的两个对象是可以访问的,但是如果是 A<int, double&> 和 A<int, const double> 两个对象就不行了。在库当中可以实现是因为 人家不是类模板,是 struct的。因为类有类域

 

#pragma once
#include"hash.h"

namespace unordered
{
	template<class K, class V>
	class unordered_map
	{
		// map 当中从 T 当中取出key 的仿函数
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:

		typedef typename hash_bucket::hashtable<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
		typedef typename hash_bucket::hashtable<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;

		iterator begin()
		{
			return _ht.begin();
		}

		iterator end()
		{
			return _ht.end();
		}

		const_iterator begin() const
		{
			return _ht.begin();
		}

		const_iterator end() const 
		{
			return _ht.end();
		}

		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _ht.Insert(kv);
		}

	private:
		hash_bucket::hashtable<K, pair<const K, V>, MapKeyOfT> _ht;
	};
}

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1056442.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java环境配置无效

Java环境配置无效 老是使用1.8版本&#xff0c;象牛皮癣。 查找java来源 where java 打开C:\Windows\System32 删掉java.exe javaaw.exe javaaws.exe 正常

【超强图解Docker常见命令与实战】

目录 一、镜像容器基础&#xff08;一&#xff09;基本概念&#xff08;二&#xff09;镜像&#xff08;三&#xff09;容器&#xff08;四&#xff09;运行的容器&#xff08;五&#xff09; 镜像层(image layer) 二、常用命令&#xff08;一&#xff09;docker create <im…

XWiki Platform 安全漏洞RCE:CVE-2023-37462

2023年7月14日&#xff0c;美国国家标准与技术研究院&#xff08;NIST&#xff09;维护的综合性漏洞数据库&#xff08;NVD&#xff09;第一次记录了这个漏洞&#xff0c;这个漏洞影响版本是从7.0到14.48&#xff08;不包括&#xff09;和从14.5到14.10.4&#xff08;不包括&am…

BGP服务器租用价格表_腾讯云PK阿里云

BGP云服务器像阿里云和腾讯云均是BGP多线网络&#xff0c;速度更快延迟更低&#xff0c;阿里云BGP服务器2核2G3M带宽优惠价格108元一年起&#xff0c;腾讯云BGP服务器2核2G3M带宽95元一年起&#xff0c;阿腾云分享更多云服务器配置如2核4G、4核8G、8核16G等配置价格表如下&…

A*搜索算法(含Java源代码)

前言 本来是想写一块的&#xff0c;但是为了这个国庆的专属勋章就分开写了&#xff0c;这个侧重还是对作业题目要求的实现。 课题目的 理解 A Star 算法设计流程。 理解 A Star 算法的启发式函数的作用。 掌握 A Start 解决搜索问题的过程&#xff0c;能够应用 A Star 算法…

凉鞋的 Godot 笔记 101. Hello Godot!

101. Hello Godot 学习任何一门技术&#xff0c;第一件事就是先完成 Hello World&#xff01;的输出 所以我们也来先完成 Godot 的 Hello World。 我们所使用的 Godot 版本是 4.x 版本。 安装的过程就不给大家展示了&#xff0c;笔者更推荐初学者用 Steam 版本的 Godot&…

Scala第十八章节

Scala第十八章节 scala总目录 文档资料下载 章节目标 掌握Iterable集合相关内容.掌握Seq集合相关内容.掌握Set集合相关内容.掌握Map集合相关内容.掌握统计字符个数案例. 1. Iterable 1.1 概述 Iterable代表一个可以迭代的集合, 它继承了Traversable特质, 同时也是其他集合…

学习开发一个RISC-V上的操作系统(汪辰老师) — 环境配置

前言 &#xff08;1&#xff09;此系列文章是跟着汪辰老师的RISC-V课程所记录的学习笔记。 &#xff08;2&#xff09;该课程相关代码gitee链接&#xff1b; &#xff08;3&#xff09;PLCT实验室实习生长期招聘&#xff1a;招聘信息链接 &#xff08;4&#xff09;在学习汪辰老…

正则表达式验证和跨域postmessage

1.用正则表达式验证用户名 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title>…

msvcp120.dll放在哪个文件夹?msvcp120.dll丢失解决方法详细分析

Msvcp120.dll 丢失可能会导致一些基于 Microsoft Visual C 编写的程序和游戏无法正常运行。Msvcp120.dll 是 Microsoft Visual C Redistributable 的一个组件&#xff0c;它包含了 C 运行时库&#xff0c;这些库在运行程序时会被加载到内存中。如果该文件丢失或损坏&#xff0c…

多目标平衡黏菌算法(MOEOSMA)求解八个现实世界受约束的工程问题

目录 1 受约束的工程问题 1.1 减速器设计问题(Speed reducer design problem) 1.2 弹簧设计问题(Spring design problem) 1.3 静压推力轴承设计问题(Hydrostatic thrust bearing design problem) 1.4 振动平台设计问题(Vibrating platform design problem) 1.5 汽车侧面碰…

18.示例程序(编码器接口测速)

STM32标准库开发-各章节笔记-查阅传送门_Archie_IT的博客-CSDN博客https://blog.csdn.net/m0_61712829/article/details/132434192?spm1001.2014.3001.5501 main.c #include "stm32f10x.h" // Device header #include "Delay.h" #incl…

数据结构:KMP算法的原理图解和代码解析

文章目录 应用场景算法方案算法原理完整代码 本篇总结的是关于串中的KMP算法解析 应用场景 现给定两个串&#xff0c;现在要看较短的一个串是不是较长的串的子串&#xff0c;如果是就输出子串后面的内容&#xff0c;如果不是则输出Not Found 能匹配到&#xff1a; 长串&…

基于SSM的连锁经营商业管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

JavaSE | 初识Java(五) | 方法的使用

方法就是一个代码片段&#xff0c; 类似于 C 语言中的 " 函数 "。 方法可以是我们代码逻辑更清晰&#xff0c;并且可以服用方法使代码更简洁 方法语法格式 // 方法定义 修饰符 返回值类型 方法名称([参数类型 形参 ...]){ 方法体代码; [return 返回值]; } 实例&…

自媒体文章改写工具-自媒体文章改写软件

自媒体时代已然来临&#xff0c;每个人都有机会成为自己的内容创作者&#xff0c;分享自己的观点和故事。在竞争激烈的自媒体领域&#xff0c;如何让自己的文章脱颖而出&#xff0c;吸引更多读者成为了一个重要的问题。 自媒体文章改写是一项旨在提高文章原创性和吸引力的关键任…

Arcgis打开影像分析窗口没反应

Arcgis打开影像分析窗口没反应 问题描述 做NDVI计算的时候&#xff0c;一直点击窗口-影像分析&#xff0c;发现影像分析的小界面一直不跳出来。 原因 后来发现是被内容列表给遮住了&#xff0c;其实是已经出来了的。。 拖动内容列表就能找到。 解决方案 内容列表和影像分…

热点文章采集-热点资讯采集工具免费

在信息时代&#xff0c;掌握热点资讯、了解热门时事、采集热门文章是许多自媒体从业者和信息追踪者的重要任务。然而&#xff0c;这并不是一项容易的任务。信息的海洋庞大而繁杂&#xff0c;要从中捞取有价值的热点和文章需要耗费大量时间和精力。 热点资讯采集&#xff1a;信息…

[Linux 基础] 一篇带你了解linux权限问题

文章目录 1、Linux下的两种用户2、文件类型和访问权限&#xff08;事物属性&#xff09;2.1 Linux下的文件类型2.2 基本权限2.3 文件权限值的表示方法&#xff08;1&#xff09;字符表示方法&#xff08;2&#xff09;8进制数值表示方法 2.4 文件访问权限的相关设置方法(1) chm…

番外4:VMware安装

step4: 安装过程中&#xff0c;有些选项不需要点&#xff08;安装地址建议选C盘或默认&#xff0c;装载在其他盘后续会报错&#xff09;&#xff0c;如&#xff1a; may error&#xff08;本人猜测安装虚拟机完整版需要C盘的一些桥插件支持&#xff09;: step5: 安装虚拟机成功…