用哈希表封装出unordered_set/_map
前提:
①:本博客是对哈希表(开散列)进行封装,因为闭散列不优秀(与库保持一致)
②:哈希表封装出unordered_set/_map和红黑树封装出ste/map是大同小异的,可以先看下:用红黑树封装出set和map -CSDN博客
③:本博客基于手撕哈希表-CSDN博客的基础上讲解,所以哈希表的起始代码如下:
#pragma once
#include<iostream>
#include<vector>
using namespace std;template<class K>
//仿函数 用于得到kv对应的哈希值
//默认的是能直接转换成整形值的 比如int 地址 指针 这种
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};//特化
//string比较通用,所以特化
//采取DKBR
//使用循环遍历字符串中的每个字符e。
//将字符的ASCII值加到hash上,然后乘以一个常数131。
template<>
struct HashFunc<string>
{size_t operator()(const string& s){size_t hash = 0;for (auto e : s){hash += e;hash *= 131;}return hash;}
};//开散列(哈希桶)的实现
namespace hash_bucket
{//这里叫HashNode 因为值存在链表中哦~template<class K, class V>struct HashNode{HashNode<K, V>* _next;//只想下一个节点的指针pair<K, V> _kv;//存储的键值对//节点的构造HashNode(const pair<K, V>& kv):_next(nullptr)//next默认为空, _kv(kv){}};//哈希表类 依旧是一个仿函数用于得到kv对应的哈希值template<class K, class V, class Hash = HashFunc<K>>class HashTable{typedef HashNode<K, V> Node;public://哈希表的构造 默认开存储10个空指针的vector//相比于闭散列的构造 多了置nullptr这一步HashTable(){_tables.resize(10, nullptr);_n = 0;}//析构 用于释放vector下面挂着的链表 //外层for循环->遍历vector//内层while循环->遍历每一个桶中的节点~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;}}//插入bool Insert(const pair<K, V>& kv){//在插入之前,首先调用Find函数检查kv是否已经存在于哈希表中。//如果存在,则不需要插入,直接返回false。if (Find(kv.first))return false;//仿函数实例化Hash hs;// 负载因子到1就扩容if (_n == _tables.size()){//不再是开一个新哈希表对象了,而是一个新的2倍大小的vector//这样效率更高vector<Node*> newTables(_tables.size() * 2, nullptr);//依旧是外层for循环->遍历原来的vector//内层while循环->遍历每个桶内的节点for (size_t i = 0; i < _tables.size(); i++){// 取出旧表中节点,重新计算桶的位置挂到新表桶中Node* cur = _tables[i];while (cur){//保存下一个节点Node* next = cur->_next;// 头插到新表//计算新位置size_t hashi = hs(cur->_kv.first) % newTables.size();//让当前节点的 next 指向新表的桶头cur->_next = newTables[hashi];//把当前节点设为新表的桶头。newTables[hashi] = cur;cur = next;}//清空旧表的桶_tables[i] = nullptr;}//交换两个vector_tables.swap(newTables);}//走到这里代表已经扩容完毕 或者不需要扩容//直接仿函数对象得到哈希值hashisize_t hashi = hs(kv.first) % _tables.size();Node* newnode = new Node(kv);//然后头插即可 切记vector里面放的是节点指针 指向一个桶的首节点newnode->_next = _tables[hashi];_tables[hashi] = newnode;//插入n记得++++_n;return true;}//查找函数//找到返回该节点的指针 反之nullptrNode* Find(const K& key){Hash hs;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (cur->_kv.first == key){return cur;}cur = cur->_next;}return nullptr;}//删除函数//不能像闭散列那样find+delete 因为我们单向链表删除后还要链接删除节点的前后节点//易错:得看前驱是否为空 即需要以防删除的就是首节点bool Erase(const K& key){Hash hs;size_t hashi = hs(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (cur->_kv.first == key){// 情况1:删除的是中间或尾节点if (prev)//前驱不为空 {prev->_next = cur->_next;// 前驱节点直接跳过当前节点}//情况2:删除的是头节点else//前驱为空{_tables[hashi] = cur->_next;// 让桶头指向下一个节点}//释放delete cur;//n-1--_n;return true;}//没找到则继续遍历prev = cur;cur = cur->_next;}//遍历结束仍未找到 return false;}//测试我们写的哈希桶 测试内容如下/*负载因子(load factor)总桶数量(all bucketSize)非空桶数量(bucketSize)最长链表长度(maxBucketLen)平均链表长度(averageBucketLen)*/void Some(){size_t bucketSize = 0;size_t maxBucketLen = 0;size_t sum = 0;double averageBucketLen = 0;for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){++bucketSize;}size_t bucketLen = 0;while (cur){++bucketLen;cur = cur->_next;}sum += bucketLen;if (bucketLen > maxBucketLen){maxBucketLen = bucketLen;}}averageBucketLen = (double)sum / (double)bucketSize;printf("load factor:%lf\n", (double)_n / _tables.size());printf("all bucketSize:%d\n", _tables.size());printf("bucketSize:%d\n", bucketSize);printf("maxBucketLen:%d\n", maxBucketLen);printf("averageBucketLen:%lf\n\n", averageBucketLen);}private://与闭散列不同的是 vector里面存储的是节点指针了vector<Node*> _tables; // 指针数组size_t _n;};
}
引入:想要完成封装,我们的哈希表有什么不足?
①:哈希表结构的优化
我们哈希表目前只能存储kv模型;所以结构需要优化,以便既能够成为K模型,又要能成为KV模型
以及我们要做到不论是k模型还是kv模型,都要取得到k值
②:两个仿函数的嵌套适用
我们要先通过仿函数得到k模型或者kv模型中的k值,然后再通过仿函数(哈希函数)得到k值对应的一个整形值;这是和红黑树封装的不同,红黑树只需要得到k值即可。
③:迭代器的实现
④:unordered_map的[ ]的实现
一:优化哈希表的结构
A:要想即能创建出k模型的unordered_set和kv模型的unordered_map:
和红黑树类似,哈希表也是从第二个模板参数进行操作,就能达到效果
B:要想无论是k模型还是kv模型,都要取得到k值
从仿函数入手即可
相关细节不再赘述,红黑树封装博客里面有类似的详细讲解:用红黑树封装出set和map -CSDN博客
直接看代码:
unordered_set代码:
#include"HashTable.h"template<class K, class Hash = HashFunc<K>>//外界调用所需参数class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:private://创建一个哈希表对象hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;};
unordered_map代码:
#include"HashTable.h"template<class K, class V, class Hash = HashFunc<K>>//外界调用所需参数class unordered_map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};private://创建一个哈希表对象hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;};
解释:对哈希表的第四个参数的解释
①: 创建哈希表中的第四个参数Hash,是实现在HashTable.h中的,且unordered_set和unordered_map文件中在包含了HashTable.h后,都是在模版中的Hash给上缺省值的,所以我们HashTable.h中,哈希表的模版中的Hash参数不需要再给缺省值
②:为什么在unordered_set/map中处理这个参数? 因为,使用者不可能直接用哈希表而是用unordered_set或unordered_map,所以一般是不需要对unordered_set/map参数中的哈希函数进行传参的,只有k值是特殊类型的时候,才需要自己实现哈希函数进行手动传参
所以哈希表的起始状态如下:
//哈希函数 以及模版参数为string的特化处理
template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};//特化
template<>
struct HashFunc<string>
{size_t operator()(const string& s){size_t hash = 0;for (auto e : s){hash += e;hash *= 131;}return hash;}
};namespace hash_bucket
{// T -> K// T -> pair<K, V>//哈希节点类 哈希桶中都是节点 所以叫节点类template<class T>struct HashNode{HashNode<T>* _next;T _data;HashNode(const T& data):_next(nullptr), _data(data){}};//哈希表类template<class K, class T, class KeyOfT, class Hash>//Hash不需要缺省值class HashTable{//节点类的缩写 typedef HashNode<T> Node;public:private:vector<Node*> _tables; // 指针数组size_t _n;};
}
到这里,我们的哈希表既能创建出k模型的unordered_set和kv模型的unordered_map;也能无论是k模型还是kv模型,都要取得到k值;通过两个仿函数就可以办到~
二:两个仿函数的嵌套使用
我们的函数,也要进行改变,不再是之前的写死了对pair类型进行操作,现在获取到的是T类型的data值,我们要先对data进行取出其中的k,然后再用仿函数让k得到一个整形
所以插入 查找 删除函数改动如下:
//插入函数pair<iterator, bool> Insert(const T& data){KeyOfT kot;iterator it = Find(kot(data));if (it != end())return make_pair(it, false);//仿函数对象的实例化Hash hs;// 负载因子到1就扩容if (_n == _tables.size()){vector<Node*> newTables(GetNextPrime(_tables.size()), nullptr);for (size_t i = 0; i < _tables.size(); i++){// 取出旧表中节点,重新计算挂到新表桶中Node* cur = _tables[i];while (cur){Node* next = cur->_next;// 头插到新表size_t hashi = hs(kot(cur->_data)) % newTables.size();cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newTables);}size_t hashi = hs(kot(data)) % _tables.size();Node* newnode = new Node(data);// 头插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return make_pair(iterator(newnode, this), true);}//查找函数iterator Find(const K& key){KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur, this);}cur = cur->_next;}return iterator(nullptr, this);}//删除函数bool Erase(const K& key){KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){// 删除if (prev){prev->_next = cur->_next;}else{_tables[hashi] = cur->_next;}delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;}
解释:
①:仿函数的实例化
//取出data里面的k值
KeyOfT kot; //将k值转换为一个整形值
Hash hs;
②:仿函数嵌套适用:
hs(kot(data))
这就叫仿函数的嵌套使用,先拿出data里面的k值,然后得到k值对应的整形值
③:insert函数的返回值,和红黑树一样,是为了unordered_map的[ ]所准备的
④:扩容中用到了一个GetNextPrime函数,先别管,后面会讲
三:迭代器的实现
哈希表的迭代器的实现是一个重点,和以往的任何的迭代器实现都不同,以前的实现,要么是直接复用的指针(如底层连续的vector),要么就是对一个节点指针进行封装,去重载++ * != 等操作符;
哈希表的迭代器的问题:
Q:你对一个节点指针进行了封装,虽然可以* ++ !=....重载,但是一个桶走完了,怎么找到下一个桶?
A:所以哈希表的迭代器不再只对节点指针封装,且还要对哈希表指针封装,也就是有两个成员变量
注意:迭代器博主只实现了正向非const迭代器,其余的大同小异,不再赘述
1:迭代器代码:
//哈希桶的迭代器类template<class K, class T, class KeyOfT, class Hash>struct __HTIterator{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef __HTIterator<K, T, KeyOfT, Hash> Self;//两个成员变量Node* _node;HT* _ht;//迭代器的构造__HTIterator(Node* node, HT* ht):_node(node), _ht(ht){}//*重载T& operator*(){return _node->_data;}//->的重载T* operator->(){return &_node->_data;}//++的重载Self& operator++(){if (_node->_next){// 当前桶还是节点_node = _node->_next;}else{// 当前桶走完了,找下一个桶KeyOfT kot;Hash hs;size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();// 找下一个桶hashi++;while (hashi < _ht->_tables.size()){if (_ht->_tables[hashi]){_node = _ht->_tables[hashi];break;}hashi++;}// 后面没有桶了if (hashi == _ht->_tables.size()){_node = nullptr;}}return *this;}//!=的重载bool operator!=(const Self& s){return _node != s._node;}};
2:迭代器代码解释:
①:成员变量
Node* _node; // 当前指向的哈希节点
HT* _ht; // 指向所属的哈希表
②:关键类型定义
typedef HashNode<T> Node;
typedef HashTable<K, T, KeyOfT, Hash> HT;
typedef __HTIterator<K, T, KeyOfT, Hash> Self;
Self:迭代器自身的类型,简化返回值类型声明(如 operator++ 返回 Self&)。
③:迭代器的核心操作
a:解引用操作 operator*
T* operator->()
{return &_node->_data; // 返回节点数据的指针
}
b:成员访问操作 operator->
T* operator->()
{return &_node->_data; // 返回节点数据的指针
}
c: 不等比较 operator!=
bool operator!=(const Self& s)
{return _node != s._node; // 比较两个迭代器是否指向同一节点
}
d:最关键的 operator++
(跨桶遍历)
Self& operator++()
{//情况1:当前桶还有下一个节点if (_node->_next) {_node = _node->_next;} //来到这里 代表是情况2//情况2:当前桶已遍历完,需跳转到下一个非空桶else {//所以需要先计算当前节点所在的桶索引 hashi!KeyOfT kot;Hash hs;//hashi即当前节点所在的桶索引size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();hashi++; // 从下一个桶开始找//hashi得一直++,直到找到一个不为空的桶(为空代表vector此槽位是nullptr)while (hashi < _ht->_tables.size()) {if (_ht->_tables[hashi]) // 找到非空桶{_node = _ht->_tables[hashi]; // 指向该桶的首节点break;}hashi++;}//hashi一直++ 直到vector遍历完了 都没发现非空桶 //则将迭代器置为end()(因为end()就是nullptr)if (hashi == _ht->_tables.size()) {_node = nullptr;}}return *this;
}
解释:代码逻辑转换为图片如下
四:map的[ ]的实现
在unordered_map中进行以下实现即可,和红黑树类似,不再赘述:
//[ ]函数V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}
五:总代码
①:HashTable.h
#pragma once
#include<iostream>
#include<vector>
#include<unordered_set>
#include<unordered_map>
#include<set>
using namespace std;//哈希函数 以及模版参数为string的特化处理
template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};//特化
template<>
struct HashFunc<string>
{size_t operator()(const string& s){size_t hash = 0;for (auto e : s){hash += e;hash *= 131;}return hash;}
};//开散列的域
//开散列 也叫哈希桶
namespace hash_bucket
{// T -> K// T -> pair<K, V>//哈希节点类 哈希桶中都是节点 所以叫节点类template<class T>struct HashNode{HashNode<T>* _next;T _data;HashNode(const T& data):_next(nullptr), _data(data){}};// 前置声明 template<class K, class T, class KeyOfT, class Hash >class HashTable;//哈希桶的迭代器类template<class K, class T, class KeyOfT, class Hash>struct __HTIterator{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef __HTIterator<K, T, KeyOfT, Hash> Self;//两个成员变量Node* _node;HT* _ht;//迭代器的构造__HTIterator(Node* node, HT* ht):_node(node), _ht(ht){}//*重载T& operator*(){return _node->_data;}//->的重载T* operator->(){return &_node->_data;}//++的重载Self& operator++(){if (_node->_next){// 当前桶还剩节点_node = _node->_next;}else{// 当前桶走完了,找下一个桶KeyOfT kot;Hash hs;size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();// 找下一个桶hashi++;while (hashi < _ht->_tables.size()){if (_ht->_tables[hashi]){_node = _ht->_tables[hashi];break;}hashi++;}// 后面没有桶了if (hashi == _ht->_tables.size()){_node = nullptr;}}return *this;}//!=的重载bool operator!=(const Self& s){return _node != s._node;}};//哈希表类template<class K, class T, class KeyOfT, class Hash>// KeyOfT用于取出T中的k值 Hash则是得到这个取出的k值对应的整形class HashTable{template<class K, class T, class KeyOfT, class Hash>friend struct __HTIterator;//节点类的缩写 typedef HashNode<T> Node;public:typedef __HTIterator<K, T, KeyOfT, Hash> iterator;//迭代器的两个函数//begin()函数iterator begin(){for (size_t i = 0; i < _tables.size(); i++){// 找到第一个桶的第一个节点if (_tables[i]){return iterator(_tables[i], this);}}return end();}//end()函数iterator end(){return iterator(nullptr, this);}//构造函数HashTable(){_tables.resize(10, nullptr);_n = 0;}//析构函数~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;}}//获取本次增容后哈希表的大小 空间呈素数增长size_t GetNextPrime(size_t prime){const int PRIMECOUNT = 28;//素数序列const size_t primeList[PRIMECOUNT] ={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};size_t i = 0;for (i = 0; i < PRIMECOUNT; i++){if (primeList[i] > prime)return primeList[i];}return primeList[i];//逻辑返回}//插入函数pair<iterator, bool> Insert(const T& data){KeyOfT kot;iterator it = Find(kot(data));if (it != end())return make_pair(it, false);//仿函数对象的实例化Hash hs;// 负载因子到1就扩容if (_n == _tables.size()){vector<Node*> newTables(GetNextPrime(_tables.size()), nullptr);for (size_t i = 0; i < _tables.size(); i++){// 取出旧表中节点,重新计算挂到新表桶中Node* cur = _tables[i];while (cur){Node* next = cur->_next;// 头插到新表size_t hashi = hs(kot(cur->_data)) % newTables.size();cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newTables);}size_t hashi = hs(kot(data)) % _tables.size();Node* newnode = new Node(data);// 头插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return make_pair(iterator(newnode, this), true);}//查找函数iterator Find(const K& key){KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur, this);}cur = cur->_next;}return iterator(nullptr, this);}//删除函数bool Erase(const K& key){KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){// 删除if (prev){prev->_next = cur->_next;}else{_tables[hashi] = cur->_next;}delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;}private:vector<Node*> _tables; // 指针数组size_t _n;};
}
②:My_unordered_set
#pragma once
#include"HashTable.h"namespace bit
{template<class K, class Hash = HashFunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public://复用哈希表类的迭代器typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::iterator iterator;//复用哈希表的begin函数iterator begin(){return _ht.begin();}//复用哈希表的end函数iterator end(){return _ht.end();}//复用哈希表的插入函数pair<iterator, bool> insert(const K& key){return _ht.Insert(key);}//复用哈希表的查找函数iterator Find(const K& key){return _ht.Find(key);}//复用哈希表的删除函数bool erase(const K& key){return _ht.Eease(key);}private://创建一个哈希表对象hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;};//测试set 体现迭代器遍历出来是无序的 且set的k值不能被修改void test_set1(){unordered_set<int> us;us.insert(3);us.insert(1);us.insert(5);us.insert(15);us.insert(45);us.insert(7);unordered_set<int>::iterator it = us.begin();while (it != us.end()){//*it += 100;cout << *it << " ";++it;}cout << endl;for (auto e : us){cout << e << " ";}cout << endl;}}
③:My_unordered_map
#pragma once
#include"HashTable.h"namespace bit
{template<class K, class V, class Hash = HashFunc<K>>class unordered_map{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, Hash>::iterator iterator;//复用哈希表的begin()函数iterator begin(){return _ht.begin();}//复用哈希表的end()函数iterator end(){return _ht.end();}//复用哈希表的插入函数pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.Insert(kv);}//复用哈希表的查找函数iterator Find(const K& key){return _ht.Find(key);}//复用哈希表的删除函数bool erase(const K& key){return _ht.Eease(key);}//[ ]函数V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}private://创建一个哈希表对象hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;};//测试map 体现迭代器遍历出来是无序的 且map的k值不能被修改 v值可以修改void test_map1(){unordered_map<string, string> dict;dict.insert(make_pair("sort", "排序"));dict.insert(make_pair("left", "左面"));dict.insert(make_pair("right", "右面"));for (auto& kv : dict){//kv.first += 'x';kv.second += 'y';cout << kv.first << ":" << kv.second << endl;}}//测试map的[]void test_map2(){string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "西瓜", "香蕉", "草莓" };unordered_map<string, int> countMap;for (auto& e : arr){countMap[e]++;}for (auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}cout << endl;}}
④:test.cpp
#define _CRT_SECURE_NO_WARNINGS 1#include"MyOrderedMap.h"
#include"MyOrderedSet.h"int main()
{//测试封装后的OrderedSet和OrderedMap 体现无序性bit::test_set1();bit::test_map1();//测试OrderedMap的[]bit::test_map2();return 0;
}
⑤:代码整合
当多份独立可运行的代码合并到一个项目中时,需要解决一些问题:
a:创建哈希表时候的第二个参数的写法
//My_unordered_set中:
const K//My_unordered_map中:
pair<const K, V>
解释:这样写,对于My_unordered_set,k不能改;对于My_unordered_map ,k不能改,v可以改
和库保持一致,符合这种数据结构的特性
b:在迭代器之前要前置声明一下哈希表类,注意写法:
// 前置声明 template<class K, class T, class KeyOfT, class Hash >class HashTable;
解释:
Q:为什么在 迭代器之前要前置声明哈希表类?
A:因为迭代器类中又需要用到 HashTable
的类型(如 typedef HashTable<K, T, KeyOfT, Hash> HT;
);这样迭代器类才能安全地引用 HashTable
,而不会报错“未定义的类型”。
c:哈希表类中要声明迭代器类为友元:
template<class K, class T, class KeyOfT, class Hash>friend struct __HTIterator;
Q: 为什么要在哈希表类中要声明迭代器类为友元?
A:因为迭代器需要访问 HashTable
的 私有成员(如 _tables
和 _n
),但默认情况下,外部类(包括迭代器)无法访问私有成员;
例如,operator++
中需要计算哈希桶位置:
size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size(); // 访问 _tables
如果 _tables
是 private
,且迭代器不是友元,这段代码会编译失败。
Q:为什么不能直接把 _tables
改成 public
?
A:
-
破坏封装性,外部任何代码都可以随意修改哈希表内部结构,不安全。
-
友元是一种 受控的暴露,只允许特定的类(如迭代器)访问私有成员
d:GetNextPrime函数的作用
GetNextPrime
是哈希表扩容时用于 计算新容量 的函数,其核心目的是 返回一个比当前容量大的素数,作为哈希表的新桶数组大小。这是为了解决哈希表扩容时的效率问题,并减少哈希冲突。
因为,经过前辈研究,素数扩容是最好的,会有效的减少哈希冲突!
六:测试代码
①:测试My_unordered_set
void test_set1(){unordered_set<int> us;us.insert(3);us.insert(1);us.insert(5);us.insert(15);us.insert(45);us.insert(7);unordered_set<int>::iterator it = us.begin();while (it != us.end()){//*it += 100;cout << *it << " ";++it;}cout << endl;for (auto e : us){cout << e << " ";}cout << endl;}
运行结果:
解释:迭代器和范围for均正常,且符合无序的预期
②:测试My_unordered_map
void test_map1(){unordered_map<string, string> dict;dict.insert(make_pair("sort", "排序"));dict.insert(make_pair("left", "左面"));dict.insert(make_pair("right", "右面"));for (auto& kv : dict){//kv.first += 'x';kv.second += 'y';cout << kv.first << ":" << kv.second << endl;}}
运行结果:
解释:符合无序的预期,且k值不能改变,但可以对v值进行了改变,正确
void test_map2(){string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "西瓜", "香蕉", "草莓" };unordered_map<string, int> countMap;for (auto& e : arr){countMap[e]++;}for (auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}cout << endl;}
运行结果:
解释:符合预期