【C++】unordered系列的封装

目录

一、改造哈希表

[1. 改造模板参数](#1. 改造模板参数)

[2. 改造细节: KeyOfT](#2. 改造细节: KeyOfT)

[3. 实现哈希表的迭代器](#3. 实现哈希表的迭代器)

(1)迭代器的定义

(2)*重载和->重载

[(3)重载 == 和 !=](#(3)重载 == 和 !=)

[(4)重载 ++](#(4)重载 ++)

(5)普通迭代器和const迭代器

[4. 改造插入的返回值](#4. 改造插入的返回值)

[5. 哈希表的总代码](#5. 哈希表的总代码)

二、封装unordered_set和unordered_map

[1. 封装迭代器](#1. 封装迭代器)

(1)unordered_set的迭代器

(2)unordered_map的迭代器

[2. 插入操作的封装](#2. 插入操作的封装)

[3. unordered_map的 [ ] 重载](#3. unordered_map的 [ ] 重载)

[三、 最终代码](#三、 最终代码)


前言

C++的STL中的unordered_set和unordered_map与set和map的使用区别就是unordered系列的存储数据是无序的(因为底层是哈希表),而set和map的数据是有序的(因为底层是红黑树)。所以,unordered_set系列的实现和set和map很相似,下面我们就来使用哈希表的封装实现一下C++中的unordered_set和unordered_map。

一、改造哈希表

下面我们将使用开散列的链地址法 (开链法 )实现unordered系列的set和map。基于【C++】哈希表-CSDN博客 实现的代码。

1. 改造模板参数

和set和map类似,unordered_set和unordered_map的底层使用的都是一个哈希表哈希表的Kay_Value模型实现的。对于unordered_set需要使用 HashTable<Key, Key>来实现;而unordered_map需要使用HashTable<Key, pair<Key, Value>>来实现。

所以,我们需改造HashTable的模板参数,让哈希表能够实现第二个模板参数既可以表示Key也可以表示pair<Key, Value>的类型,所以我们需要修改哈希表的存储数据,如下所示:

cpp 复制代码
// HashTable.h
template<class T>
struct HashNode // 单链表节点
{
	T _data; // 这个既可以表示key,也可以表示pair类型
	HashNode<T>* _next; // 表示单链表的下一个节点

	// 构造
	HashNode(const T& data)
		:_data(data)
		, _next(nullptr)
	{  }
};

// 这里的第二个参数 T 表示到是 key 还是 pair<key,value>
template<class K, class T, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
	typedef HashNode<T> Node;
public:
    // 哈希表操作的实现
    // ......
private:
	vector<Node*> _table; // 指针数组
	size_t _n = 0; // 表示表中有效数据(头指针)的个数
};

除此之外,当我们实现哈希表的其他操作的时候,比如插入操作,我们需要计算出所插入数据的映射对应的下标,由于这里的插入的数据类型是T,而T又可以是 key 也可以是 pair<key,value>,而我们计算时只需要key就可以了,所以如果这里的T是key,则就没有问题,但如果这里的T是 pair<key,value> ,则就需要取出pair中的first来作为映射的对象。所以我们这里可以使用一个仿函数开解决这个问题。如下所示:

cpp 复制代码
// 这里的KeyOfT表示从T类型中取出key
template<class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
	typedef HashNode<T> Node;
public:
    // 哈希表操作的实现
    // ......
private:
	vector<Node*> _table; // 指针数组
	size_t _n = 0; // 表示表中有效数据(头指针)的个数
};

这里的KeyOfT可以传一个实现方括号重载的类,表示从T类型中取出key。这个类的实现地方是在实现unordered_set和unordered_map类中实现的,对于unordered_set就直接返回key就可以了;对于unordered_map就直接返回pair中的first。

所以对于unordered_set和unordered_map的大体是如下所示:

对于unordered_set实现:

cpp 复制代码
#pragma once
#include "HashTable.h"
namespace MyCreate
{
	template<class K>
	class unordered_set
	{
		struct UnorderedSetOfKey
		{
			// 获取key中的key本身
			const K& operator()(const K& key)
			{
				return key;
			}
		};

		// unordered_set的操作

	private:
		hash_bucket::HashTable<K, K, UnorderedSetOfKey> _ht;
	};
}

对于unordered_map实现:

cpp 复制代码
#pragma once
#include "HashTable.h"
namespace MyCreate
{
	template<class K, class V>
	class unordered_map
	{
		struct UnorderedMapOfKey
		{
			// 获取pair<K,V>中的key
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;

			}
		};

		// unordered_map的操作

	private:
		hash_bucket::HashTable<K, pair<K, V>, UnorderedMapOfKey> _ht;

	};
}

2. 改造细节: KeyOfT

因为,我们现在需要使用KeyOfT来实现映射时取出key的操作,因为我们主要修改的地方就是在计算映射下标时,需要通过KeyOfT的对象将T中的key取出来再进行取模求映射下标位置的操作,需要修改的地方有许多地方,修改方法就是:

像这样,将所有需要使用T类型中的key时的地方都进行修改,则最后修改后的代码实现如下所示:

cpp 复制代码
#pragma once
#include<vector>
#include<string>
#include<utility>
using namespace std;

template<class K>
struct DefaultHashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

//使用模板的特化,解决string类型的问题
template<>
struct DefaultHashFunc<string>
{
	size_t operator()(const string& str)
	{
		size_t hash = 0;
		for (auto ch : str)
		{
			hash *= 131;
			hash += ch;
		}
		return hash;
	}
};

namespace hash_bucket
{
	template<class T>
	struct HashNode // 单链表节点
	{
		T _data; // 这个既可以表示key,也可以表示pair类型
		HashNode<T>* _next; // 表示单链表的下一个节点

		// 构造
		HashNode(const T& data)
			:_data(data)
			, _next(nullptr)
		{  }
	};

	// 这里的KeyOfT表示从T类型中取出key
	template<class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
	class HashTable
	{
		typedef HashNode<T> Node;
	public:
		// 构造
		HashTable()
		{
			_table.resize(10, nullptr);
		}
		// 析构
		~HashTable()
		{
			// 遍历哈希表
			for (int i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				// 遍历单链表
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_table[i] = nullptr;
			}
		}

		// 查找操作
		Node* Find(const K& key)
		{
			HashFunc hf;
			KeyOfT kot;
			size_t hash_i = hf(key) % _table.size();
			Node* cur = _table[hash_i];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}
		// 删除
		bool Erase(const K& key)
		{
			HashFunc hf;
			KeyOfT kot;
			size_t hash_i = hf(key) % _table.size();
			Node* cur = _table[hash_i];
			Node* prev = nullptr;
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (prev == nullptr)
					{
						_table[hash_i] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;

					}
					delete cur;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}

		// 插入
		bool insert(const T& data)
		{
			// 判断是否存在
			if (Find(data.first)) return false;

			//判断是否扩容
			_CheckCapcity();

			// 插入新节点---头插
			HashFunc hf; // 兼容计算string类型映射的下标
			KeyOfT kot; // 取出T中的key
			size_t hash_i = hf(kot(data)) % _table.size();
			Node* cur = new Node(data);

			cur->_next = _table[hash_i];
			_table[hash_i] = cur;

			_n++;
			return true;
		}

	private:
		// 判断是否扩容
		void _CheckCapcity()
		{
			// 计算负载因子
			size_t a = _n * 10 / _table.size(); 
			if (a == 10) // 负载因子到1就扩容
			{
				// 2倍扩容
				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;
						//头插到新表
						HashFunc hf;
						KeyOfT kot;
						size_t hash_i = hf(kot(cur->_data)) % _table.size();
						cur->_next = newTable[hash_i];
						newTable[hash_i] = cur;
						cur = next;
					}
					_table[i] = nullptr;
				}
				//交换和这两个表
				_table.swap(newTable);
			}
		}

	private:
		vector<Node*> _table; // 指针数组
		size_t _n = 0; // 表示表中有效数据(头指针)的个数
	};
}

3. 实现哈希表的迭代器

在上一篇文章中,我们没有实现哈希表的迭代器,这里我们可以来实现一下。

(1)迭代器的定义

哈希表的迭代器,我们需要实现普通迭代器和const迭代器,所以首先我们就需要三个模板参数(class T, class Ptr, class Ref 用于方便实现const迭代器),这里还需要KeyOfT的模板参数以及HashFunc用来兼容string类型的模板参数(用于方便迭代器++重载的实现)。通过开散列熟悉的哈希表中存储的是一个一个的链表,所以其迭代器也是节点指针,所以迭代器的基本定义如下所示:

cpp 复制代码
// 实现哈希表的迭代器
template<class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
struct HTIterator
{
	typedef HashNode<T> Node;
	typedef HTIterator<T, Ref, Ptr, KeyOfT, HashFunc> Self;
	Node* _node; // 迭代器就是指针

	// 构造函数
	HTIterator(Node* node)
		:_node(node)
	{ }
};

但是,为了后续便于是重载++,还需要一个表示当前哈希表的指针,因此,这里的定义需要改一改,首先,模板参数需要再加一个K用于构建哈希表,然后迭代器变量需要添加一个哈希表指针,则迭代器定义如下所示:

cpp 复制代码
// 实现哈希表的迭代器
template<class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
struct HTIterator
{
	typedef HashNode<T> Node;
	typedef HTIterator<K, T, Ref, Ptr, KeyOfT, HashFunc> Self;
	Node* _node; // 迭代器就是指针
	const HashTable<K, T, KeyOfT, HashFunc>* _pht; //需要一个哈希表,便于实现++重载

	// 构造函数
	HTIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht)
		:_node(node)
		,_pht(pht)
	{ }
};

但是,由于我们的哈希表中也要调用迭代器,那么这里又有一个问题,即:迭代器中调了哈希表,而哈希表中调了迭代器 。此时就是一个循环依赖 了,会出现编译错误 ,因此,我们应该添加一个哈希表的前置声明 ,表示哈希表这个类存在;同时由于哈希表中的成员变量是私有的,所以在定义哈希表的类中,我们应该声明迭代器这个类是哈希表类的友元,允许它访问私有成员,这样就不会有问题了,则实现代码如下所示:

cpp 复制代码
// 前置声明
template<class K, class T, class KeyOfT, class HashFunc>
class HashTable;

// 实现哈希表的迭代器
template<class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
struct HTIterator
{
	typedef HashNode<T> Node;
	typedef HTIterator<K, T, Ref, Ptr, KeyOfT, HashFunc> Self;
	Node* _node; // 迭代器就是指针
	const HashTable<K, T, KeyOfT, HashFunc>* _pht; //需要一个哈希表,便于实现++重载

	// 构造函数
	HTIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht)
		:_node(node)
		,_pht(pht)
	{ }
};

// 这里的KeyOfT表示从T类型中取出key
template<class K, class T, class KeyOfT, class HashFunc= DefaultHashFunc<K>>
class HashTable
{
	// 友元声明
	template<class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
	friend struct HTIterator;

	typedef HashNode<T> Node;
public:
	// 成员函数
	// ......

private:
	vector<Node*> _table; // 指针数组
	size_t _n = 0; // 表示表中有效数据(头指针)的个数
};

(2)*重载和->重载

实现 * 重载的函数需要使用到第二个模板参数Ref ,这样可以保证:

  • 当定义迭代器时传递的第二个模板参数是 T& 时,是普通迭代器;
  • 如果第二个模板参数是 const T& 时,是const迭代器

所以 * 重载的代码如下所示:

cpp 复制代码
Ref operator*()
{
	return _node->_data;
}

实现 -> 重载的函数需要使用到第三个模板参数Ref ,这样可以保证:

  • 当定义迭代器时传递的第三个模板参数是 T* 时,是普通迭代器;
  • 如果第三个模板参数是const T*时,是const迭代器

-> 重载的代码如下所示:

cpp 复制代码
Ptr operator->()
{
	return &_node->_data;
}

(3)重载 == 和 !=

!=和==的重载只需要比较两个指针是否相等即可,实现代码如下所示:

cpp 复制代码
// 迭代器与迭代器的比较
bool operator!=(const Self& s) const
{
	return _node != s._node;
}

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

(4)重载 ++

哈希表的迭代器每加一下,就是寻找下一个有效数据,这里分为了两种情况:

  1. 如果加之前的迭代器不是当前链表的最后一个节点,则加一,就是直接将迭代器指向其下一个节点即可(单链表的操作);
  2. 如果加之前的迭代器是当前链表的最后一个节点,则加一就需要寻找下一个有效链表(即存在数据的链表)的第一个节点。如下图所示:可以看到,此时的++操作,就是从下一个位置开始查找查找下一个不为空的桶。

第2中情况中,我们需要查找下一个不为空的位置,那么我们就需要知道当前是在什么位置,即使用当前存储的值先来进行映射出当前位置哈希表的哪一个位置才行,所以我们就需要知道哈希表有多长,这就需要我们使用到哈希表的变量(因此我们在定义迭代器的时候就需要定义一下哈希表的变量变量,以及增加模板参数)。

所以具体的实现代码如下所示:

cpp 复制代码
// 前置++
Self& operator++()
{
	if (_node->_next)
	{
		_node = _node->_next; // 如果当前链表还没有完,则指向下一个节点
	}
	else
	{
		// 如果当前链表还没有完,则找下一个不为空的链表
		KeyOfT kot;
		HashFunc hf;
		size_t	hash_i = hf(kot(_node->_data)) % (_pht->_table.size());
		hash_i++;
		// 找下一个位置
		while (hash_i<_pht->_table.size()) // 判断位置,可以防止全空时陷入死循环
		{
			if (_pht->_table[hash_i])
			{
				_node = _pht->_table[hash_i];
				return *this;
			}
			hash_i++;
		}
		_node = nullptr;
	}
	return *this;
}

(5)普通迭代器和const迭代器

我们熟悉了迭代器的类之后,就可以分别实现普通迭代器和const迭代器,通过传递对应的模板参数,就可以实现对应的类型迭代器。如下所示:

cpp 复制代码
// 这里的 KeyOfT 表示从 T 类型中取出 key
template<class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
	// 友元声明
	template<class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
	friend struct HTIterator;

	typedef HashNode<T> Node;
public:
	// 定义迭代器
	typedef HTIterator<K, T, T&, T*, KeyOfT, HashFunc> iterator;
	typedef HTIterator<K, T, const T&, const T*, KeyOfT, HashFunc> const_iterator;

    // 哈希表的成员函数
    // ......


private:
	vector<Node*> _table; // 指针数组
	size_t _n = 0; // 表示表中有效数据(头指针)的个数
};

下面我们就可以实现哈希表的普通版本的和const版本的begin()和end()函数了。对应begin函数,就是找到第一个不为空的桶的位置;而end就返回的是一个空的迭代器就行了,实现如下所示:

cpp 复制代码
iterator begin()
{
	// 找到第一个不为空的桶
	for (size_t i = 0; i < _table.size(); i++)
	{
		Node* cur = _table[i];
		if (cur)
		{
			//不为空
			return iterator(cur, this); // 构造一个表示cur的迭代器
		}
	}
	return iterator(nullptr, this); // 如果全为空,则返回表示空的迭代器
}
iterator end()
{
	return iterator(nullptr, this); // 返回表示空的迭代器
}
const_iterator begin() const
{
	// 找到第一个不为空的桶
	for (size_t i = 0; i < _table.size(); i++)
	{
		Node* cur = _table[i];
		if (cur)
		{
			//不为空
			return const_iterator(cur, this); // 构造一个表示cur的迭代器
		}
	}
	return const_iterator(nullptr, this); // 如果全为空,则返回表示空的迭代器
}
const_iterator end() const
{
	return const_iterator(nullptr, this); // 返回表示空的迭代器
}

4. 改造插入的返回值

在库中,插入操作的返回值是一个pair<当前位置的迭代器,布尔值> ,所以我们这里也需要实现改造一下这里的插入操作,改造后的代码如下所示:

cpp 复制代码
// 插入
pair<iterator, bool> insert(const T& data)
{
	// 判断是否存在
	KeyOfT kot; // 取出T中的key
	Node* tmp = Find(kot(data));
	if (tmp) return make_pair(iterator(tmp, this), true);

	//判断是否扩容
	_CheckCapcity();

	// 插入新节点---头插
	HashFunc hf; // 兼容计算string类型映射的下标
	size_t hash_i = hf(kot(data)) % _table.size();
	Node* cur = new Node(data);

	cur->_next = _table[hash_i];
	_table[hash_i] = cur;

	_n++;
	return make_pair(iterator(cur,this), true);
}

5. 哈希表的总代码

改造后,哈希表总的实现代码:

cpp 复制代码
#pragma once
#include<vector>
#include<string>
#include<utility>
using namespace std;

template<class K>
struct DefaultHashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

//使用模板的特化,解决string类型的问题
template<>
struct DefaultHashFunc<string>
{
	size_t operator()(const string& str)
	{
		size_t hash = 0;
		for (auto ch : str)
		{
			hash *= 131;
			hash += ch;
		}
		return hash;
	}
};

namespace hash_bucket
{
	template<class T>
	struct HashNode // 单链表节点
	{
		T _data; // 这个既可以表示key,也可以表示pair类型
		HashNode<T>* _next; // 表示单链表的下一个节点

		// 构造
		HashNode(const T& data)
			:_data(data)
			, _next(nullptr)
		{  }
	};

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

	// 实现哈希表的迭代器
	template<class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
	struct HTIterator
	{
		typedef HashNode<T> Node;
		typedef HTIterator<K, T, Ref, Ptr, KeyOfT, HashFunc> Self;
		Node* _node; // 迭代器就是指针
		const HashTable<K, T, KeyOfT, HashFunc>* _pht; //需要一个哈希表,便于实现++重载

		// 构造函数
		HTIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht)
			:_node(node)
			,_pht(pht)
		{ }

		Ref operator*()
		{
			return _node->_data;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}
		// 迭代器与迭代器的比较
		bool operator!=(const Self& s) const
		{
			return _node != s._node;
		}

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


		// 前置++
		Self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next; // 如果当前链表还没有完,则指向下一个节点
			}
			else
			{
				// 如果当前链表还没有完,则找下一个不为空的链表
				KeyOfT kot;
				HashFunc hf;
				size_t	hash_i = hf(kot(_node->_data)) % (_pht->_table.size());
				hash_i++;
				// 找下一个位置
				while (hash_i<_pht->_table.size()) // 判断位置,可以防止全空时陷入死循环
				{
					if (_pht->_table[hash_i])
					{
						_node = _pht->_table[hash_i];
						return *this;
					}
					hash_i++;
				}
				_node = nullptr;
			}
			return *this;
		}

	};




	// 这里的 KeyOfT 表示从 T 类型中取出 key
	template<class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
	class HashTable
	{
		// 友元声明
		template<class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
		friend struct HTIterator;

		typedef HashNode<T> Node;
	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); // 构造一个表示cur的迭代器
				}
			}
			return iterator(nullptr, this); // 如果全为空,则返回表示空的迭代器
		}
		iterator end()
		{
			return iterator(nullptr, this); // 返回表示空的迭代器
		}
		const_iterator begin() const
		{
			// 找到第一个不为空的桶
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				if (cur)
				{
					//不为空
					return const_iterator(cur, this); // 构造一个表示cur的迭代器
				}
			}
			return const_iterator(nullptr, this); // 如果全为空,则返回表示空的迭代器
		}
		const_iterator end() const
		{
			return const_iterator(nullptr, this); // 返回表示空的迭代器
		}

		// 构造
		HashTable()
		{
			_table.resize(10, nullptr);
		}
		// 析构
		~HashTable()
		{
			// 遍历哈希表
			for (int i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				// 遍历单链表
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_table[i] = nullptr;
			}
		}

		// 查找操作
		Node* Find(const K& key)
		{
			HashFunc hf;
			KeyOfT kot;
			size_t hash_i = hf(key) % _table.size();
			Node* cur = _table[hash_i];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}
		// 删除
		bool Erase(const K& key)
		{
			HashFunc hf;
			KeyOfT kot;
			size_t hash_i = hf(key) % _table.size();
			Node* cur = _table[hash_i];
			Node* prev = nullptr;
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (prev == nullptr)
					{
						_table[hash_i] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;

					}
					delete cur;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}

		// 插入
		pair<iterator, bool> insert(const T& data)
		{
			// 判断是否存在
			KeyOfT kot; // 取出T中的key
			Node* tmp = Find(kot(data));
			if (tmp) return make_pair(iterator(tmp, this), true);

			//判断是否扩容
			_CheckCapcity();

			// 插入新节点---头插
			HashFunc hf; // 兼容计算string类型映射的下标
			size_t hash_i = hf(kot(data)) % _table.size();
			Node* cur = new Node(data);

			cur->_next = _table[hash_i];
			_table[hash_i] = cur;

			_n++;
			return make_pair(iterator(cur,this), true);
		}

	private:
		// 判断是否扩容
		void _CheckCapcity()
		{
			// 计算负载因子
			size_t a = _n * 10 / _table.size(); 
			if (a == 10) // 负载因子到1就扩容
			{
				// 2倍扩容
				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;
						//头插到新表
						HashFunc hf;
						KeyOfT kot;
						size_t hash_i = hf(kot(cur->_data)) % _table.size();
						cur->_next = newTable[hash_i];
						newTable[hash_i] = cur;
						cur = next;
					}
					_table[i] = nullptr;
				}
				//交换和这两个表
				_table.swap(newTable);
			}
		}

	private:
		vector<Node*> _table; // 指针数组
		size_t _n = 0; // 表示表中有效数据(头指针)的个数
	};
}

到这里我们就几乎改造完了哈希表。下面就来封装一下unordered_set和unordered_map

二、封装unordered_set和unordered_map

1. 封装迭代器

(1)unordered_set的迭代器

在unordered_set的迭代器中,为了保证Key不能被修改,所以这里只有const迭代器,所以,不论这里不论是普通迭代器还是const迭代器,都是const迭代器。其定义如下所示:

cpp 复制代码
#pragma once
#include "HashTable.h"
namespace MyCreate
{
	template<class K>
	class unordered_set
	{
		struct UnorderedSetOfKey
		{
			// 获取key中的key本身
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		// 定义迭代器
		typename typedef hash_bucket::HashTable<K, K, UnorderedSetOfKey>::const_iterator iterator;
		typename typedef hash_bucket::HashTable<K, K, UnorderedSetOfKey>::const_iterator const_iterator;
		// 迭代器的操作
		iterator begin() const
		{
			return _ht.begin();
		}
		iterator end() const
		{
			return _ht.end();
		}

	private:
		hash_bucket::HashTable<K, K, UnorderedSetOfKey> _ht;
	};
}

注意:

这里的begin和end后面都要加const修饰,保证它们调用的都是哈希表中的const迭代器。否则就会出现错误:

因为这里如果不加const修饰,那么这里的end会调用哈希表的普通迭代器的end,会返回普通迭代器,而这里end的返回值是const迭代器,出现了类型不匹配,所以会报错。

(2)unordered_map的迭代器

对于unordered_map而言,普通迭代器(unordered_map实现层)就是普通迭代器(哈希表实现层),const迭代器(unordered_map实现层)就是const迭代器(哈希表实现层)。实现如下:

cpp 复制代码
#pragma once
#include "HashTable.h"
namespace MyCreate
{
	template<class K, class V>
	class unordered_map
	{
		struct UnorderedMapOfKey
		{
			// 获取pair<K,V>中的key
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;

			}
		};
	public:
		// 定义迭代器
		typename typedef hash_bucket::HashTable<K, K, UnorderedSetOfKey>::const_iterator iterator;
		typename typedef hash_bucket::HashTable<K, K, UnorderedSetOfKey>::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();
		}
		// unordered_map的操作
		// ......
	private:
		hash_bucket::HashTable<K, pair<K, V>, UnorderedMapOfKey> _ht;

	};
}

但是我们还需要保证在普通迭代器中,unordered_map中<Key,Value>中的Key不能修改,而Value可以修改。这里的解决办法就是:pair的K,将K改为const K,即:pair<K, V>修改为pair<const K, V>即可。代码实现如下所示:

cpp 复制代码
#pragma once
#include "HashTable.h"
namespace MyCreate
{
	template<class K, class V>
	class unordered_map
	{
		struct UnorderedMapOfKey
		{
			// 获取pair<K,V>中的key
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;

			}
		};
	public:
		// 定义迭代器
		typename typedef hash_bucket::HashTable<K, pair<const K, V>, UnorderedSetOfKey>::iterator iterator;
		typename typedef hash_bucket::HashTable<K, pair<const K, V>, UnorderedSetOfKey>::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();
		}
		// unordered_map的操作
		// ......
	private:
		hash_bucket::HashTable<K, pair<K, V>, UnorderedMapOfKey> _ht;

	};
}

这样,那我们实现后,如果是普通迭代器,则key不能修改,value可以修改,如果是const迭代器,则key和value都不能修改。

2. 插入操作的封装

对于unordered_set和unordered_map的插入操作,都是只用调用哈希表中的插入函数就可以了。

对于unordered_map,直接调用哈希表中的插入函数就行,实现代码如下所示:

cpp 复制代码
// unordered_map的插入
pair<iterator, bool> insert(const pair<K, V>& kv)
{
	return _ht.insert(kv);
}

但是对于unordered_set,则只是调用哈希表就不可了,这里有一些细节要考虑。首先我们来看一下代码:

出现这个问题的原因我们在封装set的插入时说过了。这里我们再说一遍:

因为在unordered_set这里的插入操作的返回值pair<iterator, bool>中的itreator本质是const迭代器,而调用的哈希表返回的pair<iterator, bool>中的迭代器是const迭代器,所以这两个构造的pair类型是有两个不同的模板(一个是普通迭代器,一个是cosnt迭代器)参数实现的,所以这两个pair的类型是不吐的,所以就无法转换。就会报错。

解决方法就是在哈希表的迭代器中添加如下的函数:

这个如果这个类被实例化成了普通迭代器,则这个函数就是一个拷贝构造函数;如果这个类被实例化成了一个const迭代器,则这个函数就是一个构造函数,它支持一个普通迭代构造一个const迭代器。

写了这部分代码,我们的unordered_set的代码就可以通过直接调用哈希表的插入函数实现了,如以下代码:

cpp 复制代码
// unordered_set的插入操作
pair<iterator, bool> insert(const K& key)
{
	return _ht.insert(key);
}

当然,我们也可要这样写:

cpp 复制代码
// unordered_set的插入操作
pair<iterator, bool> insert(const K& key)
{
	pair<hash_bucket::HashTable<K, K, UnorderedSetOfKey>::iterator, bool> ret = _ht.insert(key);

	// ret中的first是一个普通迭代器,这里就是使用普通迭代器来构造一个const迭代器
	return make_pair(ret.first, ret.second); 
}

3. unordered_map的 [ ] 重载

unordered_map的 [ ] 重载函数的本质和map一样,都是 插入+引用返回 :首先是插入k的操作,然后:

  • 如果k已经在表里面了,这时插入的返回值就是pair<表中的k所在节点的迭代器iterator, false>;
  • 如果k不在表里面,这时插入的返回值就是pair<新插入k所在节点的迭代器iterator, false>;

然后再返回ret中的first迭代器对应的second的引用。代码实现如下所示:

cpp 复制代码
// [ ] 重载
V& operator[](const K& key)
{
	pair<iterator, bool> ret = _ht.insert(make_pair(key, V()));
	return ret.first->second;
}

三、 最终代码

下面我们汇总一下本篇文章的代码:

HashTable.h

cpp 复制代码
#pragma once
#include<vector>
#include<string>
#include<utility>
using namespace std;

template<class K>
struct DefaultHashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

//使用模板的特化,解决string类型的问题
template<>
struct DefaultHashFunc<string>
{
	size_t operator()(const string& str)
	{
		size_t hash = 0;
		for (auto ch : str)
		{
			hash *= 131;
			hash += ch;
		}
		return hash;
	}
};

namespace hash_bucket
{
	template<class T>
	struct HashNode // 单链表节点
	{
		T _data; // 这个既可以表示key,也可以表示pair类型
		HashNode<T>* _next; // 表示单链表的下一个节点

		// 构造
		HashNode(const T& data)
			:_data(data)
			, _next(nullptr)
		{  }
	};

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

	// 实现哈希表的迭代器
	template<class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
	struct HTIterator
	{
		typedef HashNode<T> Node;
		typedef HTIterator<K, T, Ref, Ptr, KeyOfT, HashFunc> Self;
		Node* _node; // 迭代器就是指针
		const HashTable<K, T, KeyOfT, HashFunc>* _pht; //需要一个哈希表,便于实现++重载

		typedef HTIterator<K, T, T&, T*, KeyOfT, HashFunc> Iterator;
		// 构造函数
		HTIterator(const Iterator& it)
			:_node(it._node)
			,_pht(it._pht)
		{ }

		// 构造函数
		HTIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht)
			:_node(node)
			,_pht(pht)
		{ }

		Ref operator*()
		{
			return _node->_data;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}
		// 迭代器与迭代器的比较
		bool operator!=(const Self& s) const
		{
			return _node != s._node;
		}

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


		
		// 前置++
		Self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next; // 如果当前链表还没有完,则指向下一个节点
			}
			else
			{
				// 如果当前链表还没有完,则找下一个不为空的链表
				KeyOfT kot;
				HashFunc hf;
				size_t	hash_i = hf(kot(_node->_data)) % (_pht->_table.size());
				hash_i++;
				// 找下一个位置
				while (hash_i<_pht->_table.size()) // 判断位置,可以防止全空时陷入死循环
				{
					if (_pht->_table[hash_i])
					{
						_node = _pht->_table[hash_i];
						return *this;
					}
					hash_i++;
				}
				_node = nullptr;
			}
			return *this;
		}

	};




	// 这里的 KeyOfT 表示从 T 类型中取出 key
	template<class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
	class HashTable
	{
		// 友元声明
		template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
		friend struct HTIterator;

		typedef HashNode<T> Node;
	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); // 构造一个表示cur的迭代器
				}
			}
			return iterator(nullptr, this); // 如果全为空,则返回表示空的迭代器
		}
		iterator end()
		{
			return iterator(nullptr, this); // 返回表示空的迭代器
		}
		const_iterator begin() const
		{
			// 找到第一个不为空的桶
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				if (cur)
				{
					//不为空
					return const_iterator(cur, this); // 构造一个表示cur的迭代器
				}
			}
			return const_iterator(nullptr, this); // 如果全为空,则返回表示空的迭代器
		}
		const_iterator end() const
		{
			return const_iterator(nullptr, this); // 返回表示空的迭代器
		}

		// 构造
		HashTable()
		{
			_table.resize(10, nullptr);
		}
		// 析构
		~HashTable()
		{
			// 遍历哈希表
			for (int i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				// 遍历单链表
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_table[i] = nullptr;
			}
		}

		// 查找操作
		Node* Find(const K& key)
		{
			HashFunc hf;
			KeyOfT kot;
			size_t hash_i = hf(key) % _table.size();
			Node* cur = _table[hash_i];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}
		// 删除
		bool Erase(const K& key)
		{
			HashFunc hf;
			KeyOfT kot;
			size_t hash_i = hf(key) % _table.size();
			Node* cur = _table[hash_i];
			Node* prev = nullptr;
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (prev == nullptr)
					{
						_table[hash_i] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;

					}
					delete cur;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}

		// 插入
		pair<iterator, bool> insert(const T& data)
		{
			// 判断是否存在
			KeyOfT kot; // 取出T中的key
			Node* tmp = Find(kot(data));
			if (tmp) return make_pair(iterator(tmp, this), true);

			//判断是否扩容
			_CheckCapcity();

			// 插入新节点---头插
			HashFunc hf; // 兼容计算string类型映射的下标
			size_t hash_i = hf(kot(data)) % _table.size();
			Node* cur = new Node(data);

			cur->_next = _table[hash_i];
			_table[hash_i] = cur;

			_n++;
			return make_pair(iterator(cur,this), true);
		}

	private:
		// 判断是否扩容
		void _CheckCapcity()
		{
			// 计算负载因子
			size_t a = _n * 10 / _table.size(); 
			if (a == 10) // 负载因子到1就扩容
			{
				// 2倍扩容
				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;
						//头插到新表
						HashFunc hf;
						KeyOfT kot;
						size_t hash_i = hf(kot(cur->_data)) % _table.size();
						cur->_next = newTable[hash_i];
						newTable[hash_i] = cur;
						cur = next;
					}
					_table[i] = nullptr;
				}
				//交换和这两个表
				_table.swap(newTable);
			}
		}

	private:
		vector<Node*> _table; // 指针数组
		size_t _n = 0; // 表示表中有效数据(头指针)的个数
	};
}

UnorderedSet.h

cpp 复制代码
#pragma once
#include "HashTable.h"
namespace MyCreate
{
	template<class K>
	class unordered_set
	{
		struct UnorderedSetOfKey
		{
			// 获取key中的key本身
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		// 定义迭代器
		typename typedef hash_bucket::HashTable<K, K, UnorderedSetOfKey>::const_iterator iterator;
		typename typedef hash_bucket::HashTable<K, K, UnorderedSetOfKey>::const_iterator const_iterator;
		// 迭代器的操作
		iterator begin() const
		{
			return _ht.begin();
		}
		iterator end() 
		{
			return _ht.end();
		}

		// unordered_set的插入操作
		//pair<iterator, bool> insert(const K& key)
		//{
		//	return _ht.insert(key);
		//}
		pair<iterator, bool> insert(const K& key)
		{
			pair<hash_bucket::HashTable<K, K, UnorderedSetOfKey>::iterator, bool> ret = _ht.insert(key);

			// ret中的first是一个普通迭代器,这里就是使用普通迭代器来构造一个const迭代器
			return make_pair(ret.first, ret.second); 
		}

	private:
		hash_bucket::HashTable<K, K, UnorderedSetOfKey> _ht;
	};
}

UnorderedMap.h

cpp 复制代码
#pragma once
#include "HashTable.h"
namespace MyCreate
{
	template<class K, class V>
	class unordered_map
	{
		struct UnorderedMapOfKey
		{
			// 获取pair<K,V>中的key
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;

			}
		};
	public:
		// 定义迭代器
		typename typedef hash_bucket::HashTable<K, pair<const K, V>, UnorderedMapOfKey>::iterator iterator;
		typename typedef hash_bucket::HashTable<K, pair<const K, V>, UnorderedMapOfKey>::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();
		}
		// unordered_map的插入
		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _ht.insert(kv);
		}

		// [ ] 重载
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _ht.insert(make_pair(key, V()));
			return ret.first->second;
		}

	private:
		hash_bucket::HashTable<K, pair<const K, V>, UnorderedMapOfKey> _ht;

	};
}

感谢各位观看!希望能多多支持

相关推荐
小王不爱笑1325 小时前
IO 模型
开发语言·python
知我Deja_Vu5 小时前
【避坑指南】ConcurrentHashMap 并发计数优化实战
java·开发语言·python
AI+程序员在路上6 小时前
CANopen 协议:介绍、调试命令与应用
linux·c语言·开发语言·网络
2401_831824966 小时前
基于C++的区块链实现
开发语言·c++·算法
汉克老师6 小时前
GESP5级C++考试语法知识(六、链表(一)单链表)
c++·链表·单链表·快慢指针·进阶·gesp5级·gesp五级
m0_518019486 小时前
C++与机器学习框架
开发语言·c++·算法
ZTLJQ6 小时前
深入理解逻辑回归:从数学原理到实战应用
开发语言·python·机器学习
qq_417695056 小时前
C++中的代理模式高级应用
开发语言·c++·算法
学嵌入式的小杨同学7 小时前
STM32 进阶封神之路(十九):ADC 深度解析 —— 从模拟信号到数字转换(底层原理 + 寄存器配置)
c++·stm32·单片机·嵌入式硬件·mcu·架构·硬件架构
xiaoye-duck7 小时前
《算法题讲解指南:动态规划算法--路径问题》--5.不同路径,6.不同路径II
c++·算法·动态规划