C++ STL unordered系列(哈希) 底层剖析与模拟实现万字详解 | 基于哈希表,复刻 SGI-STL 泛型哈希容器架构

目录

引言:

[SGI-STL 哈希容器源码与框架分析](#SGI-STL 哈希容器源码与框架分析)

无序哈希容器核心接口释义

头文件依赖

[stl_hash_set.h 源码核心](#stl_hash_set.h 源码核心)

[stl_hash_map.h 源码核心](#stl_hash_map.h 源码核心)

[stl_hashtable.h 哈希表底层源码](#stl_hashtable.h 哈希表底层源码)

框架核心思想解读

[常见疑问:既然 Value 控制节点存储类型,为什么还需要第二个模板参数 Key?](#常见疑问:既然 Value 控制节点存储类型,为什么还需要第二个模板参数 Key?)

模拟实现unordered_map和unordered_set

[1.迭代封装通用哈希表底层(适配 unordered_set /unordered_map)](#1.迭代封装通用哈希表底层(适配 unordered_set /unordered_map))

[一:初始版本:耦合 pair 的哈希表(仅支持 unordered_map)](#一:初始版本:耦合 pair 的哈希表(仅支持 unordered_map))

[二:搭建初始 unordered_set、unordered_map 外层框架](#二:搭建初始 unordered_set、unordered_map 外层框架)

三:修改哈希表节点与类模板,泛化存储类型

[四:新增 Key 萃取仿函数,解耦 Key 获取逻辑](#四:新增 Key 萃取仿函数,解耦 Key 获取逻辑)

[五:扩展哈希表模板参数,接收 KeyOfT 萃取仿函数](#五:扩展哈希表模板参数,接收 KeyOfT 萃取仿函数)

六:哈希映射仿函数抽离为模板参数,支持自定义哈希

七:相等判断逻辑抽离为谓词仿函数,提升为模板参数

八:总结

2.支持iterator实现

2.1.iterator核心源代码

2.2.iterator实现思路分析

3.map支持\[\]

4.qiu::map和qiu::set实现

[Hash Table.h](#Hash Table.h)

unordered_map.h

unordered_set.h

test.h

结语:


引言:

提示:阅读本文前,需要掌握哈希表和底层封装的思想------------C++ 高阶数据结构:哈希表万字详解 | 哈希冲突、负载因子、扩容机制、链地址法与开放定址法实现【STL unordered系列底层】-CSDN博客

C++ STL map 与 set 底层剖析与模拟实现万字详解|基于红黑树,复刻 SGI-STL 泛型复用架构-CSDN博客
在 C++ STL 容器体系中,map/set 依托红黑树实现有序查找,时间复杂度为 \(O(logN)\);而 C++11 正式引入的 unordered_map/unordered_set 基于哈希表实现,能够在平均 O(1) 时间复杂度下完成增删查操作,是高频查询场景的首选容器。

不少开发者仅熟练使用无序容器,但对其底层原理、桶结构、哈希映射、冲突解决、扩容机制、迭代器遍历逻辑一知半解,尤其不理解: 为什么 unordered_map 存储 pair、unordered_set 存储普通键,却能复用同一套哈希表底层? 为什么 Key 不能修改、Value 可以修改? STL 如何通过模板 + 仿函数萃取实现完全解耦?

事实上 C++11 的无序容器设计完全沿袭 SGI-STL 早期 hash_map/hash_set 的底层架构。STL 采用底层通用哈希表 + 上层容器适配封装的分层思想,通过「键萃取仿函数、哈希仿函数、相等判断仿函数」三套策略类,彻底解耦数据存储、哈希映射、元素比对逻辑,实现一套底层结构复用两种上层容器。

本文将 从 SGI-STL 源码溯源 → 逐层迭代手写重构 → 完整实现迭代器、扩容、删除、查找、operator \[\] ,从零复刻一版贴合标准库设计的 unordered_set/unordered_map,彻底吃透哈希容器的底层核心精髓。

那么话不多说,接下来进入正文------------------------------>


SGI-STL 哈希容器源码与框架分析

无序哈希容器核心接口释义

理解哈希表底层结构之后,我们再去看哈希表专属接口,一切就很好理解了

bucket_count------返回有多少个桶

max_bucket_count------最大桶数量

bucket_size------获取指定桶长度

bucket------给一个Key,获取这个Key在哪个桶

load_factor------当前负载因子

max_load_factor------最大负载因子(1)

rehash------可以类比为普通容器的 resize,但存在明显区别:扩容时会从 SGI 预设素数表选取容量,最终开辟的桶数量大于等于传入的参数 n

reserve------和 rehash 使用体感十分接近,reserve 本质上是 rehash 的上层封装。设计 reserve 主要目的是和 STL 其他容器接口风格保持统一,提升 API 一致性。

头文件依赖

我们当前分析的版本为 SGI-STL 3.0。需要注意:SGI-STL 3.0 属于 C++11 之前的 STL 实现,源码内部并不存在unordered_mapunordered_set,这两个无序容器是 C++11 标准正式引入的。

但 SGI-STL 3.0 内部已经实现了哈希表结构,对应的容器名为hash_maphash_set,属于非标准扩展容器 。这里的 "非标准" 含义是:C++ 标准并未强制要求编译器实现这组容器。相关底层源码分布在 hash_map.hhash_set.hstl_hash_map.hstl_hash_set.hstl_hashtable.h 等文件中。

cpp 复制代码
//hash_set头文件依赖(对标C++11的unordered_set)
#ifndef __SGI_STL_HASH_SET
#define __SGI_STL_HASH_SET

#ifndef __SGI_STL_INTERNAL_HASHTABLE_H
#include <stl_hashtable.h>
#endif 

#include <stl_hash_set.h>


//hash_map头文件依赖(对标C++11的unordered_map)
#ifndef __SGI_STL_HASH_MAP
#define __SGI_STL_HASH_MAP

#ifndef __SGI_STL_INTERNAL_HASHTABLE_H
#include <stl_hashtable.h>
#endif 

#include <stl_hash_map.h>

从头文件依赖可以看出:hash_map 和 hash_set 底层共用同一套数据结构,核心实现都放在 stl_hashtable.h ,也就是哈希表。

stl_hash_set.h 源码核心

cpp 复制代码
#ifndef __STL_LIMITED_DEFAULT_TEMPLATES
template <class Value, class HashFcn = hash<Value>,
          class EqualKey = equal_to<Value>,
          class Alloc = alloc>
#else
template <class Value, class HashFcn, class EqualKey, class Alloc = alloc>
#endif
class hash_set
{
private:
  typedef hashtable<Value, Value, HashFcn, identity<Value>, 
                    EqualKey, Alloc> ht;
  ht rep;
public:
  typedef typename ht::key_type key_type;
  typedef typename ht::value_type value_type;
};

hash_set 中存储的 Value 就是 Key,传入两个相同的 Value,目的是和下方stl_hash_map的模板设计保持统一。

stl_hash_map.h 源码核心

cpp 复制代码
#ifndef __STL_LIMITED_DEFAULT_TEMPLATES
template <class Key, class T, class HashFcn = hash<Key>,
          class EqualKey = equal_to<Key>,
          class Alloc = alloc>
#else
template <class Key, class T, class HashFcn, class EqualKey, 
          class Alloc = alloc>
#endif
class hash_map
{
private:
  typedef hashtable<pair<const Key, T>, Key, HashFcn,
                    select1st<pair<const Key, T> >, EqualKey, Alloc> ht;
  ht rep;

public:
  typedef typename ht::key_type key_type;
  typedef T data_type;
  typedef T mapped_type;
  typedef typename ht::value_type value_type;
};

重点:unordered_map 的 value_type 并不是 T,而是 pair<const Key, T>

将 Key 设置为 const,目的是防止用户迭代过程中修改 Key,一旦 Key 被修改,哈希映射位置失效,会破坏哈希表结构。

stl_hashtable.h 哈希表底层源码

cpp 复制代码
template <class Value>
struct __hashtable_node
{
  __hashtable_node* next;
  Value val;
};  

template <class Value, class Key, class HashFcn,
          class ExtractKey, class EqualKey,
          class Alloc>
class hashtable {
public:
  typedef Key key_type;
  typedef Value value_type;
  typedef HashFcn hasher;
  typedef EqualKey key_equal;

  typedef size_t            size_type;
  typedef ptrdiff_t         difference_type;
  typedef value_type*       pointer;
  typedef const value_type* const_pointer;
  typedef value_type&       reference;
  typedef const value_type& const_reference;

  hasher hash_funct() const { return hash; }
  key_equal key_eq() const { return equals; }

private:
  hasher hash;
  key_equal equals;
  ExtractKey get_key;

  typedef __hashtable_node<Value> node;
  typedef simple_alloc<node, Alloc> node_allocator;

  vector<node*,Alloc> buckets;
  size_type num_elements;
};

这套封装思路和 map、set 依托红黑树的设计高度一致。

注意底层hashtable模板参数:Value 代表节点存储的数据,Key 代表用于哈希、比较的关键字,和我们自己手写简易哈希表的参数顺序刚好相反,但不影响功能逻辑。

框架核心思想解读

通过源码可以看到,结构上hash_maphash_setmapset思路高度相似:复用同一个底层容器 hashtable。

hash_sethashtable 传入相同的 Key 与 Value,搭配 identity 萃取器直接取值;

hash_map 底层存储 pair<const Key, T>,依靠键萃取仿函数select1st(即模板参数列表中的ExtractKey) 从 pair 中提取 Key,以此实现 K-V 模型。

常见疑问:既然 Value 控制节点存储类型,为什么还需要第二个模板参数 Key?

很多人学到这里都会产生疑惑:unordered_set 场景下 Key 和 Value 完全相同,为什么要额外多出 Key 参数?------原理和 set/map 复用红黑树的设计思想同源

核心原因: find()、erase() 这类接口接收的参数类型是 Key。

  • set:插入元素类型 = 查找元素类型(都是 Key);
  • map:插入的是完整 pair<const K,T>,但查找、删除只需要传入 Key。

如果不单独抽离 Key 作为模板参数,底层哈希表无法统一处理两种场景的查找入参。

附:我们基于手写哈希表封装 hash_set(C++11 unordered_set) 与 hash_map(C++11 unordered_map) 时,可以先实现最简版本:暂时只支持基础 key/value 模型。只要理解这套分层思想,后续拓展自定义比较器、unordered_multiset/unordered_multimap 只是增量开发。


模拟实现unordered_map和unordered_set

1.迭代封装通用哈希表底层(适配 unordered_set /unordered_map)

我们先实现基础版本哈希表,节点内部直接存储pair<K,V>。该版本哈希表强绑定键值对结构,只能给 unordered_map 使用,无法适配只存储单个 key 的 unordered_set。后续借助模板 + 萃取仿函数抽取 key的思路进行改造,让同一套哈希表底层同时支撑 unordered_set 和 unordered_map。

这部分的思路和封装map和set部分的思路是极其相似的,所以这里我就不过多讲解了

一:初始版本:耦合 pair 的哈希表(仅支持 unordered_map)

Hash Table.h

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


inline unsigned long __stl_next_prime(unsigned long n)
{
	// Note: assumes long is at least 32 bits.
	static const int __stl_num_primes = 28;
	static const unsigned long __stl_prime_list[__stl_num_primes] =
	{
		53, 97, 193, 389, 769,
		1543, 3079, 6151, 12289, 24593,
		49157, 98317, 196613, 393241, 786433,
		1572869, 3145739, 6291469, 12582917, 25165843,
		50331653, 100663319, 201326611, 402653189, 805306457,
		1610612741, 3221225473, 4294967291
	};
	const unsigned long* first = __stl_prime_list;
	const unsigned long* last = __stl_prime_list + __stl_num_primes;
	const unsigned long* pos = lower_bound(first, last, n);
	return pos == last ? *(last - 1) : *pos;
}

template<class K, class V>
class HashNode
{
	using Node = HashNode<K, V>;
public:
	HashNode() = default;
	HashNode(const pair<K, V>& kv)
		:_kv(kv)
	{

	}

	pair<K, V> _kv;
	Node* _next = nullptr;
};

template<class K>
class Hash
{
public:
	size_t operator()(const K& k)
	{
		return (size_t)k;
	}
};

template<>
class Hash<string>
{
public:
	size_t operator()(const string& s)
	{
		size_t ret = 0;
		for (auto c : s)
		{
			ret = ret * 131 + c;
		}
		return ret;
	}
};


template<class K, class V>
class HashTable
{
	using Node = HashNode<K, V>;
public:
	HashTable()
		:_tables(__stl_next_prime(0))
		, _n(0)
	{

	}

	HashTable(const HashTable<K, V>& ht)
		:_tables(__stl_next_prime(ht._tables.size()))
		, _n(0)
	{
		for (size_t i = 0; i < ht._tables.size(); i++)
		{
			Node* cur = ht._tables[i];
			while (cur)
			{
				Insert(cur->_kv);
				cur = cur->_next;
			}
		}
	}

	HashTable<K, V>& operator=(HashTable<K, V> ht)
	{
		swap(ht);
		return *this;
	}

	~HashTable()
	{
		for (size_t i = 0; i < _tables.size(); i++)
		{
			Node* cur = _tables[i];
			while (cur)
			{
				Node* next = cur->_next;
				delete cur;
				cur = next;
			}
			_tables[i] = nullptr;
		}
	}

	void swap(HashTable<K, V>& ht)
	{
		std::swap(this->_tables, ht._tables);
		std::swap(this->_n, ht._n);
	}

	bool Insert(const pair<K, V>& kv)
	{
		if (Find(kv.first))
			return false;
		if (_n == _tables.size())
		{
			vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
			for (int i = 0; i < _tables.size(); i++)
			{
				if (_tables[i])
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t hash0 = Hash<K>()(cur->_kv.first) % newtables.size();
						cur->_next = newtables[hash0];
						newtables[hash0] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
			}
			_tables.swap(newtables);
		}

		size_t hash0 = Hash<K>()(kv.first) % _tables.size();
		Node* newnode = new Node(kv);
		newnode->_next = _tables[hash0];
		_tables[hash0] = newnode;

		++_n;
		return true;
	}
	bool Erase(const K& k)
	{
		size_t hash0 = Hash<K>()(k) % _tables.size();
		Node* cur = _tables[hash0];
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_kv.first == k)
			{
				if (parent)
				{
					parent->_next = cur->_next;
				}
				else
				{
					_tables[hash0] = cur->_next;
				}
				delete cur;
				--_n;
				return true;
			}
			else
			{
				parent = cur;
				cur = cur->_next;
			}
		}

		return false;
	}
	Node* Find(const K& k)
	{
		size_t hash0 = Hash<K>()(k) % _tables.size();
		Node* cur = _tables[hash0];
		while (cur)
		{
			if (cur->_kv.first == k)
				return cur;
			else
				cur = cur->_next;
		}


		return nullptr;
	}



private:
	vector<Node*> _tables;
	size_t _n = 0;//数据个数

};

二:搭建初始 unordered_set、unordered_map 外层框架

unordered_set.h

cpp 复制代码
	template<class K>
	class unordered_set
	{
		using ht = HashTable<K, K>;

	public:
		bool insert(const K& kv)
		{
			return _ht.Insert(kv);
		}
		bool erase(const K& k)
		{
			return _ht.Erase(k);
		}


	private:
		ht _ht;
	};

unordered_map.h

cpp 复制代码
	template<class K,class V>
	class unordered_map
	{
		using ht = HashTable<K, pair<const K, V>>;

	public:
		bool insert(const pair<const K, V>& kv)
		{
			return _ht.Insert(kv);
		}
		bool erase(const K& k)
		{
			return _ht.Erase(k);
		}


	private:
		ht _ht;
	};

三:修改哈希表节点与类模板,泛化存储类型

我们将节点存储内容泛化为模板参数T

cpp 复制代码
template<class K, class T>
class HashNode
{
	using Node = HashNode<K, T>;
public:
	HashNode() = default;
	HashNode(const T& kv)
		:_data(kv)
	{

	}

	T _data;
	Node* _next = nullptr;
};


template<class K, class T>
class HashTable
{
	using Node = HashNode<K, T>;
	bool Insert(const T& kv);
}

此时出现核心矛盾:底层哈希表无法预知模板参数T是什么类型。 T可能是unordered_set存储的 Key,也可能是unordered_map存储的pair<const K,V>

如果直接使用类型原生比较运算符:pair 默认operator==会同时对比 key 与 value;但哈希容器规则要求仅依靠 key 判断元素重复,不能对比 value。

解决方案:在unordered_setunordered_map内部各自定义 Key 萃取仿函数,负责从存储对象T中提取关键字 Key;将萃取仿函数作为模板参数传入底层哈希表。哈希表内部统一调用仿函数获取 key,彻底消除硬编码.first

四:新增 Key 萃取仿函数,解耦 Key 获取逻辑

unordered_set.h

cpp 复制代码
#pragma once
#include "Hash Table.h"

namespace qiu
{
	template<class K>
	class unordered_set
	{
		struct SetKeyOfT;

		using ht = HashTable<K, K, SetKeyOfT>;
	public:
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

		bool insert(const K& kv)
		{
			return _ht.Insert(kv);
		}
		bool erase(const K& k)
		{
			return _ht.Erase(k);
		}


	private:
		ht _ht;
	};
}

unordered_map.h

cpp 复制代码
#pragma once
#include "Hash Table.h"

namespace qiu
{
	template<class K,class V>
	class unordered_map
	{
		struct MapKeyOfT;

		using ht = HashTable<K, pair<const K, V>, MapKeyOfT>;
	public:
		struct MapKeyOfT
		{
			const K& operator()(const pair<const K,V>& kv)
			{
				return kv.first;
			}
		};

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


	private:
		ht _ht;
	};

}

五:扩展哈希表模板参数,接收 KeyOfT 萃取仿函数

给 Hashtable 增加模板参数KeyOfT接收上层传入的 key 提取仿函数,把代码里所有硬编码_data.first全部替换为仿函数调用。重点改造 insert , fiind 和 erase 接口,其余接口同步适配模板参数。

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


inline unsigned long __stl_next_prime(unsigned long n)
{
	// Note: assumes long is at least 32 bits.
	static const int __stl_num_primes = 28;
	static const unsigned long __stl_prime_list[__stl_num_primes] =
	{
		53, 97, 193, 389, 769,
		1543, 3079, 6151, 12289, 24593,
		49157, 98317, 196613, 393241, 786433,
		1572869, 3145739, 6291469, 12582917, 25165843,
		50331653, 100663319, 201326611, 402653189, 805306457,
		1610612741, 3221225473, 4294967291
	};
	const unsigned long* first = __stl_prime_list;
	const unsigned long* last = __stl_prime_list + __stl_num_primes;
	const unsigned long* pos = lower_bound(first, last, n);
	return pos == last ? *(last - 1) : *pos;
}

template<class T>
class HashNode
{
	using Node = HashNode<T>;
public:
	HashNode() = default;
	HashNode(const T& kv)
		:_data(kv)
	{

	}

	T _data;
	Node* _next = nullptr;
};

template<class K>
class Hash
{
public:
	size_t operator()(const K& k)
	{
		return (size_t)k;
	}
};

template<>
class Hash<string>
{
public:
	size_t operator()(const string& s)
	{
		size_t ret = 0;
		for (auto c : s)
		{
			ret = ret * 131 + c;
		}
		return ret;
	}
};


template<class K, class T,class KeyOfT>
class HashTable
{
	using Node = HashNode<T>;
public:
	HashTable()
		:_tables(__stl_next_prime(0))
		, _n(0)
	{

	}

	HashTable(const HashTable<K, T, KeyOfT>& ht)
		:_tables(__stl_next_prime(ht._tables.size()))
		, _n(0)
	{
		for (size_t i = 0; i < ht._tables.size(); i++)
		{
			Node* cur = ht._tables[i];
			while (cur)
			{
				Insert(cur->_data);
				cur = cur->_next;
			}
		}
	}

	HashTable<K, T, KeyOfT>& operator=(HashTable<K, T, KeyOfT> ht)
	{
		swap(ht);
		return *this;
	}

	~HashTable()
	{
		for (size_t i = 0; i < _tables.size(); i++)
		{
			Node* cur = _tables[i];
			while (cur)
			{
				Node* next = cur->_next;
				delete cur;
				cur = next;
			}
			_tables[i] = nullptr;
		}
	}

	void swap(HashTable<K, T, KeyOfT>& ht)
	{
		std::swap(this->_tables, ht._tables);
		std::swap(this->_n, ht._n);
	}

	bool Insert(const T& kv)
	{
		KeyOfT kot;
		if (Find(kot(kv)))
			return false;
		if (_n == _tables.size())
		{
			vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
			for (int i = 0; i < _tables.size(); i++)
			{
				if (_tables[i])
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t hash0 = Hash<K>()(kot(cur->_data)) % newtables.size();
						cur->_next = newtables[hash0];
						newtables[hash0] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
			}
			_tables.swap(newtables);
		}

		size_t hash0 = Hash<K>()(kot(kv)) % _tables.size();
		Node* newnode = new Node(kv);
		newnode->_next = _tables[hash0];
		_tables[hash0] = newnode;

		++_n;
		return true;
	}
	bool Erase(const K& k)
	{
		size_t hash0 = Hash<K>()(k) % _tables.size();
		Node* cur = _tables[hash0];
		Node* parent = nullptr;
		while (cur)
		{
			KeyOfT kot;
			if (kot(cur->_data) == k)
			{
				if (parent)
				{
					parent->_next = cur->_next;
				}
				else
				{
					_tables[hash0] = cur->_next;
				}
				delete cur;
				--_n;
				return true;
			}
			else
			{
				parent = cur;
				cur = cur->_next;
			}
		}

		return false;
	}
	Node* Find(const K& k)
	{
		KeyOfT kot;
		size_t hash0 = Hash<K>()(k) % _tables.size();
		Node* cur = _tables[hash0];
		while (cur)
		{
			if (kot(cur->_data) == k)
				return cur;
			else
				cur = cur->_next;
		}


		return nullptr;
	}



private:
	vector<Node*> _tables;
	size_t _n = 0;//数据个数

};

当前版本哈希函数Hash<K>仍然硬编码在哈希表内部,下一步我们将把哈希仿函数提升为模板参数,对外支持自定义哈希规则

六:哈希映射仿函数抽离为模板参数,支持自定义哈希

改造思路比较清晰:将哈希表内部使用的哈希映射仿函数,一路向上透传,作为unordered_setunordered_map模板参数,并设置默认实参。上层容器接收哈希仿函数后,转发给底层HashTable

unordered_set.h

cpp 复制代码
#pragma once
#include "Hash Table.h"

namespace qiu
{
	template<class K,class Hash = HashKey<K>>
	class unordered_set
	{
		struct SetKeyOfT;

		using ht = HashTable<K, K, SetKeyOfT, Hash>;
	public:
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

		bool insert(const K& kv)
		{
			return _ht.Insert(kv);
		}
		bool erase(const K& k)
		{
			return _ht.Erase(k);
		}


	private:
		ht _ht;
	};
}

unordered_map.h

cpp 复制代码
#pragma once
#include "Hash Table.h"

namespace qiu
{
	template<class K,class V, class Hash = HashKey<K>>
	class unordered_map
	{
		struct MapKeyOfT;

		using ht = HashTable<K, pair<const K, V>, MapKeyOfT, Hash>;
	public:
		struct MapKeyOfT
		{
			const K& operator()(const pair<const K,V>& kv)
			{
				return kv.first;
			}
		};

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


	private:
		ht _ht;
	};

}

Hash Table.h

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


inline unsigned long __stl_next_prime(unsigned long n)
{
	// Note: assumes long is at least 32 bits.
	static const int __stl_num_primes = 28;
	static const unsigned long __stl_prime_list[__stl_num_primes] =
	{
		53, 97, 193, 389, 769,
		1543, 3079, 6151, 12289, 24593,
		49157, 98317, 196613, 393241, 786433,
		1572869, 3145739, 6291469, 12582917, 25165843,
		50331653, 100663319, 201326611, 402653189, 805306457,
		1610612741, 3221225473, 4294967291
	};
	const unsigned long* first = __stl_prime_list;
	const unsigned long* last = __stl_prime_list + __stl_num_primes;
	const unsigned long* pos = lower_bound(first, last, n);
	return pos == last ? *(last - 1) : *pos;
}

template<class T>
class HashNode
{
	using Node = HashNode<T>;
public:
	HashNode() = default;
	HashNode(const T& kv)
		:_data(kv)
	{

	}

	T _data;
	Node* _next = nullptr;
};

template<class K>
class HashKey
{
public:
	size_t operator()(const K& k)
	{
		return (size_t)k;
	}
};

template<>
class HashKey<string>
{
public:
	size_t operator()(const string& s)
	{
		size_t ret = 0;
		for (auto c : s)
		{
			ret = ret * 131 + c;
		}
		return ret;
	}
};


template<class K, class T,class KeyOfT, class Hash>
class HashTable
{
	using Node = HashNode<T>;
public:
	HashTable()
		:_tables(__stl_next_prime(0))
		, _n(0)
	{

	}

	HashTable(const HashTable<K, T, KeyOfT, Hash>& ht)
		:_tables(__stl_next_prime(ht._tables.size()))
		, _n(0)
	{
		for (size_t i = 0; i < ht._tables.size(); i++)
		{
			Node* cur = ht._tables[i];
			while (cur)
			{
				Insert(cur->_data);
				cur = cur->_next;
			}
		}
	}

	HashTable<K, T, KeyOfT, Hash>& operator=(HashTable<K, T, KeyOfT, Hash> ht)
	{
		swap(ht);
		return *this;
	}

	~HashTable()
	{
		for (size_t i = 0; i < _tables.size(); i++)
		{
			Node* cur = _tables[i];
			while (cur)
			{
				Node* next = cur->_next;
				delete cur;
				cur = next;
			}
			_tables[i] = nullptr;
		}
	}

	void swap(HashTable<K, T, KeyOfT, Hash>& ht)
	{
		std::swap(this->_tables, ht._tables);
		std::swap(this->_n, ht._n);
	}

	bool Insert(const T& kv)
	{
		KeyOfT kot;
		Hash hk;
		if (Find(kot(kv)))
			return false;
		if (_n == _tables.size())
		{
			vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
			for (int i = 0; i < _tables.size(); i++)
			{
				if (_tables[i])
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t hash0 = hk(kot(cur->_data)) % newtables.size();
						cur->_next = newtables[hash0];
						newtables[hash0] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
			}
			_tables.swap(newtables);
		}

		size_t hash0 = hk(kot(kv)) % _tables.size();
		Node* newnode = new Node(kv);
		newnode->_next = _tables[hash0];
		_tables[hash0] = newnode;

		++_n;
		return true;
	}
	bool Erase(const K& k)
	{
		Hash hk;
		size_t hash0 = hk(k) % _tables.size();
		Node* cur = _tables[hash0];
		Node* parent = nullptr;
		while (cur)
		{
			KeyOfT kot;
			if (kot(cur->_data) == k)
			{
				if (parent)
				{
					parent->_next = cur->_next;
				}
				else
				{
					_tables[hash0] = cur->_next;
				}
				delete cur;
				--_n;
				return true;
			}
			else
			{
				parent = cur;
				cur = cur->_next;
			}
		}

		return false;
	}
	Node* Find(const K& k)
	{
		Hash hk;
		KeyOfT kot;
		size_t hash0 = hk(k) % _tables.size();
		Node* cur = _tables[hash0];
		while (cur)
		{
			if (kot(cur->_data) == k)
				return cur;
			else
				cur = cur->_next;
		}


		return nullptr;
	}



private:
	vector<Node*> _tables;
	size_t _n = 0;//数据个数

};

本次改造完成后,哈希算法不再被底层哈希表写死。外部使用者可以自定义仿函数传入unordered_set/unordered_map,实现自定义哈希策略。

当前遗留局限:

元素相等判断依旧硬编码使用==运算符,没有抽离相等谓词。接下来我继续新增比较仿函数模板参数,进一步对齐 C++ 标准unordered容器设计。

七:相等判断逻辑抽离为谓词仿函数,提升为模板参数

改造思路和第六步哈希仿函数抽离基本一致,就不过多赘述了

unordered_set.h

cpp 复制代码
#pragma once
#include "Hash Table.h"

namespace qiu
{
	template<class K,class Hash = HashKey<K>,class Pred = Equal<K>>
	class unordered_set
	{
		struct SetKeyOfT;

		using ht = HashTable<K, K, SetKeyOfT, Hash, Pred>;
	public:
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

		bool insert(const K& kv)
		{
			return _ht.Insert(kv);
		}
		bool erase(const K& k)
		{
			return _ht.Erase(k);
		}
		HashNode<K>* find(const K& key)
		{
			return _ht.Find(key);
		}

	private:
		ht _ht;
	};
}

unordered_map.h

cpp 复制代码
#pragma once
#include "Hash Table.h"

namespace qiu
{
	template<class K,class V, class Hash = HashKey<K>, class Pred = Equal<K>>
	class unordered_map
	{
		struct MapKeyOfT;

		using ht = HashTable<K, pair<const K, V>, MapKeyOfT, Hash, Pred>;
	public:
		struct MapKeyOfT
		{
			const K& operator()(const pair<const K,V>& kv)
			{
				return kv.first;
			}
		};

		bool insert(const pair<const K, V>& kv)
		{
			return _ht.Insert(kv);
		}
		bool erase(const K& k)
		{
			return _ht.Erase(k);
		}
		HashNode<pair<const K, V>>* find(const K& key)
		{
			return _ht.Find(key);
		}

	private:
		ht _ht;
	};

}

Hash Table.h

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


inline unsigned long __stl_next_prime(unsigned long n)
{
	// Note: assumes long is at least 32 bits.
	static const int __stl_num_primes = 28;
	static const unsigned long __stl_prime_list[__stl_num_primes] =
	{
		53, 97, 193, 389, 769,
		1543, 3079, 6151, 12289, 24593,
		49157, 98317, 196613, 393241, 786433,
		1572869, 3145739, 6291469, 12582917, 25165843,
		50331653, 100663319, 201326611, 402653189, 805306457,
		1610612741, 3221225473, 4294967291
	};
	const unsigned long* first = __stl_prime_list;
	const unsigned long* last = __stl_prime_list + __stl_num_primes;
	const unsigned long* pos = lower_bound(first, last, n);
	return pos == last ? *(last - 1) : *pos;
}

template<class T>
class HashNode
{
	using Node = HashNode<T>;
public:
	HashNode() = default;
	HashNode(const T& kv)
		:_data(kv)
	{

	}

	T _data;
	Node* _next = nullptr;
};

template<class K>
class Equal
{
public:
	bool operator()(const K& k1,const K& k2)
	{
		return k1 == k2;
	}
};

template<class K>
class HashKey
{
public:
	size_t operator()(const K& k)
	{
		return (size_t)k;
	}
};

template<>
class HashKey<string>
{
public:
	size_t operator()(const string& s)
	{
		size_t ret = 0;
		for (auto c : s)
		{
			ret = ret * 131 + c;
		}
		return ret;
	}
};


template<class K, class T,class KeyOfT, class Hash, class Pred>
class HashTable
{
	using Node = HashNode<T>;
public:
	HashTable()
		:_tables(__stl_next_prime(0))
		, _n(0)
	{

	}

	HashTable(const HashTable<K, T, KeyOfT, Hash, Pred>& ht)
		:_tables(__stl_next_prime(ht._tables.size()))
		, _n(0)
	{
		for (size_t i = 0; i < ht._tables.size(); i++)
		{
			Node* cur = ht._tables[i];
			while (cur)
			{
				Insert(cur->_data);
				cur = cur->_next;
			}
		}
	}

	HashTable<K, T, KeyOfT, Hash, Pred>& operator=(HashTable<K, T, KeyOfT, Hash, Pred> ht)
	{
		swap(ht);
		return *this;
	}

	~HashTable()
	{
		for (size_t i = 0; i < _tables.size(); i++)
		{
			Node* cur = _tables[i];
			while (cur)
			{
				Node* next = cur->_next;
				delete cur;
				cur = next;
			}
			_tables[i] = nullptr;
		}
	}

	void swap(HashTable<K, T, KeyOfT, Hash, Pred>& ht)
	{
		std::swap(this->_tables, ht._tables);
		std::swap(this->_n, ht._n);
	}

	bool Insert(const T& kv)
	{
		KeyOfT kot;
		Hash hk;
		if (Find(kot(kv)))
			return false;
		if (_n == _tables.size())
		{
			vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
			for (int i = 0; i < _tables.size(); i++)
			{
				if (_tables[i])
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t hash0 = hk(kot(cur->_data)) % newtables.size();
						cur->_next = newtables[hash0];
						newtables[hash0] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
			}
			_tables.swap(newtables);
		}

		size_t hash0 = hk(kot(kv)) % _tables.size();
		Node* newnode = new Node(kv);
		newnode->_next = _tables[hash0];
		_tables[hash0] = newnode;

		++_n;
		return true;
	}
	bool Erase(const K& k)
	{
		Pred pd;
		Hash hk;
		size_t hash0 = hk(k) % _tables.size();
		Node* cur = _tables[hash0];
		Node* parent = nullptr;
		while (cur)
		{
			KeyOfT kot;
			if (pd(kot(cur->_data), k))
			{
				if (parent)
				{
					parent->_next = cur->_next;
				}
				else
				{
					_tables[hash0] = cur->_next;
				}
				delete cur;
				--_n;
				return true;
			}
			else
			{
				parent = cur;
				cur = cur->_next;
			}
		}

		return false;
	}
	Node* Find(const K& k)
	{
		Pred pd;
		Hash hk;
		KeyOfT kot;
		size_t hash0 = hk(k) % _tables.size();
		Node* cur = _tables[hash0];
		while (cur)
		{
			if (pd(kot(cur->_data), k))
				return cur;
			else
				cur = cur->_next;
		}


		return nullptr;
	}



private:
	vector<Node*> _tables;
	size_t _n = 0;//数据个数

};

八:总结

本次重构核心目标:消除哈希表底层对上层存储类型的依赖,实现底层复用 。 初始版本哈希表硬编码存储pair<K,V>,只能服务 unordered_map,无法复用给 unordered_set。 解决方案就是 STL 广泛使用的仿函数 + 模板萃取思想

最终版本通过三套仿函数彻底解耦业务逻辑:

  1. KeyOfT 键萃取仿函数:解决「单个 Key /pair 键值对」不同存储类型的取键统一问题,统一从任意存储类型 T 中提取关键字,解决 "T 可能是 Key,也可能是 pair" 的类型差异问题;
  2. Hash 哈希仿函数:剥离哈希算法,支持内置类型、字符串、自定义类型哈希拓展
  3. Pred 相等谓词仿函数 :将元素判等逻辑抽离,脱离原生 == 硬编码

模板参数自上而下传递:unordered_set/unordered_map → HashTable,底层不再写死任何业务逻辑,完全开放扩展。至此通用哈希表骨架搭建完毕,下一步我们在此基础上,实现单向迭代器遍历、素数表扩容、拷贝构造、赋值重载、删除查找、map 专属 operator[] 等核心接口,完成工业级简易哈希容器复刻。

2.支持iterator实现

2.1.iterator核心源代码

cpp 复制代码
template <class Value, class Key, class HashFcn,
 class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_iterator {
 typedef hashtable<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc>
 hashtable;
 typedef __hashtable_iterator<Value, Key, HashFcn, 
 ExtractKey, EqualKey, Alloc>
 iterator;
 typedef __hashtable_const_iterator<Value, Key, HashFcn, 
 ExtractKey, EqualKey, Alloc>
 const_iterator;
 typedef __hashtable_node<Value> node;
 typedef forward_iterator_tag iterator_category;
 typedef Value value_type;
 node* cur;
 hashtable* ht;
 __hashtable_iterator(node* n, hashtable* tab) : cur(n), ht(tab) {}
 __hashtable_iterator() {}
 reference operator*() const { return cur->val; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
 pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
 iterator& operator++();
 iterator operator++(int);
 bool operator==(const iterator& it) const { return cur == it.cur; }
 bool operator!=(const iterator& it) const { return cur != it.cur; }
};
template <class V, class K, class HF, class ExK, class EqK, class A>
__hashtable_iterator<V, K, HF, ExK, EqK, A>&
__hashtable_iterator<V, K, HF, ExK, EqK, A>::operator++()
{
 const node* old = cur;
 cur = cur->next;
 if (!cur) {
 size_type bucket = ht->bkt_num(old->val);
 while (!cur && ++bucket < ht->buckets.size())
 cur = ht->buckets[bucket];
 }
 return *this;
}

2.2.iterator实现思路分析

  • 哈希表迭代器大体实现思路和list迭代器相近:封装结点指针,配合运算符重载模拟指针访问行为;哈希迭代器属于单向迭代器,仅支持自增++,不支持反向迭代
  • 核心难点在于operator++:迭代器内部持有结点指针。若当前桶内还有后继结点,直接走到同桶下一个结点;如果当前桶链表遍历完毕,则需要向后寻找下一个非空桶。 实现上关键设计:迭代器除结点指针外,额外保存底层哈希表的指针。当桶内链表走到末尾时,先算出当前桶下标,从当前位置向后遍历桶数组,找到第一个存在结点的桶。
  • begin():遍历桶数组,找到第一个非空桶,使用桶内首结点构造迭代器; end():统一使用空结点构造迭代器作为尾后标记。
  • unordered_set迭代器禁止修改元素:底层哈希表存储类型使用const K
cpp 复制代码
HashTable<K, const K, SetKeyOfT, Hash, Pred> _ht;
  • unordered_map迭代器规则:key 不可修改,value 允许修改。底层存储pair<const K, V>,天然限制 key,value 可读写
cpp 复制代码
HashTable<K, pair<const K, V>, MapKeyOfT, Hash, Pred> _ht;

完整可用迭代器还需要补齐大量边界细节,完整实现参考后续代码。

3.map支持\[\]

  • unordered_map想要实现[]运算符,核心前提是改造插入接口:将底层HashTable::Insert的返回值从bool修改为pair<Iterator, bool> Insert(const T& data)。 规则对齐标准库:
    • 若 key 不存在:插入新元素,返回{新元素迭代器, true}
    • 若 key 已存在:不插入,返回{已有元素迭代器, false}
  • 当 Insert 接口支持返回迭代器后,operator[]的实现逻辑就非常清晰。具体实现参考下方代码。

4.qiu::map和qiu::set实现

Hash Table.h

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

inline unsigned long __stl_next_prime(unsigned long n)
{
	// Note: assumes long is at least 32 bits.
	static const int __stl_num_primes = 28;
	static const unsigned long __stl_prime_list[__stl_num_primes] =
	{
		53, 97, 193, 389, 769,
		1543, 3079, 6151, 12289, 24593,
		49157, 98317, 196613, 393241, 786433,
		1572869, 3145739, 6291469, 12582917, 25165843,
		50331653, 100663319, 201326611, 402653189, 805306457,
		1610612741, 3221225473, 4294967291
	};
	const unsigned long* first = __stl_prime_list;
	const unsigned long* last = __stl_prime_list + __stl_num_primes;
	const unsigned long* pos = lower_bound(first, last, n);
	return pos == last ? *(last - 1) : *pos;
}

template<class T>
class HashNode
{
	using Node = HashNode<T>;
public:
	HashNode() = default;
	HashNode(const T& kv)
		:_data(kv)
	{

	}

	T _data;
	Node* _next = nullptr;
};

template<class K>
class Equal
{
public:
	bool operator()(const K& k1,const K& k2)
	{
		return k1 == k2;
	}
};

template<class K>
class HashKey
{
public:
	size_t operator()(const K& k)
	{
		return (size_t)k;
	}
};

template<>
class HashKey<string>
{
public:
	size_t operator()(const string& s)
	{
		size_t ret = 0;
		for (auto c : s)
		{
			ret = ret * 131 + c;
		}
		return ret;
	}
};

template<class K, class T, class KeyOfT, class Hash, class Pred>
class HashTable;

template<class K, class T, class KeyOfT, class Hash, class Pred,class Ptr,class Ref>
class HashIterator
{
	using Node = HashNode<T>;
	using hash = HashTable<K, T, KeyOfT, Hash, Pred>;
	using Self = HashIterator< K, T, KeyOfT, Hash, Pred, Ptr, Ref>;

public:

	HashIterator(Node* node,const hash* ht)
		:_node(node)
		, _ht(ht)
	{

	}

	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	bool operator!=(const Self& it)
	{
		return _node != it._node;
	}
	Self& operator++()
	{
		if (_node->_next)
		{
			_node = _node->_next;
		}
		else
		{
			Hash hs;
			KeyOfT kot;
			size_t hash0 = hs(kot(_node->_data)) % _ht->_tables.size();
			++hash0;
			while (hash0 < _ht->_tables.size())
			{
				if (_ht->_tables[hash0])
					break;
				++hash0;
			}
			if (hash0 == _ht->_tables.size())
				_node = nullptr;
			else
				_node = _ht->_tables[hash0];
		}
		return *this;
	}

	Self operator++(int)
	{
		Self s = *this;
		++(*this);
		return s;
	}

private:
	Node* _node = nullptr;
	const hash* _ht = nullptr;
};




template<class K, class T,class KeyOfT, class Hash, class Pred>
class HashTable
{
	using Node = HashNode<T>;

	template<class K, class T, class KeyOfT, class Hash, class Pred, class Ptr, class Ref>
	friend class HashIterator;
public:
	using Iterator = HashIterator<K, T, KeyOfT, Hash, Pred, T*, T&>;
	using ConstIterator = HashIterator<K, T, KeyOfT, Hash, Pred, const T*, const T&>;
	Iterator Begin()
	{
		if (_n == 0)
			return  End();
		size_t hashi = 0;
		while (_tables[hashi] == nullptr)
		{
			++hashi;
		}
		return Iterator(_tables[hashi], this);
	}
	Iterator End()
	{
		return Iterator(nullptr, this);
	}
	ConstIterator Begin() const
	{
		if (_n == 0)
			return End();
		size_t hashi = 0;
		while (_tables[hashi] == nullptr)
		{
			++hashi;
		}
		return ConstIterator(_tables[hashi], this);
	}
	ConstIterator End() const
	{
		return ConstIterator(nullptr, this);
	}

	HashTable()
		:_tables(__stl_next_prime(0))
		, _n(0)
	{

	}

	HashTable(const HashTable<K, T, KeyOfT, Hash, Pred>& ht)
		:_tables(__stl_next_prime(ht._tables.size()))
		, _n(0)
	{
		for (size_t i = 0; i < ht._tables.size(); i++)
		{
			Node* cur = ht._tables[i];
			while (cur)
			{
				Insert(cur->_data);
				cur = cur->_next;
			}
		}
	}

	HashTable<K, T, KeyOfT, Hash, Pred>& operator=(HashTable<K, T, KeyOfT, Hash, Pred> ht)
	{
		swap(ht);
		return *this;
	}

	~HashTable()
	{
		for (size_t i = 0; i < _tables.size(); i++)
		{
			Node* cur = _tables[i];
			while (cur)
			{
				Node* next = cur->_next;
				delete cur;
				cur = next;
			}
			_tables[i] = nullptr;
		}
	}

	void swap(HashTable<K, T, KeyOfT, Hash, Pred>& ht)
	{
		std::swap(this->_tables, ht._tables);
		std::swap(this->_n, ht._n);
	}

	pair<Iterator, bool> Insert(const T& kv)
	{
		KeyOfT kot;
		Hash hk;
		Iterator it = Find(kot(kv));
		if (it != End())
			return { it,false };
		if (_n == _tables.size())
		{
			vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
			for (int i = 0; i < _tables.size(); i++)
			{
				if (_tables[i])
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t hash0 = hk(kot(cur->_data)) % newtables.size();
						cur->_next = newtables[hash0];
						newtables[hash0] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
			}
			_tables.swap(newtables);
		}

		size_t hash0 = hk(kot(kv)) % _tables.size();
		Node* newnode = new Node(kv);
		newnode->_next = _tables[hash0];
		_tables[hash0] = newnode;

		++_n;
		return { Iterator(newnode,this),true };
	}
	bool Erase(const K& k)
	{
		Pred pd;
		Hash hk;
		size_t hash0 = hk(k) % _tables.size();
		Node* cur = _tables[hash0];
		Node* parent = nullptr;
		while (cur)
		{
			KeyOfT kot;
			if (pd(kot(cur->_data), k))
			{
				if (parent)
				{
					parent->_next = cur->_next;
				}
				else
				{
					_tables[hash0] = cur->_next;
				}
				delete cur;
				--_n;
				return true;
			}
			else
			{
				parent = cur;
				cur = cur->_next;
			}
		}

		return false;
	}
	Iterator Find(const K& k)
	{
		Pred pd;
		Hash hk;
		KeyOfT kot;
		size_t hash0 = hk(k) % _tables.size();
		Node* cur = _tables[hash0];
		while (cur)
		{
			if (pd(kot(cur->_data), k))
				return Iterator(cur, this);
			else
				cur = cur->_next;
		}


		return Iterator(nullptr, this);
	}



private:
	vector<Node*> _tables;
	size_t _n = 0;//数据个数

};

unordered_map.h

cpp 复制代码
#pragma once
#include "Hash Table.h"

namespace qiu
{
	template<class K,class V, class Hash = HashKey<K>, class Pred = Equal<K>>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};

		using ht = HashTable<K, pair<const K, V>, MapKeyOfT, Hash, Pred>;

	public:
		using iterator = typename HashTable<K, pair<const K, V>, MapKeyOfT, Hash, Pred>::Iterator;
		using const_iterator = typename HashTable<K, pair<const K, V>, MapKeyOfT, Hash, Pred>::ConstIterator;


		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<const K, V>& kv)
		{
			return _ht.Insert(kv);
		}
		V& operator[](const K& k)
		{
			pair<iterator, bool> ret = _ht.Insert({ k,V() });
			return ret.first->second;
		}

		bool erase(const K& k)
		{
			return _ht.Erase(k);
		}
		iterator find(const K& key)
		{
			return _ht.Find(key);
		}

	private:
		ht _ht;
	};

}

unordered_set.h

cpp 复制代码
#pragma once
#include "Hash Table.h"

namespace qiu
{
	template<class K,class Hash = HashKey<K>,class Pred = Equal<K>>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
		using ht = HashTable<K,const K, SetKeyOfT, Hash, Pred>;

	public:
		using iterator = typename HashTable<K,const K, SetKeyOfT, Hash, Pred>::Iterator;
		using const_iterator = typename HashTable<K,const K, SetKeyOfT, Hash, Pred>::ConstIterator;


		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 K& kv)
		{
			return _ht.Insert(kv);
		}
		bool erase(const K& k)
		{
			return _ht.Erase(k);
		}
		iterator find(const K& key)
		{
			return _ht.Find(key);
		}

	private:
		ht _ht;
	};
}

test.h

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include "unordered_map.h"
#include "unordered_set.h"
using namespace std;


int  main()
{
	qiu::unordered_map<int, int> mp;
	qiu::unordered_set<string> st;

	mp.insert({ 1,1 });
	mp[2] = 2;
	mp.insert({ 3,3 });
	mp[4];
	qiu::unordered_map<int, int> mp1;

	st.insert("1");
	st.insert("2");
	st.insert("33");
	st.insert("44");
	qiu::unordered_set<string> st1;

	mp1 = mp;
	st1 = st;
	mp1[4] = 4;
	for (auto x : mp)
		cout << x.first << "->" << x.second << " ";
	cout << endl;;
	for (auto x : mp1)
		cout << x.first << "->" << x.second << " ";
	cout << endl;;

	auto it = st.begin();
	
	while (it != st.end())
	{
		cout << *it << " ";
		++it;
	}

	return 0;
}

结语:

至此,我们从源码溯源、原理剖析、分步重构、迭代器实现、接口补齐,完整实现了 C++ unordered_setunordered_map 的底层哈希表模拟实现。 从硬编码耦合版本,到完全解耦、可拓展、贴近标准库的通用哈希表,层层递进吃透了哈希容器的全部核心考点。 掌握这套架构,不仅能彻底理解无序容器的底层机制,也能为后续学习自定义哈希、解决哈希冲突、实现多值哈希容器(multiset/multimap)打下扎实基础。

那么,C++ unordered_set和unordered_map底层实现部分的内容就全部讲解完毕啦,希望以上内容对你有所帮助,感谢观看,若觉得写的还可以,可以分享给朋友一起来看哦,毕竟一起进步更有动力嘛,当然能关注一下就更好啦。

相关推荐
鸿芯微控科技1 小时前
MFC设定流量达不到怎么办?上游供气能力、压差与管路阻力排查
c++·mfc·质量流量控制器·流量不足排查·压差测试·气体流量控制
小林ixn1 小时前
NestJS 入门实战:从 0 到 1 撸一个 Todo CRUD,感受装饰器与模块化的优雅
后端·mvc·nestjs
阿弱1 小时前
graph-core 的边与命令模式设计
java·后端·agent
智驭未来掌门人1 小时前
利用Qt设计实现一款桌面程序
后端
长栎1 小时前
你以为抽象工厂是「创建一组对象」——其实它是「锁定产品族兼容性」
后端
郝学胜_神的一滴1 小时前
C++20 高级编程 003:吃透现代基础语法与标准容器
c++
长栎1 小时前
你的 AI 品控规则越来越多了——但它们已经在打架了,你没看见
后端
萧瑟余晖1 小时前
Java深入解析篇三十四之分布式事务
java·开发语言·分布式
CAE虚拟与现实1 小时前
源码注解 /class 注解 / 运行时注解三者生命周期
java·开发语言