C++ unordered封装

news/2024/11/5 16:03:31 标签: c++, 开发语言

C++ 哈希-CSDN博客

哈希表是unordered系列容器的底层逻辑,再实现了哈希的底层后,我们按照如下步骤封装unordered:

1. 改变数据类型,将HashTable中的所有的_kv都改成T

2. 因为map需要取key,写一个KeyOfT的仿函数并封装

3. iterator , ConstIterator的实现和封装

4. 解决set和map中的key不能修改(value应该设计成可以修改)

5. opereator[]

依照上述思路,我们来实现代码。 

1. 调整数据类型

                                   

但是在改数据的过程中,会发现一些需要KeyOfT的内容(不明白什么是KeyOfT的可以先去看红黑树改造为set和map)

因为不确定data是单个数据或者是一个pair,不能直接这样用first。

2. KeyOfT

然后分别拿两个仿函数KeyOfMap和KeyOfSet: 

            

对照着到HashTable中也去改变一下: 

定义出仿函数对象 :

         

然后要将刚刚写的"data.first"都改了:

有细心的读者可能会问,为什么HashTable需要既传Key,又传T,原因是Key是需要被当做一个类型去使用的,比如Find函数。


3. 迭代器

               

关于迭代器的遍历:

        一个桶内很好动,一直it = it->_next即可;可是一个桶走完了如何找下一个桶?

因为每一条链都是独立的,因此迭代器中光有一个节点指针是不够的。

                  

库中的实现方式:

                        

直接传过来整个表对象,这样就能用_hst里的_table来遍历了。   

因此,构造HSTIterator时需要多一个参数

 


 利用哈希映射找到下一个该去的位置:

                                                     

解决思路:通过_pnode重新找一个当前的hashi并遍历                                                   

这样就能根据迭代器指向的元素的data计算出一个hashi,但是需要先把将key转换成int的仿函数Hashfunc传入。

Self& operator++()
{
	if (_pnode->_next) {
		_pnode = _pnode->_next;
	}
	else {
		//去找下一条链
		KeyOfT kot;
		Hash _hs;
		size_t hashi = _hs(kot(_pnode->_data)) % _hst->_table.size();
		//找到了_pnode指向数据所对应的hashi,现在让hashi在vector<Node*>中往后找到有效位置
		while (hashi < _hst->_table.size())
		{
			++hashi;
			if (_hst->_table[hashi] != nullptr) {
				_pnode = _hst->_table[hashi];
				break;
			}
		}
		//走到这的时候,可能是hashi已经走到了末尾,也可能是找到了有效位置所以break了
		if (hashi == _hst->_table.size()) {
			_pnode = nullptr;
		}
	}
	return *this;
}

hashi和表中的vector的size一样大的时候,表明已经走完了vector但是没有遇到合适的桶。

再实现几个小接口:


 然后就可以回到HashTable中去封装iterator了:

                                  (为了让编译器检查通过,还要在最后加一个return)

 End直接返回一个Iterator(nullptr,this)即可。

回到两个unordered中,封装迭代器:

模版类中拿内置类型,需要加一个typename说明是类型而不是static修饰的变量。 


我们尝试对已经写好的代码进行纠错和更正,现在还有以下几个问题值得说明:

 iterator中使用了HashTable,HashTable中的Begin()等函数使用了iterator。两个模块在互相使用、互相包,所以编译iterator的时候找不到hashtable

 解决办法:前置声明。

在写iterato之前先声明HashTable。


友元:

现在存在的问题:

在迭代器(HSTIterator)的operator++中,我们访问了_table,但是_table是私有,是不能被访问的

_table是私有,但是我们要在iterator中访问他,因此需要友元。

普通类的友元直接frend即可,但是类模版的友元:

要在friend的声明前加上模版参数。

最后再封装constIterator

ConstIterator在构造时会有问题

错误出现在HashTable封装的这一层上:

这里的this是被const修饰了的,所以传进去构造ConstIterator的this也是被const修饰了的。

而红框中的pht是没有const修饰的,所以出现了权限的错误。

但是请注意:对于构造函数和析构函数的this添加const修饰是非法的,所以只修饰hst即可。 

正确修改:


3. key不能修改

因为map和set都是不允许改变T(也就是value)的数值的,所以第二个K要改成const。 

set:

map:

其他小接口直接套:

                               


4.operator方括号

根据库中的逻辑改写insert,然后再利用Insert实现方括号访问。

pair<Iterator,bool> insert(const T& data) {
	HashFunc<K> hs;
	KeyOfT kot;
	if (Find(kot(data))!=End()) return make_pair(Find(kot(data)),false);//!!!!!data.first应该改成KeyOfT来控制

	//扩容
	if ((double)_n / _tables.size() >= 1) {
		/*HashTable newHT;
		newHT._tables.resize(2 * _tables.size());

		for (int i = 0; i < _tables.size(); i++) {
			Node* cur = _tables[i];
			while (cur) {
				newHT.insert(cur->_kv);
				cur = cur->_next;
			}
		}
		_tables.swap(newHT._tables);*/

		vector<Node*> newTable;
		newTable.resize(_tables.size() * 2);
		for (int i = 0; i < _tables.size(); i++) {
			Node* cur = _tables[i];
			while (cur) {
				Node* next = cur->_next;
				//找新表的下标
				size_t hashi = hs(kot(cur->_data)) % newTable.size();//!!!!!data.first应该改成KeyOfT来控制
				//头插到新表
				cur->_next = newTable[hashi];
				newTable[hashi] = cur;

				cur = next;
			}
			_tables[i] = nullptr;
		}
		_tables.swap(newTable);
	}


	//头插
	Node* newnode = new Node(data);
	size_t hashi = hs(kot(data)) % _tables.size();
	if (_tables[hashi] == nullptr) {
		_tables[hashi] = newnode;
	}
	else {
		Node* cur = newnode;
		cur->_next = _tables[hashi];
		_tables[hashi] = cur;
	}
	++_n;
	return make_pair(Iterator(newnode,this), true);
}

 利用insert :


5. 控制HashFunc

 如果此时传入一个Date的自定义类型进入unordered_set,会因无法对“日期类”取模而报错。

原因是class Hash这个默认参数传参的位置不对。像日期类这样一个特殊的类,他的哈希映射函数和我们之前所实现的都不一样,需要将日期类传入set或map的用户自己来传入哈希映射。

所以,应该将这个控制提到外层,并且加入默认参数。

     

比如现在要传Date类型的,就需要自己传一个Hash的函数上去。(Hash是用来将key转化为int以便进行取模的函数)

注意,改变一个位置的模版参数,所有其他对应位置的模版参数都需要改变! 


6. 小结

回过头看:

现在,unordered系列容器中很多我们之前不理解的接口就能理解了。

比如bucket_count就是返回桶的数量(包括空桶和非空桶)。

                                       

 在对实现出来的数据结构进行测试时也发现,扩容对于unordered系列容器都是不小的消耗,所以提前reserve的意义就体现出来了,的确能加快速度。


完整代码链接:

111 · 2096177 · lsnmjp/c语言学习过程 - Gitee.com 


http://www.niftyadmin.cn/n/5739656.html

相关文章

【React】初学React

A. react中如何创建元素呢&#xff1f; 说明一点&#xff1a; 属性都改为驼峰形式&#xff08;无障碍属性aria-*除外&#xff09;&#xff0c; class改成className 创建元素 B. 变量或表达式如何表示呢&#xff1f;大括号{ }包起来 变量值用大括号包裹 C. 元素和组件的区别 元素…

深入了解 Vite 中的手动分包技术

之前自己的项目当中&#xff0c;对于分包的概念仅停留在前后端分离以及我做开源项目时&#xff0c;已有的项目进行了分包&#xff0c;包与包之间变量和方法的相互调用等&#xff0c;上线项目也没有特意为分包方案做具体的配置。但在现代前端开发中&#xff0c;提高应用程序的加…

如何评估共享旅游卡的市场潜力?

评估共享旅游卡的市场潜力&#xff0c;我们可以从以下几个维度进行分析&#xff1a; 市场需求分析&#xff1a; 根据搜索结果&#xff0c;随着人们生活水平的提高和旅游消费观念的转变&#xff0c;越来越多的人追求个性化、多样化、性价比高的旅游体验。这表明共享旅游卡项目正…

Hadoop-004-Big Data Tools插件的使用

一、Big Data Tools插件配置流程 1、安装Big Data Tools插件 以IntelliJ IDEA 2024.2.3为例打开setting, 搜索安装Big Data Tools插件后重启IDEA 2、Windows系统基础配置 Windows系统需要做一些基础设置&#xff0c;配合插件使用,将之前下载的hadoop-3.2.4.tar.gz 解压到D…

音视频入门基础:H.264专题(22)——通过FFprobe显示H.264裸流每个packet的信息

音视频入门基础&#xff1a;H.264专题系列文章&#xff1a; 音视频入门基础&#xff1a;H.264专题&#xff08;1&#xff09;——H.264官方文档下载 音视频入门基础&#xff1a;H.264专题&#xff08;2&#xff09;——使用FFmpeg命令生成H.264裸流文件 音视频入门基础&…

IDEA加载通义灵码插件及使用指南

安装通义灵码插件 登录通义灵码IDE插件 下载登录参考教程 https://help.aliyun.com/zh/lingma/user-guide/download-the-installation-guide 本地工程和企业知识库准备 请下载本地工程和知识库压缩包&#xff0c;并在本地解压缩&#xff0c;其中包含demoProject和知识库文件…

PyQt5实战——操作台打印重定向,主界面以及stacklayout使用(四)

个人博客&#xff1a;苏三有春的博客 系类往期文章&#xff1a; PyQt5实战——多脚本集合包&#xff0c;前言与环境配置&#xff08;一&#xff09; PyQt5实战——多脚本集合包&#xff0c;UI以及工程布局&#xff08;二&#xff09; PyQt5实战——多脚本集合包&#xff0c;程序…

在区块链技术中,什么是权益证明(PoS)?

权益证明&#xff08;Proof of Stake, PoS&#xff09;是一种与工作量证明&#xff08;Proof of Work, PoW&#xff09;类似的共识机制&#xff0c;但它通过不同的方式来确保区块链网络的安全性和一致性。PoS的主要目标是解决PoW中存在的高能耗问题&#xff0c;并提高网络的扩展…