部队网站设计,wordpress 免费cdn,如何做亚马逊备案的网站,经营网站建设#x1f525; 个人主页#xff1a;大耳朵土土垚 #x1f525; 所属专栏#xff1a;C从入门至进阶 这里将会不定期更新有关C/C的内容#xff0c;欢迎大家点赞#xff0c;收藏#xff0c;评论#x1f973;#x1f973;#x1f389;#x1f389;#x1f389; 文章目录… 个人主页大耳朵土土垚 所属专栏C从入门至进阶 这里将会不定期更新有关C/C的内容欢迎大家点赞收藏评论 文章目录 前言1. unordered_set和unordered_map介绍✨unordered_map介绍✨unordered_set介绍 2. 修改哈希表3. 迭代器✨const迭代器 4. unordered_map的[]访问5. unordered_set和unordered_map封装✨修改后的哈希表✨unordered_map类✨unordered_set类 6. 结语 前言 前面我们学习过红黑树实现map、set的封装而unordered_set和unordered_map的功能与map和set类似所不同的是其存储元素是无序的底层是使用哈希表所以今天我们就可以利用之前学习过的哈希表的实现来对CSTL库中的unordered_set和unordered_map进行模拟实现。 1. unordered_set和unordered_map介绍 在C98中STL提供了底层为红黑树结构的一系列关联式容器例如map、set。在查询时效率可达到 l o g 2 N log_2 N log2N即最差情况下需要比较红黑树的高度次当树中的节点非常多时查询效率也不理想。最好的查询是进行很少的比较次数就能够将元素找到因此在C11中STL又提供了4个unordered系列的关联式容器这四个容器与红黑树结构的关联式容器使用方式基本类似只是其底层结构不同。
✨unordered_map介绍 介绍文档点击跳转
unordered_map是存储key, value键值对的关联式容器其允许通过key快速的索引到与其对应的value。在unordered_map中键值通常用于惟一地标识元素而映射值是一个对象其内容与此键关联。键和映射值的类型可能不同。在内部,unordered_map没有对kye, value按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的valueunordered_map将相同哈希值的键值对放在相同的桶中。unordered_map容器通过key访问单个元素要比map快但它通常在遍历元素子集的范围迭代方面效率较低。unordered_maps实现了直接访问操作符(operator[])它允许使用key作为参数直接访问value。
unordered_map和map核心功能重复90%它们区别在于
对键值对中key要求不同
mapkey要支持比较大小unordered_mapkey要支持转换成整型比较相等
map遍历有序unordered_map遍历无序map有双向迭代器unordered_map单向迭代器它们之间性能有差异
unordered_map常见接口
函数声明功能介绍unordered_map()构造不同格式的unordered_map对象bool empty() const检测unordered_map是否为空size_t size() const获取unordered_map的有效元素个数begin返回unordered_map第一个元素的迭代器end返回unordered_map最后一个元素下一个位置的迭代器cbegin返回unordered_map第一个元素的const迭代器cend返回unordered_map最后一个元素下一个位置的const迭代器operator[]返回与key对应的valueiterator find(const K key)返回key在哈希桶中的位置size_t count(const K key)返回哈希桶中关键码为key的键值对的个数insert向容器中插入键值对erase删除容器中的键值对void clear()清空容器中有效元素个数void swap(unordered_map)交换两个容器中的元素size_t bucket_count()const返回哈希桶中桶的总个数size_t bucket_size(size_t n)const返回n号桶中有效元素的总个数size_t bucket(const K key)返回元素key所在的桶号
注意operator[]函数中实际调用哈希桶的插入操作用参数key与V()构造一个默认值往底层哈希桶中插入如果key不在哈希桶中插入成功返回V()插入失败说明key已经在哈希桶中将key对应的value返回。这与之前的map类似插入函数返回一个键值对键存放指针对存放bool值用来判断是否插入成功。 ✨unordered_set介绍 文档介绍点击跳转 unordered_set与unordered_map类似不同在于前者储存单个数据后者储存键值对这里就不过多介绍。 2. 修改哈希表 因为我们要使用哈希表来实现对unordered_set和unordered_map的封装之前实现的哈希表都是插入键值对是没办法很好封装unordered_set的所以我们先得对哈希表进行改造改造类似于使用红黑树封装map和set对红黑树的改造具体实现如下 我们之前模拟实现过哈希表插入的节点是键值对pair类型而如果要使用哈希表来对unordered_set和unordered_map封装的话unordered_set存储的应该是单个值而不是键值对所以我们就需要对哈希表进行修改使得unordered_set和unordered_map都能适用 首先哈希表存储节点的类需要从只能存储键值对改为能够存储任意数据
修改前
templateclass K,class V
struct HashNode
{pairK, V _kv;HashNodeK, V* _next;HashNode(const pairK, V kv):_kv(kv), _next(nullptr){}
};修改后
templateclass T
struct HashNode
{T _data;HashNodeT* _next;HashNode(const T data):_data(data), _next(nullptr){}
};
相应的哈希表的模板参数也需要修改
修改前
//哈希表类
templateclass K,class V, class Hash HashFuncK
class HashTable {
public:typedef HashNodeK,V Node;Node* Find(const K key);//查找函数private:vectorNode* _tables;size_t _n;//记录存储数据个数
};因为节点类只有一个模板参数了所以哈希表前面两个模板参数有点多余但是如果将哈希表的模板参数也改为一个如下面代码所示
//哈希表
templateclass T,class Hash HashFuncT
class HashTable
{
public:typedef HashNodeT Node;Node* Find(const T key);//查找函数private:vectorNode* _tables;size_t _n;//记录存储数据个数
}; 那么对于class Hash这个模板参数不可能也传入一个键值对此外在实现查找上面代码中的Find函数时对于unordered_map查找时Find函数参数就得传一个完整的键值对我们不可能把完整的键值对全部传过去查找这样查找就没有意义我们只需要获得键值对的键查找到相应的键值对即可所以我们还应该传一个模板参数用于传给class Hash、Find和Erase函数
//哈希表
templateclass K,class T,class Hash HashFuncK//K存储键T存储键值对
class HashTable
{
public:typedef HashNodeT Node;//传入键值对Node* Find(const K key);//查找函数只传键pairNode*,bool Insert(const T data);//插入传入Tbool Erase(const K key);//删除只传键
private:vectorNode* _tables;size_t _n;//记录存储数据个数
};这样对于不同函数的需求就可以传入不同的模板参数了 如果是unordered_map存储的是键值对我们就可以往哈希表的两个模板参数中传入一个键和一个键值对
//unordered_map类
templateclass K,class V
class unordered_map {
public:
private:HashTableK, pairK,V _ht;
};这样哈希表的第一个模板参数帮助我们解决仿函数传参、查找和删除函数传参的问题第二个则是必须的除了存储数据外插入等函数也需要第二个模板参数 如果是unordered_set存储的不是键值对我们也可以复用上面的代码传入两个一样的参数即可
//unordered_set类
templateclass K
class unordered_set {
public:
private:HashTableK, K _ht;
};虽然unordered_set看起来传了两个一模一样的参数是无意义的但是这样就可以实现对哈希表的复用不用单独为了unordered_set再写一个哈希表了如下图所示 插入函数的参数也得从键值对改为任意数据
bool Insert(const T data)//之前bool Insert(const pairK, V data)除此以外我们在寻找插入的位置时不可避免需要通过节点中存储的_data使用哈希函数来获取插入的位置
//通过Hash函数找到插入位置
Hash hs;
size_t addr hs(data.first) % _tables.size();我们发现之前插入键值对都是使用键值对的键来传给哈希函数获取哈希值当我们将哈希表改成可以存储任意数据后就不支持上述获取哈希值的方式了。 因此为了实现代码的复用我们需要传入一个新的模板参数以便在不同情况下都能按照我们需要的方式进行获取哈希值因为如果是unordered_map需要通过键来获取unordered_set则直接通过数据进行获取所以我们可以在set类和map类中各定义一个类来获取传给哈希函数的数据再传给哈希表
// unordered_map类
templateclass K,class V
class unordered_map {
//定义一个类获取键值对里面的键
struct MapKeyOfT {const K operator()(const pairK, V data){return data.first;}
};private:HashTableK, pairK,V, MapKeyOfT _ht;//传给哈希表};//unordered_set类
templateclass K
class unordered_set{
//也定义一个类返回data即可虽然有点多此一举但是可以实现代码复用
struct SetKeyOfT {const K operator()(const K data){return data;}
};private:HashTableK, K, SetKeyOfT _ht;//传给哈希表
};看起来unordered_set定义的类似乎做了无用功但是这一切都是为了能够实现哈希表的复用unordered_map多传了一个参数unordered_set也要多传。 那么哈希表的模板参数也要修改一下
templateclass K, class T, class KeyOfT, class Hash HashFuncK
class HashTable {
public:typedef HashNodeT Node;private:vectorNode* _tables;size_t _n;//记录存储数据个数
};这样我们在插入函数进行比较时就可以通过类模板KeyOfT定义一个对象然后使用括号来获取需要进行比较的数了
bool Insert(const T data)
{KeyOfT kot;//使用类模板定义一个对象Hash hs;//1.先找是否已经插入过相同的值if (Find(kot(data)))return false;//2.判断是否需要扩容...//3.通过Hash函数找到插入位置size_t addr hs(kot(data)) % _tables.size();//...}这样就可以使用类模板定义的对象通过重载括号获取你想要的值如果插入类型是键值对那么就获得键值对中的键如果不是键值对括号返回的也是数据本身这样不同的数据都可以传给哈希函数 3. 迭代器 哈希表的迭代器与链表的迭代器类似都是使用指针来构造
//迭代器
templateclass T
struct HashTableIterator {typedef HashNodeT Node;typedef HashTableIteratorT self;T operator*(){return _node-_data;}T* operator-(){return (_node-_data);}bool operator!(const self t){return _node ! t._node;}bool operator(const self t){return _node t._node;}self operator(){//...}//构造HashTableIterator(Node* node):_node(node){}Node* _node;};✨对于operator 因为哈希表中是使用哈希桶当一个桶遍历完就需要寻找下一个桶所以我们得将哈希表传过去来寻找下一个桶
self operator()
{if (_node-_next)_node _node-_next;//如果当前桶走完了找下一个桶//1.先找到当前位置KeyOfT kot;Hash hs;size_t hash hs(kot(_node-_data)) % _pht-_tables.size();hash;while (hash _pht-_tables.size()){if (_pht-_tables[hash])break;hash;}if (hash _pht-_tables.size())_node nullptr;else_node _pht-_tables[hash];return *this;
}可以看出上述代码使用了KeyOfT和Hash类分别获得传给哈希函数的值和哈希函数计算的哈希值所以迭代器模板也要传入这两个参数
templateclass K, class T, class KeyOfT, class Hash
truct HashTableIterator {typedef HashNodeT Node;typedef HashTableIteratorK,T, KeyOfT, Hash self;typedef HashTableK, T, KeyOfT, Hash HashTable;//...};又因为获取哈希表需要传入四个参数所以迭代器的参数也得包括这四个所以除了KeyOfT和Hash还要传入K这个参数 然后在迭代器中除了需要定义一个指针外还需要一个哈希表的指针以便将哈希表传过去
templateclass K, class T, class KeyOfT, class Hash
truct HashTableIterator {typedef HashNodeT Node;typedef HashTableIteratorK,T, KeyOfT, Hash self;typedef HashTableK, T, KeyOfT, Hash HashTable;//构造HashTableIterator(Node* node, HashTable* pht):_node(node),_pht(pht){}Node* _node;HashTable* _pht;//传入哈希表指针
};
完整的普通迭代器类代码如下
// 前置声明因为迭代器类中要使用哈希表所以需要前置声明
templateclass K, class T, class KeyOfT, class Hash
class HashTable;//迭代器
templateclass K, class T, class KeyOfT, class Hash
struct HashTableIterator {typedef HashNodeT Node;typedef HashTableIteratorK,T, KeyOfT, Hash self;typedef HashTableK, T, KeyOfT, Hash HashTable;T operator*(){return _node-_data;}T* operator-(){return (_node-_data);}bool operator!(const self t){return _node ! t._node;}bool operator(const self t){return _node t._node;}self operator(){if (_node-_next)_node _node-_next;//如果当前桶走完了找下一个桶//1.先找到当前位置KeyOfT kot;Hash hs;size_t hash hs(kot(_node-_data)) % _pht-_tables.size();hash;while (hash _pht-_tables.size()){if (_pht-_tables[hash])break;hash;}if (hash _pht-_tables.size())_node nullptr;else_node _pht-_tables[hash];return *this;}//构造HashTableIterator(Node* node, HashTable* pht):_node(node),_pht(pht){}Node* _node;HashTable* _pht;}; ✨const迭代器 相较于普通迭代器const迭代器指向的内容不可以被修改也就是说operator*和operator-返回值不可以修改所以只要在其返回值前加const修饰即可为了与普通迭代器复用同一个迭代器类我们需要在迭代器类的模板参数中多加两个
// 前置声明
templateclass K, class T,class KeyOfT, class Hash
class HashTable;//迭代器
templateclass K, class T, class Ref, class Ptr,class KeyOfT, class Hash
struct HashTableIterator {typedef HashNodeT Node;typedef HashTableIteratorK,T,Ref, Ptr, KeyOfT, Hash self;typedef const HashTableK, T, KeyOfT, Hash HashTable;//注意这里是constRef operator*(){return _node-_data;}Ptr operator-(){return (_node-_data);}bool operator!(const self t){return _node ! t._node;}bool operator(const self t){return _node t._node;}self operator(){if (_node-_next)_node _node-_next;//如果当前桶走完了找下一个桶//1.先找到当前位置KeyOfT kot;Hash hs;size_t hash hs(kot(_node-_data)) % _pht-_tables.size();hash;while (hash _pht-_tables.size()){if (_pht-_tables[hash])break;hash;}if (hash _pht-_tables.size())_node nullptr;else_node _pht-_tables[hash];return *this;}//构造HashTableIterator(Node* node, HashTable* pht):_node(node),_pht(pht){}Node* _node;HashTable* _pht;};这样在哈希表中如果是普通迭代器就传TTT*这三个模板参数如果是const迭代器就传Tconst Tconst T*这三个模板参数这样就很好限制了const迭代器修改指向的内容
templateclass K, class T, class KeyOfT, class Hash HashFuncK
class HashTable {
public:// 友元声明templateclass K, class T, class Ref, class Ptr,class KeyOfT, class Hashfriend struct HashTableIterator;typedef HashNodeT Node;typedef HashTableIteratorK, T, T, T*, KeyOfT, Hash Iterator;typedef HashTableIteratorK, T, const T, const T*, KeyOfT, Hash Const_Iterator;Iterator Begin(){if (_n 0)return End();int i 0;while (i _tables.size()){if (_tables[i] ! nullptr)break;i;}return Iterator(_tables[i],this);}Iterator End(){return Iterator(nullptr,this);}Const_Iterator Begin() const{if (_n 0)return End();int i 0;while (i _tables.size()){if (_tables[i] ! nullptr)break;i;}return Const_Iterator(_tables[i], this);}Const_Iterator End() const{return Const_Iterator(nullptr, this);}//...
private:vectorNode* _tables;size_t _n;//记录存储数据个数
}; 因为在迭代器类中要使用哈希表中的数据所以我们将迭代器设置为哈希表类的友元类 有了迭代器之后Find查找函数的返回值就可以使用迭代器了:
// 检测哈希表中是否存在值为key的节点存在返回该节点的迭代器否则返回End()
Iterator Find(const K key)
{//先找到key对应的Hash值KeyOfT kot;Hash hs;size_t ht hs(key) % _tables.size();Node* cur _tables[ht];while (cur){if (kot(cur-_data) key)return Iterator(cur,this);cur cur-_next;}return End();
}这里同样要注意查找函数需要通过特定的值获取哈希值所以我们可以利用之前在插入函数中使用的类模板继续创建一个对象来获取哈希值。 4. unordered_map的[]访问 在unordered_map的使用介绍中我们知道可以用[]来访问修改键值对以及插入数据
//迭代器构造
std::vectorpairstring, string v { {上, up}, { 下, down}, { 左, left}, { 右, right} };
std::unordered_mapstring, string m(v.begin(), v.end());//[]访问
cout m[上] endl;
cout m[下] endl;
cout m[左] endl;
cout m[右] endl;//[]修改
m[右] tutu;//[]插入
m[中] center;cout endl;
cout 修改插入后 endl;
std::unordered_mapstring, string::iterator it m.begin();
while (it ! m.end())
{cout it-first : it-second endl;it;
}
cout endl;结果如下 unordered_map的[]能够插入数据是因为其复用了插入函数如果[]里面引用的值不存在unordered_map中就会插入并返回键值对的值存在就直接返回键值对的值而插入函数中恰好会先寻找合适的插入位置并返回bool值所以我们只需对插入函数返回的值进行修改这与之前学习过的map类似 我们将插入函数的返回值设为pair类型如果插入成功就返回新节点的迭代器和true如果插入失败那么map中肯定以及有相同的值那么返回该位置的迭代器和false
这样在[]中就可以复用插入函数完成插入和修改操作了 V operator[](const K key){pairiterator, bool ret _ht.insert(make_pair(key, V()));return ret.first-second;}插入函数只需要修改返回值即可
pairIterator,bool Insert(const T data)
{//...简写//1.插入成功return make_pair(Iterator(newnode,this),true);//2.插入失败return make_pair(it,false);//已有位置的迭代器
} 5. unordered_set和unordered_map封装 我们对于unordered_set和unordered_map封装需要各种新开一个头文件unordered_set.h和unordered_map.h来进行并且都需要包含Hash.h头文件放在自己的命名空间内避免与STL标准库中的map和set弄混。
✨修改后的哈希表
//哈希函数
templateclass K
struct HashFunc
{size_t operator()(const K key){return (size_t)key;}
};// 哈希表中支持字符串的操作
template
struct HashFuncstring
{size_t operator()(const string key){size_t hash 0;for (auto e : key){hash * 31;hash e;}return hash;}
};//哈希节点类
templateclass T
struct HashNode
{T _data;HashNodeT* _next;HashNode(const T data):_data(data), _next(nullptr){}
};// 前置声明
templateclass K, class T,class KeyOfT, class Hash
class HashTable;//迭代器
templateclass K, class T, class Ref, class Ptr,class KeyOfT, class Hash
struct HashTableIterator {typedef HashNodeT Node;typedef HashTableIteratorK,T,Ref, Ptr, KeyOfT, Hash self;typedef const HashTableK, T, KeyOfT, Hash HashTable;Ref operator*(){return _node-_data;}Ptr operator-(){return (_node-_data);}bool operator!(const self t){return _node ! t._node;}bool operator(const self t){return _node t._node;}self operator(){if (_node-_next)_node _node-_next;//如果当前桶走完了找下一个桶//1.先找到当前位置KeyOfT kot;Hash hs;size_t hash hs(kot(_node-_data)) % _pht-_tables.size();hash;while (hash _pht-_tables.size()){if (_pht-_tables[hash])break;hash;}if (hash _pht-_tables.size())_node nullptr;else_node _pht-_tables[hash];return *this;}//构造HashTableIterator(Node* node, HashTable* pht):_node(node),_pht(pht){}Node* _node;HashTable* _pht;};//哈希类templateclass K, class T, class KeyOfT, class Hash HashFuncKclass HashTable {public:// 友元声明templateclass K, class T, class Ref, class Ptr,class KeyOfT, class Hashfriend struct HashTableIterator;typedef HashNodeT Node;typedef HashTableIteratorK, T, T, T*, KeyOfT, Hash Iterator;typedef HashTableIteratorK, T, const T, const T*, KeyOfT, Hash Const_Iterator;Iterator Begin(){if (_n 0)return End();int i 0;while (i _tables.size()){if (_tables[i] ! nullptr)break;i;}return Iterator(_tables[i],this);}Iterator End(){return Iterator(nullptr,this);}Const_Iterator Begin() const{if (_n 0)return End();int i 0;while (i _tables.size()){if (_tables[i] ! nullptr)break;i;}return Const_Iterator(_tables[i], this);}Const_Iterator End() const{return Const_Iterator(nullptr, this);}HashTable(){_tables.resize(10, nullptr);}~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;}}pairIterator, bool Insert(const T data){KeyOfT kot;//1.先找是否已经插入过相同的值Iterator it Find(kot(data));if (it ! End())return make_pair(it,false);Hash hs;//2.判断是否需要扩容//如果负载因子为1就扩容if (_n _tables.size()){HashTableK, T, KeyOfT h;h._tables.resize(2 * _tables.size(), nullptr);//只需要将哈希桶插入即可for (size_t i 0; i _tables.size(); i){Node* cur _tables[i];while (cur){size_t hash hs(kot(cur-_data)) % h._tables.size();Node* Next cur-_next;cur-_next h._tables[hash];h._tables[hash] cur;cur Next;}_tables[i] nullptr;}_tables.swap(h._tables);}//3.通过Hash函数找到插入位置size_t addr hs(kot(data)) % _tables.size();//4.头插到新表if (_tables[addr] nullptr)//如果是空_n就需要_n;Node* newnode new Node(data);newnode-_next _tables[addr];_tables[addr] newnode;return make_pair(Iterator(newnode,this), true);}Iterator Find(const K key){//先找到key对应的Hash值Hash hs;KeyOfT kot;size_t ht hs(key) % _tables.size();Node* cur _tables[ht];while (cur){if (kot(cur-_data) key)return Iterator(cur,this);cur cur-_next;}return End();}bool Erase(const K key){//1.先找到删除的位置Hash hs;KeyOfT kot;size_t ht hs(key) % _tables.size();Node* cur _tables[ht];Node* parent nullptr;while (cur){if (kot(cur-_data) key)break;parent cur;cur cur-_next;}if (cur nullptr)return false;//2.删除对应节点if (parent)parent-_next cur-_next;else_tables[ht] cur-_next;//修改_nif (_tables[ht] nullptr)_n--;//3.释放原节点delete cur;return true;}private:vectorNode* _tables;size_t _n;//记录存储数据个数};✨unordered_map类
#includeHash.h
// unordered_map类
namespace tutu
{
templateclass K, class V
class unordered_map {
public://定义一个类获取键值对里面的键struct MapKeyOfT {const K operator()(const pairK, V data){return data.first;}};typedef typename HashTableK, pairK, V, MapKeyOfT::Iterator iterator;typedef typename HashTableK, pairK, 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();}pairiterator, bool insert(const pairK, V kv){return _ht.Insert(kv);}V operator[](const K key){pairiterator, bool ret _ht.Insert(make_pair(key, V()));return ret.first-second;}iterator Find(const K key){return _ht.Find(key);}bool Erase(const K key){return _ht.Erase(key);}private:HashTableK, pairK, V, MapKeyOfT _ht;//传给哈希表};//测试函数void test_map(){unordered_mapstring, string dict;dict.insert({ sort, 排序 });dict.insert({ left, 左边 });dict.insert({ right, 右边 });dict[left] 左边剩余;dict[insert] 插入;dict[string];unordered_mapstring, string::iterator it dict.begin();while (it ! dict.end()){cout it-first : it-second endl;it;}cout endl;}
}
结果如下 ✨unordered_set类
#includeHash.h
namespace tutu{templateclass Kclass unordered_set{struct SetKeyOfT{const K operator()(const K key){return key;}};public:typedef typename HashTableK, const K, SetKeyOfT::Iterator iterator;typedef typename HashTableK, const K, SetKeyOfT::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();}pairiterator, 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.Erase(key);}private:HashTableK, const K, SetKeyOfT _ht;};void Print(const unordered_setint s){unordered_setint::const_iterator it s.begin();while (it ! s.end()){// *it 1;cout *it ;it;}cout endl;}void test_set()
{unordered_setint s;int a[] { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 3,3,15 };for (auto e : a){s.insert(e);}for (auto e : s){cout e ;}cout endl;unordered_setint::iterator it s.begin();while (it ! s.end()){//*it 1;cout *it ;it;}cout endl;Print(s);
}}
结果如下 6. 结语 unordered_map和unordered_set的底层都是使用哈希表来实现的然后在外面套了一层壳为了能够更好的实现代码复用我们对哈希表进行了很多修改还使用了仿函数封装了普通迭代器和const迭代器等最终成功对unordered_map和unordered_set实现了封装它们和使用红黑树对map和set封装非常类似大家可以一起参考学习。以上就是今天所有的内容啦~ 完结撒花 ~