【数据结构】【C++】封装哈希表模拟实现unordered_map和unordered_set容器

【数据结构】&&【C++】封装哈希表模拟实现unordered_map和unordered_set容器

  • 一.哈希表的完成
  • 二.改造哈希表(泛型适配)
  • 三.封装unordered_map和unordered_set的接口
  • 四.实现哈希表迭代器(泛型适配)
  • 五.封装unordered_map和unordered_set的迭代器
  • 六.解决key不能修改问题
  • 七.实现map[]运算符重载

一.哈希表的完成

在上一篇哈希表的模拟实现中,已经将哈希表实现完毕,这里不再分析。

#pragma once
using namespace std;
#include <vector>
#include<iostream>//哈希桶//1.第一哈希表的完成//哈希结点
template <class K,class V>
struct HashNode
{pair<K, V> _kv;//存储的数据HashNode<K, V>* _next;//指向下一个结点的指针HashNode(const pair<K,V> kv):_kv(kv),_next(nullptr){}
};template <class K>
struct defaulthashfunc//默认的仿函数可以让数据模
{size_t operator()(const K& key){return (size_t)key;//将key强行类型转化//这样作的意义:可以负数也可以进行模了}
};
template <>
//模板的特化,当数据类型是int的时候就默认使用defaulthashfunc<int>,当数据类型是string类型时,就默认使用defaulthashfunc<string>
struct defaulthashfunc<string>
{size_t operator()(const string& str){//为了减少冲突,我们将字符串的每个字符的值相加size_t hash = 0;for (auto& it : str){hash *= 131;hash += it;}return hash;}
};//要写一个仿函数?  因为不是所有的数据类型都可以模的
// 一般整数是可以模的,string类型是无法模的
// 所以我们要写一个仿函数来达到传的数据可以模
//这样也就是增加了哈希表的一个模板参数l
template<class K,class V,class Hashfunc=defaulthashfunc<K>>
//哈希表
class Hash_table
{typedef HashNode<K, V> Node;//哈希需要将写析构函数,虽然自定义类型vector会自动调用默认析构,但它里面的成员是内置类型,没有默认构造,//所以需要我们自己析构每个结点
public:~Hash_table(){for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;//要先保存起来delete cur;cur = next;}_table[i] = nullptr;}}Hash_table(){//构造_table.resize(10, nullptr);//首先开出十个空间,每个空间值为空}bool insert(const pair<K, V> _kv){Hashfunc hf;//仿函数可以使数据模//在插入之前,确认一下表里是否已经有了这个值,如果有了就不用插入了if (find(_kv.first)){return false;}//我们自己控制扩容逻辑,虽然vector会自己扩容,但我们要控制。因为扩容完,有的key会冲突有的值又不冲突了。//如果不扩容,那么冲突多了就根单链表一样了//当负载因子大约等于1时就要扩容,平均每个桶里有一个数据if (n == _table.size()){//异地扩容,重新开空间size_t newSize = _table.size() * 2;vector<Node*> newtable;newtable.resize(newSize, nullptr);//不能再复用下面的方法,这样不好,因为就又重开空间,然后又要释放,//我们应该将原来的结点拿过来使用//所以我们遍历旧表,将旧表的结点拿过来,签到新表上for (size_t i = 0; i < _table.size(); i++){//扩容后,空间size变大了,有的数据就可能会存到不同的桶里了//拿下来的结点要重新计算放进哪个位置Node* cur = _table[i];//cur后面可能还有链接的结点while (cur){size_t hashi = hf(cur->_kv.first) % newtable.size();Node* next = cur->_next;//头插到新表cur->_next = newtable[hashi];//头插 这个结点的 接到插入结点的前面对吧//那么next就接到newtavle[i]newtable[hashi] = cur;//往后走接着拿走cur = next;}//当前桶里的结点被拿光后,就置为空_table[i] = nullptr;}//这个新表就是我们想要的表,那么我们利用vector、的交换,让旧表和新表交换_table.swap(newtable);}size_t hashi = hf(_kv.first) % _table.size();Node* newnode = new Node(_kv);newnode->_next = _table[hashi];_table[hashi] = newnode;//将新结点头插到哈希桶里++n;return true;}Node* find(const K& key){Hashfunc hf;size_t hashi = hf(key)% _table.size();Node* cur = _table[hashi];while (cur){if (cur->_kv.first == key)return cur;elsecur = cur->_next;}return nullptr;}void Print(){for (int i = 0; i < _table.size(); i++){printf("[%d]", i);Node* cur = _table[i];while (cur){cout << cur->_kv.first << " ";cur = cur->_next;}cout << endl;}}bool erase(const K& key){Hashfunc hf;//可以复用find吗?先用find找到key然后删除key呢?4//不可以,因为删除一个结点需要找到这个结点的前面和后面的位置,但这里只有key的位置,所以不能直接复用find,但是复用其中的步骤size_t hashi = hf(key) % _table.size();Node* cur = _table[hashi];Node* prev = nullptr;while (cur){if (cur->_kv.first == key)//找到要删除的结点后{//将前面的结点的指针指向后面的前面//还有一种可能cur就是桶里的第一个,那么就是头删了,prev就是nullptrif (prev == nullptr){_table[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;return true;}else{prev = cur;//每次先记录一下前面的结点位置cur = cur->_next;}}return false;}
private://底层封装的是一个数组//数组里的指针指向的都是哈希结点vector<Node*> _table;//底层还封装一个大小n表示实际表中的数据个数size_t n=0;///用来计算负载因子
};

二.改造哈希表(泛型适配)

我们要对哈希表进行改造,因为unordered_map和unordered_set底层用的都是哈希表,虽然不是同一个哈希表,但是是同一个模板实例化出来的哈希表。我们要让哈希表可以适配存储不同的数据类型,因为unordered_set里面存的是K类型,而unordered_map里面存的是pair<K,V>类型。
所以我们一开始并不知道哈希表里存的数据是什么类型,那么就用T表示。当传的模板参数是K类型,哈希表就实例化存的就是K类型,当传的模板参数是pair<K,V>类型,哈希表实例化存的就是pair<K,V>类型,所以我们可以通过传不同的模板参数来决定哈希表里存的是什么数据类型。
所以我们需要改造哈希表,将里面的数据都改成T类型,修改成T类型的原因是我们不知道是什么数据类型,根据传的模板参数决定。


template <class T>
//定义哈希结点
struct HashNode
{T _data;//存储的数据是T类型,根据传过来的模板参数确定HashNode<T>* _next;//指向下一个结点的指针HashNode(const T& data):_data(data), _next(nullptr){}
};
// [问题]:一旦泛型后,我们就不知道数据的具体类型了,那么我们要利用除留余数法计算哈希地址时(我们都是利用key来进行取模的而我们不知道具体的类型是K类型还是pair<K,V>类型),就要写一个仿函数,这个仿函数根据map和set传过来的,然后实例化出哈希表中的仿函数。获取数据中的key,让key进行计算```c
template<class K, class T>
//哈希表
class Hash_table
{typedef HashNode<T> Node;public:~Hash_table(){for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;//要先保存起来delete cur;cur = next;}_table[i] = nullptr;}}Hash_table(){//构造_table.resize(10, nullptr);//首先开出十个空间,每个空间值为空}bool insert(const T& data){…………………}bool find(const K& key){………………}bool erase(const K& key){………………	}
private:vector<Node*> _table;//底层还封装一个大小n表示实际表中的数据个数size_t n = 0;//用来计算负载因子
};

存在问题:
这里不管是插入还是查找还是删除第一步都是需要将数据的哈希地址找到,而哈希地址是利用除留余数法计算得到的,是利用key值进行取模的,但这里一旦适配泛型后,我们就不知道具体的类型是K类型还是pair<K,V>类型,如果是K类型那么就可以直接取模,如果是pair<K,V>类型,那是不可以直接取模的,需要将里面的key值取出来。
解决方法:
我们可以利用一个仿函数,这个仿函数的功能是可以将数据里的Key类型数据取出来。那么我们可以给哈希表增加一个模板参数,给仿函数用。一旦遇到要计算哈希地址或者比较的操作时,我们就可以将数据里的K值取出来进行计算比较。
仿函数实现的原理:当T类型是K类型数据时,直接返回K值即可,当T类型是pair<K,V>数据时,返回里面的first数据即可(就是K值)。

template<class K, class T, class KeyOfT,class Hashfunc = defaulthashfunc<K>>
//哈希表
class Hash_table
{typedef HashNode<T> Node;
public:Hash_table(){//构造_table.resize(10, nullptr);//首先开出十个空间,每个空间值为空}bool insert(const T& data){KeyOfT kt;Hashfunc hf;//仿函数可以使数据模//在插入之前,确认一下表里是否已经有了这个值,如果有了就不用插入了if (find(kt(data))){return false;}if (n == _table.size()){//异地扩容,重新开空间size_t newSize = _table.size() * 2;vector<Node*> newtable;newtable.resize(newSize, nullptr);for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];//cur后面可能还有链接的结点while (cur){size_t hashi = hf(kt(cur->_data)) % newtable.size();Node* next = cur->_next;cur->_next = newtable[hashi];newtable[hashi] = cur;//往后走接着拿走cur = next;}//当前桶里的结点被拿光后,就置为空_table[i] = nullptr;}//这个新表就是我们想要的表,那么我们利用vector、的交换,让旧表和新表交换_table.swap(newtable);}size_t hashi = hf(kt(data)) % _table.size();//这里有两个仿函数kt是用来获取data数据里的key值//hf是用来适配不同的K值,因为string类型无法取模Node* newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;//将新结点头插到哈希桶里++n;return true;}iterator find(const K& key){Hashfunc hf;KeyOfT kt;size_t hashi = hf(key) % _table.size();Node* cur = _table[hashi];while (cur){if (kt(cur->_data) == key)return iterator(cur,this);elsecur = cur->_next;}return iterator(nullptr,this);}bool erase(const K& key){Hashfunc hf;KeyOfT kt;size_t hashi = hf(key) % _table.size();Node* cur = _table[hashi];Node* prev = nullptr;while (cur){//这里仿函数kt是用来获取data数据里的key值if (kt(cur->_data) == key)//找到要删除的结点后{//将前面的结点的指针指向后面的前面//还有一种可能cur就是桶里的第一个,那么就是头删了,prev就是nullptrif (prev == nullptr){_table[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;--n;return true;}else{prev = cur;//每次先记录一下前面的结点位置cur = cur->_next;}}return false;}
private:vector<Node*> _table;//底层还封装一个大小n表示实际表中的数据个数size_t n = 0;//用来计算负载因子
};

三.封装unordered_map和unordered_set的接口

封装set,内部实现仿函数,然后底层封装的是存储K值(结点指针)的哈希表。

#include"Hash.h"
namespace tao
{template<class K>class set{struct setoft//仿函数用来获取数据的key值{const K& operator()(const K& key){return key;}};public:bool insert(const K& key){return _ht.insert(key);}private://底层封装一个哈希表,哈希表里存的是K类型Hash_table<K, K, setoft> _ht;};
};

封装map,实现仿函数,底层存储的是pair<K,V>类型的(结点指针)哈希表。

#include"Hash.h"
namespace tao
{template<class K, class V>class map{struct mapoft//仿函数获取数据中的key值{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:bool insert(const pair<K, V>& kv){return _ht.insert(kv);}private://底层封装一个哈希表,哈希表里的数据是pair<K,V>类型Hash_table<K, pair<const K, V>, mapoft> _ht;};
};

四.实现哈希表迭代器(泛型适配)

哈希表的迭代器是一个自定义类型,因为原生的结点指针不能满足我们想要的操作,所以我们就直接将结点的指针封装起来,然后自定义一个类型,对这个结点指针增加一些操作来完成我们想要的效果。
首先哈希表的迭代器里肯定要封装一个结点的指针,++后找到下一个结点,在一个哈希桶里,++就代表着找链表下面的结点即可,那么如果该结点就是链表最后一个结点呢?怎么找到下一个不为空的桶呢?又或者你是如何找到第一个不为空的桶的呢?
所以这里我们还需要一个指向哈希表的指针,这样我们才可以找到桶与桶之间的关系,而结点指针是用来找结点与结点之间的关系的。

哈希表的迭代器里封装两个对象,一个是结点指针,一个哈希表指针。

1.迭代器有普通迭代器和const迭代器,普通迭代很好实现,那么const迭代器如何实现呢?
与链表的const迭代器实现原理一样,我们通过三个模板参数(template <class T,class Ref,class Ptr>)来控制函数的返回值,从而控制返回的是普通类型的迭代器还是const类型的迭代器。这里也就是泛型适配,适配多种类型 。

Ref控制解引用函数的返回值,当Ref为T&时,返回的就是普通迭代器,当Ref为const T&时,返回的就是const迭代器。
Ptr控制的->重载函数的返回值,当Ptr为T时,返回的就是普通迭代器,当Ptr为const T时,返回的就是const迭代器。

2.哈希表迭代器的++实现原理:

①假设当前结点在一个桶里,这个桶还没走完,那么直接找下一个结点即可
②如果这个结点是桶里最后一个原生,即桶走完了,那么我们就要找下一个不为空的桶
③如何找到下一个不为空的桶呢?首先将当前结点的哈希地址计算出来,然后将哈希地址++,再利用一个循环,查找后面的桶是否为空,如果不为空,那么这个桶就是最终结果,如果为空就再找后面的桶。

//泛型适配--适配普通迭代器和const迭代器
template<class K, class T,class Ref,class Ptr, class KeyOfT, class Hashfunc>
//哈希表的迭代器里面肯定封装一个结点的指针,但还需要通过表来找到下一个桶,因为我们需要遍历先找到第一个桶,当这个桶遍历完后,怎么找到下一个不为空的桶呢?
//需要通过这个哈希表来找到桶,所以我们还需要一个指向哈希表的指针struct HSIterator
{typedef  HashNode<T> Node;/typedef HSIterator<K, T,Ref,Ptr,KeyOfT, Hashfunc> Self;Node* _node;//底层封装着一个结点指针const Hash_table <K,T, KeyOfT,Hashfunc>* _pht;//底层还封装着一个哈希表指针//这里可以加const因为我们不是根据pht来找到哈希表来修改哈希表里的内容HSIterator(Node* node, Hash_table<K, T, KeyOfT,Hashfunc>* hs):_node(node), _pht(hs){}Ref& operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){if (_node->_next)//当前桶没有走完,那么直接找下一个即可{_node = _node->_next;}else//当前桶走到空了,就要找下一个不为空的桶{KeyOfT kof;Hashfunc hf;size_t hashi = hf(kof(_node->_data)) % _pht->_table.size();//这里我们在外面去调用了哈希表的私有成员,所以我们需要让迭代器成为哈希表的友元//找到当前桶的位置++hashi;//找下一个桶不为空while (hashi<_pht->_table.size()){if (_pht->_table[hashi]){_node = _pht->_table[hashi];return *this;}else{++hashi;}}//走到这里就说明桶走走光了,还没找到_node = nullptr;}return *this;}bool operator!=(const Self& s){return _node != s._node;}};

【存在问题1】:
这里存在一个相互依赖关系问题,因为在哈希表里我们使用了迭代器,在迭代器里我们又使用了哈希表。我们需要在这里使用前置声明告诉编译器,我们是有哈希表,只不过哈希表在后面。这样就不会报错啦。

//这里存在问题,迭代器里要用哈希,哈希里面要有迭代器,相互依赖关系
//我们这里用前置声明告诉编译器,我们要是有哈希表,这个表是存在的,在后面
template<class K, class T, class KeyOfT, class Hashfunc>
class Hash_table;
template<class K, class T,class Ref,class Ptr, class KeyOfT, class Hashfunc>struct HSIterator
{………………………………………………………………………………………………………………………………
};

【存在问题2】:
在迭代器的++里我们在计算当前结点的哈希地址时,取模时,利用哈希指针找到了哈希表里的vector<Node*> _table元素,并访问了它的函数,这里我们在外面调用哈希表的私有成员,这样是不可行,所以我们需要让迭代器成为哈希表的友元类,这样在迭代器里就可以使用哈希表的私有成员了。
在这里插入图片描述

迭代器实现之后,我们就可以在哈希表里,来实现迭代器的begin()和end()了。
begin()就是找哈希表里第一个不为空的桶。
end()就是找最后一个不为空的桶的下一个位置,也就是空。

template<class K, class T, class KeyOfT,class Hashfunc = defaulthashfunc<K>>
//哈希表
class Hash_table
{typedef HashNode<T> Node;template<class K, class T, class Ref,class Ptr,class KeyOfT, class Hashfunc>friend struct HSIterator;//让迭代器成为哈希表的友元
public:typedef HSIterator<K, T,T&,T*, KeyOfT, Hashfunc> iterator;//适配普通迭代器typedef HSIterator<K, T, const T&, const T*, KeyOfT, Hashfunc> const_iterator;//适配const迭代器iterator begin(){//找第一个桶for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){if (cur){return iterator(cur, this);//这里this就是哈希表的指针}}}//最后没有找到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];while (cur){if (cur){return const_iterator(cur, this);}}}//最后没有找到return const_iterator(nullptr, this);}const_iterator end()const{return const_iterator(nullptr, this);}bool insert(const T& data){…………………………}bool erase(const K& key){…………………………}
private:vector<Node*> _table;//底层还封装一个大小n表示实际表中的数据个数size_t n = 0;//用来计算负载因子
};

这样哈希表的迭代器就完成啦。不过这里还有一个问题喔,确实封装哈希表确实比较恶心,比封装红黑树还复杂。
这里的问题在于const修饰begin()和const修饰end()这里是编译不过的。为什么呢?这里提醒没有构造函数可以接收,或者构造函数重载不明确。
在这里插入图片描述
这里的原因是因为const修饰了this指针,导致指向哈希表的指针变成const类型了,而迭代器的构造里,是用普通迭代器构造的。所以当this指针传过去构造时,const是不能传给普通类型的,权限放大了。所以这里我们只需要重载一个参数类型是const类型的哈希表指针即可。

	HSIterator(Node* node, Hash_table<K, T, KeyOfT,Hashfunc>* hs)//hs是普通的类型:_node(node), _pht(hs){}//当传过来的哈希表指针是普通指针就走上面--权限的缩小//重载一个,当传过来的是const修饰的哈希表指针就走这里---权限的平移HSIterator(Node* node, const Hash_table<K, T, KeyOfT, Hashfunc>* hs)//hs是普通的类型:_node(node), _pht(hs){}

五.封装unordered_map和unordered_set的迭代器

只有哈希表里的迭代器完成了,才可以封装map和set里的迭代器。
封装set的迭代器,本质就是调用哈希表的迭代器接口。

1.不过要注意的是,在重命名红黑树里的迭代器时,需要在类名前面加上typename,如果不加上typename是不行的,因为这时类模板还没有实例化出对象出来,就算实例化了,也有部分类型没有实例,因为编译器也不知道这个是内置类型还是静态变量,加上是告诉编译器这个是类型,这个类型在类模板里定义,等类模板实例化后再找。
2.定义好普通迭代和const迭代器后,就可以实现begin()和end()了。

namespace tao
{template<class K>class set{struct setoft{const K& operator()(const K& key){return key;}};public:typedef typename Hash_table<K, K, setoft>::iterator iterator;typedef typename Hash_table<K, K, setoft>::const_iterator const_iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}iterator begin()const{return _ht.begin();}iterator end()const{return _ht.end();}bool insert(const K& key){return _ht.insert(key);}private://底层封装一个哈希表,哈希表里存的是K类型Hash_table<K, K, setoft> _ht;};
};

封装map的迭代器,本质上就是调用哈希表里的迭代器接口。

#include"Hash.h"
namespace tao
{template<class K, class V>class map{struct mapoft{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename Hash_table<K, pair<K, V>, mapoft>::iterator iterator;typedef typename Hash_table<K, pair< K, V>, mapoft>::const_iterator const_iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}iterator begin()const{return _ht.begin();}iterator end()const{return _ht.end();}bool insert(const pair<K, V>& kv){return _ht.insert(kv);}private://底层封装一个哈希表,哈希表里的数据是pair<K,V>类型Hash_table<K, pair< K, V>, mapoft> _ht;};
};

六.解决key不能修改问题

set里面存储的Key值是不能被修改的,map里的存储的K值也是不能被修改,但是Value值是可以被修改!
如果解决这个问题呢?

问题:set里的key值不能被修改。map里的key值不能被修改,value值可以被修改。 set解决原理:
1.set里存储的值就只有Key值,索性我们直接让这个存储的数据无法被修改,只能访问读取,无法修改。即使用const修饰。而我们是通过迭代器来访问到这个数据的,所以我们让普通迭代器变成const迭代器即可。所以在set里,普通迭代器和const迭代器最终都是const迭代器。
2.那么迭代器都是const的了,最终都只会调用const修饰的begin()和end()函数了,普通的begin()和end()就不需要写了。
3.不过这样处理又会出现一个很难搞的问题,这个就是set的insert的返回值问题,我们后面要实现map的[]运算符重载就会讲到。

set的解决方法:public:typedef typename Hash_table<K, K, setoft>::const_iterator iterator;//为了解决set的key不能被修改所以我们让普通迭代器变成const迭代器typedef typename Hash_table<K, K, setoft>::const_iterator const_iterator;//因为普通迭代器也是const所以我们只用写const的begin和end即可。iterator begin()const{return _ht.begin();}iterator end()const{return _ht.end();}//这里的pair<const_iterator,bool>类型的bool insert(const K& key){//return _ht.insert(key);}private://底层封装一个哈希表,哈希表里存的是K类型Hash_table<K, K, setoft> _ht;};
};

map的解决原理
1.在存储的时候就让K值无法修改。
2.因为我们知道map里存储的数据是pair<K,V>类型,我们不能想set那个让普通迭代器变成const迭代器,因为map要求Value的值还是可以修改的,所以不让pair<K,V>类型无法修改,而是单纯的让里面的K值无法修改,也就是在里面用const修饰K,那么这样K值就不能被修改,V值可以被修改。
3.pair是可以修改的,但是里面的K是无法被修改的!

map的解决方法:public:typedef typename Hash_table<K, pair<const K, V>, mapoft>::iterator iterator;typedef typename Hash_table<K, pair<const K, V>, mapoft>::const_iterator const_iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}iterator begin()const{return _ht.begin();}iterator end()const{return _ht.end();}bool insert(const pair<K, V>& kv){return _ht.insert(kv);}private://底层封装一个哈希表,哈希表里的数据是pair<K,V>类型Hash_table<K, pair<const K, V>, mapoft> _ht;};
};

七.实现map[]运算符重载

map的[ ]运算符重载,底层实现本质是调用了insert函数。然后通过insert函数返回的pair<iterator,bool>类型数据来找到Value值。

所以在实现[ ]运算符重载时,我们需要对哈希表里的insert进行改造,因为原来的insert的返回值是布尔值,我们需要pair类型返回值。

pair<iterator,bool> insert(const T& data){KeyOfT kt;Hashfunc hf;//仿函数可以使数据模//在插入之前,确认一下表里是否已经有了这个值,如果有了就不用插入了iterator it = find(kt(data));if (it!=end()){return make_pair(it,false);}if (n == _table.size()){//异地扩容,重新开空间size_t newSize = _table.size() * 2;vector<Node*> newtable;newtable.resize(newSize, nullptr);//不能再复用下面的方法,这样不好,因为就又重开空间,然后又要释放,//我们应该将原来的结点拿过来使用//所以我们遍历旧表,将旧表的结点拿过来,签到新表上for (size_t i = 0; i < _table.size(); i++){//扩容后,空间size变大了,有的数据就可能会存到不同的桶里了//拿下来的结点要重新计算放进哪个位置Node* cur = _table[i];//cur后面可能还有链接的结点while (cur){size_t hashi = hf(kt(cur->_data)) % newtable.size();Node* next = cur->_next;//头插到新表cur->_next = newtable[hashi];//头插 这个结点的 接到插入结点的前面对吧//那么next就接到newtavle[i]newtable[hashi] = cur;//往后走接着拿走cur = next;}//当前桶里的结点被拿光后,就置为空_table[i] = nullptr;}//这个新表就是我们想要的表,那么我们利用vector、的交换,让旧表和新表交换_table.swap(newtable);}size_t hashi = hf(kt(data)) % _table.size();Node* newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;//将新结点头插到哈希桶里++n;return make_pair(iterator(newnode,this),true);}iterator find(const K& key){Hashfunc hf;KeyOfT kt;size_t hashi = hf(key) % _table.size();Node* cur = _table[hashi];while (cur){if (kt(cur->_data) == key)return iterator(cur,this);elsecur = cur->_next;}return iterator(nullptr,this);}

哈希表的insert改造后,那么set和map里的insert都需要修改,因为底层用的就是调用用哈希表的insert。

在这里插入图片描述
这样修改是对的吗?有没有什么问题呢?
但是这时会出现一个问题,set里面的insert报错。这是为什么呢?
在这里插入图片描述
问题在于,我们之前让普通迭代变成const迭代器,而这里的pair<iterator,bool>中的iterator其实本质上是const_iterator。
是pair<const_itearto,bool>类型的。而哈希表里的insert返回的是普通迭代器,也就是pair<iterator,bool>类型的。这是两个不同的类型,无法直接将pair<iterator,bool>类型转换成pair<const_itearto,bool>类型的。所以会报错。
在这里插入图片描述
·
解决方法:

1.迭代器的拷贝函数是浅拷贝,我们不需要写,编译器自动生成的拷贝就可以用,编译器自动生成的拷贝函数只能实现普通迭代器拷贝给普通迭代器,const迭代器拷贝给const迭代器。(原理就是拷贝函数的对象类型就是调用这个函数的类型,当普通迭代器调用拷贝时,那么拷贝对象就是普通类型,当const迭代器调用拷贝时,那么拷贝对象就是const类型)
2.而我们需要的是让普通迭代器能够拷贝给const迭代器。所以我们需要自己增加拷贝函数。
3.库里的设计很妙,库里重新定义了一个iterator,作为拷贝对象,而这个iterator固定了就是普通的迭代器,不会随着调用对象而改变类型。所以当普通迭代器调用时,就会将普通iterator拷贝给它。当const迭代器调用时,就会将普通迭代器iterator拷贝给它。
4.所以我们需要对哈希表的迭代器添加拷贝构造。用普通迭代器iteartor作为拷贝对象。

struct HSIterator
{typedef  HashNode<T> Node;typedef HSIterator<K, T, T&, T*, KeyOfT, Hashfunc> iterator;typedef HSIterator<K, T,Ref,Ptr,KeyOfT, Hashfunc> Self;Node* _node;const Hash_table <K,T, KeyOfT,Hashfunc>* _pht;//没有取这个类里面的内嵌类型,就不需要加typenameHSIterator(Node* node, Hash_table<K, T, KeyOfT,Hashfunc>* hs)//hs是普通的类型:_node(node), _pht(hs){}//当传过来的哈希表指针是普通指针就走上面--权限的缩小//重载一个,当传过来的是const修饰的哈希表指针就走这里---权限的平移HSIterator(Node* node, const Hash_table<K, T, KeyOfT, Hashfunc>* hs)//hs是普通的类型:_node(node), _pht(hs){}//普通的拷贝构造就是 const迭代器拷贝给const迭代器,普通迭代器拷贝给普通迭代器//而我们写的拷贝构造可以同时支持两个,当调用对象是普通迭代器时,用普通迭代器拷贝,也就是相当于赋值//当调用对象是const迭代器时,也是用普通迭代器拷贝。这样就支持了普通迭代器转化成const迭代器了HSIterator(const iterator& it):_node(it._node),_pht(it._pht){}……………………………………………………};

这样处理后,我们再利用pair<iterator,bool>类型的构造函数,将普通迭代器转换成const迭代器。

1.先将insert返回类型利用ret接收
2.利用pair<iteartor,bool>构造将ret里的普通迭代器转换为const迭代器。

	pair<iterator,bool> insert(const K& key){//return _ht.insert(key);//set调用的insert传回来的pair<iterator,bool>类型的,pair<iterator,bool>与pair<const_iterator,bool>是两个不同的类型//正常的的迭代器拷贝构造我们不需要写,因为迭代器是指针,拷贝构造就是浅拷贝,编译器自动生成的拷贝就可以//但是这里我们需要自己写拷贝构造; 因为需要将pair<iteartor,bool>类型,转化成pair<const_iterator,bool>类型,如何转化的呢?pair<typename Hash_table<K, K, setoft>::iterator, bool> it = _ht.insert(key);return pair<iterator, bool>(it.first, it.second);}

最后insert的改造到这里就结束了,insert改造完后,就可以实现[ ]运算符重载了。

V& operator[](const K&key){pair<iterator, bool> ret = _ht.insert(make_pair(key,V()));//插入的数据是pair类型,要用make_pair构造return ret.first->second;}

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

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

相关文章

家政服务小程序,家政系统开发

家政服务小程序&#xff0c;家政系统开发&#xff0c;打造一线家政系统&#xff0c;提效增收 家政服务小程序 互联网&#xff0b;家政系统&#xff0c;打造互联网&#xff0b;家政公司app开发&#xff0c;支持个性化定制&#xff0c;直接搭建&#xff0c;上手即用&#xff0e;实…

Windows上安装 Go 环境

一、下载go环境 下载go环境&#xff1a;Go下载官网链接找到自己想下载的版本&#xff0c;点击下载&#xff0c;比如我这是windows64位的&#xff0c;我就直接点击最新的。 二、安装go环境 双击下载的.msi文件 next next 他默认的是c盘&#xff0c;你自己可以改&#xff0c;然…

最新影视视频微信小程序源码-带支付和采集功能/微信小程序影视源码PHP(更新)

源码简介&#xff1a; 这个影视视频微信小程序源码&#xff0c;新更新的&#xff0c;它还带支付和采集功能&#xff0c;作为微信小程序影视源码&#xff0c;它可以为用户 提供丰富的影视资源&#xff0c;包括电影、电视剧、综艺节目等。 这个小程序影视源码&#xff0c;还带有…

C语言之内存函数篇(3)

目录 memcpy memcpy的使用 memcpy的模拟实现 NO1. NO2. memcpy可否实现重叠空间的拷贝 my_memcpy memcpy memmove memmove memmove模拟实现 分析 代码 memset memset的使用 memcmp memcmp的使用 <0 0 >0 今天我们继续介绍几个重要的内存操作函…

Celery结合flask完成异步任务与定时任务

Celery 常用于 web 异步任务、定时任务等。 使用 redis 作为 Celery的「消息代理 / 消息中间件」。 这里通过Flask-Mail使用qq邮箱延时发送邮件作为示例 pip install celery pip install redis pip install Flask-Mail1、使用flask发送邮件 使用 Flask-Mail 发送邮件需要进行…

【无标题】ICCV 2023 | CAPEAM:基于上下文感知规划和环境感知记忆机制构建具身智能体

文章链接&#xff1a; https://arxiv.org/abs/2308.07241 2023年&#xff0c;大型语言模型&#xff08;LLMs&#xff09;以及AI Agents的蓬勃发展为整个机器智能领域带来了全新的发展机遇。一直以来&#xff0c;研究者们对具身智能&#xff08;Embodied Artificial Intelligenc…

通过java向jar写入新文件

文章目录 原始需求分析实施步骤引入依赖核心编码运行效果 原始需求 有网友提问&#xff1a; 我想在程序中动态地向同一个jar包中添加文件&#xff0c;比如&#xff0c;我的可执行jar包是test.jar,我要在它运行时生成一些xml文件并将这些文件添加到test.jar中,请问如何实现&…

【分布式计算】三、虚拟化 Virtualization

1.什么是虚拟化 1.1.非虚拟化 我们首先来认识什么是非虚拟化   1.一台机器、一个操作系统、几个应用程序   2.应用程序可能会相互影响。   3.机器利用率较低&#xff0c;正常情况下低于25%。 关于X86平台&#xff1a; 1.服务器基础设施利用率低&#xff08;10-18%&#…

Linux驱动开发笔记

疑问 file_operation中每个操作函数的形参中inode的作用 file_operation定义了Linux内核驱动的所有的操作函数&#xff0c;每个操作函数与一个系统调用对应&#xff0c;对于字符设备来说&#xff0c;常用的函数有&#xff1a;llseek、read、write、pool等等&#xff0c;这些操…

阿里云七代云服务器实例、倚天云服务器及通用算力型和经济型实例规格介绍

在目前阿里云的云服务器产品中&#xff0c;既有五代六代实例规格&#xff0c;也有七代和八代倚天云服务器&#xff0c;同时还有通用算力型及经济型这些刚推出不久的新品云服务器实例&#xff0c;其中第五代实例规格目前不在是主推的实例规格了&#xff0c;现在主售的实例规格是…

【数据结构】堆,堆的实现,堆排序,TOP-K问题

大家好&#xff01;今天我们来学习数据结构中的堆及其应用 目录 1. 堆的概念及结构 2. 堆的实现 2.1 初始化堆 2.2 销毁堆 2.3 打印堆 2.4 交换函数 2.5 堆的向上调整 2.6 堆的向下调整 2.7 堆的插入 2.8 堆的删除 2.9 取堆顶的数据 2.10 堆的数据个数 2.11 堆的判…

内存函数的介绍和模拟实现

目录 1.memcpy的使用(内存拷贝) 2.memcpy的实现 3.memmove的使用&#xff08;内存拷贝&#xff09; 4.memmove的实现 5.memset 的使用&#xff08;内存设置&#xff09; 6.memcmp的使用&#xff08;内存比较&#xff09; 1.memcpy的使用(内存拷贝) void * memcpy ( void * …

整型提升——(巩固提高——字符截取oneNote笔记详解)

文章目录 前言一、整型提升是什么&#xff1f;二、详细图解1.图解展示 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 整型提升是数据存储的重要题型&#xff0c;也是计算机组成原理的核心知识点。学习c语言进阶的时候,了解内存中数据怎么存&#…

孤举者难起,众行者易趋,openGauss 5.1.0版本正式发布!

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

华为云云耀云服务器L实例评测|云耀云服务器L实例搭建个人镜像站

华为云云耀云服务器L实例评测&#xff5c;云耀云服务器L实例搭建个人镜像站 一、云耀云服务器L实例介绍1.1 云耀云服务器L实例简介1.2 云耀云服务器L实例特点 二、Apache介绍2.1 Apache简介2.2 Apache特点 三、本次实践介绍3.1 本次实践简介3.2 本次环境规划 四、远程登录华为云…

SpringCloud Alibaba 入门到精通 - Sentinel

SpringCloud Alibaba 入门到精通 - Sentinel 一、基础结构搭建1.父工程创建2.子工程创建 二、Sentinel的整合SpringCloud1.微服务可能存在的问题2.SpringCloud集成Sentinel搭建Dashboard3 SpringCloud 整合Sentinel 三、服务降级1 服务降级-Sentinel2 Sentinel 整合 OpenFeign3…

【深度学习实验】卷积神经网络(三):自定义二维卷积层:步长、填充、输入输出通道

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入必要的工具包 1. 步长、填充 a. 二维互相关运算&#xff08;corr2d&#xff09; b. 二维卷积层类&#xff08;Conv2D&#xff09; c. 模型测试 d. 代码整合 2. 输入输出通道 a…

Arcgis克里金插值报错:ERROR 999999: 执行函数时出错。 表名无效。 空间参考不存在。 ERROR 010429: GRID IO 中存在错误

ERROR 999999: 执行函数时出错。 问题描述 表名无效。 空间参考不存在。 ERROR 010429: GRID IO 中存在错误: WindowSetLyr: Window cell size does not match layer cell size. name: c:\users\lenovo\appdata\local\temp\arc2f89\t_t164, adepth: 32, type: 1, iomode: 6, …

智能合约漏洞,Dyna 事件分析

智能合约漏洞&#xff0c;Dyna 事件分析 1. 漏洞简介 https://twitter.com/BlockSecTeam/status/1628319536117153794 https://twitter.com/BeosinAlert/status/1628301635834486784 2. 相关地址或交易 攻击交易 1&#xff1a; https://bscscan.com/tx/0x7fa89d869fd1b89e…

算法通过村第十一关-位运算|青铜笔记|初始位运算

文章目录 前言1. 数字在计算中的表示拓展&#xff1a;为什么要有原码、反码和补码? 2. 位运算规则2.1 与、或、异或和取反2.2 位移运算2.3 位移运算和乘除的关系2.4 位运算的常用技巧 总结 前言 提示&#xff1a;我的父亲从我出生起便认识我&#xff0c;可他对我的了解却那么少…