利用哈希表封装unordered_map和unordered_set

news2024/12/28 10:04:17

目录

  • 一、迭代器
    • 1.1 普通迭代器
      • 1.1.1 operator++
    • 1.2 const迭代器
    • 1.3 代码实现
  • 二、封装unordered_set
  • 三、封装unordered_map

一、迭代器

1.1 普通迭代器

1.1.1 operator++

对于哈希桶结构,它的迭代器应该如何设计呢?我们仅封装一个Node的指针就行了吗?其实这里是仅用一个指针是不够的,因为在迭代器重载operator++的时候,如果当前位置的链表还没有遍历完,那就是直接_node=_node->_next就行了,但是如果哈希表中当前位置的链表已经遍历完了,那么就要计算当前迭代器是在哈希表中的哪一个位置,然后从哈希表的下一个位置开始遍历找出下一个不为空的_node,这个_node构造的迭代器就是iterator++的结果,所以现在的问题是我们要知道当前迭代器在哪一个桶,就要知道这个哈希表的size,然后用_node->_data的key%size得到的余数才是当前的位置,但是_node节点中并没有size呀,所以我们选择把哈希表的指针传过来,有这个表的指针就什么数据都有啦!
所以迭代器应该有一个Node
和一个HashTable*构成。

代码实现:

		Self& operator++()
		{
			KeyOfT kot;
			HashFunc hf;
			Node* cur = _node;
			//如果当前桶的链表还没有遍历完,那就遍历链表的下一个节点就好了
			if (_node && _node->_next)
			{
				_node = _node->_next;
			}
			//如果当前桶的链表已经遍历完了,就要在哈希表中往后找下一个不为空的链表的节点
			else
			{
				//重点
				if (_node)
				{
					//利用两层仿函数把_node->_data的key以及key对应的整形取出来,并且通过
					//HashTable的指针取出哈希表的size,通过除留余数法计算出当前桶的位置。
					size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
					//然后++hashi从哈希表的下一个桶开始往后找不为空的链表节点
					hashi++;
					for (size_t i = hashi; i < _pht->_table.size(); i++)
					{
						//找到不为空的节点,那么这个节点就是迭代器下一个要访问的链
						// 表的头节点,即把_node更新为这个节点即可
						if (_pht->_table[i] != nullptr)
						{
							_node = _pht->_table[i];
							return *this;
						}
					}
					//来到这里说明找完了整个哈希表都没有找到一个不为空的链表,说明哈希表
					//已经遍历完了,这时要把_node设置为nullptr,表示到达了结尾
					_node = nullptr;
				}
			}

			return *this;
		}

1.2 const迭代器

在这里插入图片描述

1.3 代码实现

	//利用哈希桶封装unordered_map和unordered_set时,因为unordered_map和unordered_set存储的数据
	//的类型不一样,unordered_map存放的是pair<key,value>,而unordered_set存储的是key,所以我们要
	// 把要存储的类型设置成模板参数,具体类型是什么由用户实例化的时候指定,如果是unordered_set就是T就
	//是key,如果是unordered_map那么T就是pair<key,value>
	template <class T>
	struct HashNode
	{
		//T具体是什么我不管,反正把它设置成模板参数
		//_data根据这个模板参数的类型来定义
		T _data;
		HashNode<T>* _next;

		HashNode(const T& data)
			:_data(data)
			,_next(nullptr)
		{}
	};

	//前置声明,因为迭代器里面用了HashTable,而HashTable是在后面定义的,编译器编译时只会从前面的代码
	//找,所以这里需要声明一下HashTable是存在的,否则编译器会报错,声明时需要带上模板参数
	template <class K, class T, class KeyOfT, class HashFunc>
	class HashTable;

	//迭代器,因为迭代器里面需要用到HashTable中的size来计算当前桶的位置,所以我们需要
	//把HashTable*传过来,同时我们也需要把模板参数传过来。
	template <class K,class T,class Ref,class Ptr,class KeyOfT,class HashFunc>
	struct HTIterator
	{
		typedef HashNode<T> Node;
		typedef HTIterator<K, T, Ref, Ptr, KeyOfT, HashFunc> Self;

		//无论是构造普通迭代器对象还是const迭代器对象,这个iterator都是写死的普通迭代器
		//目的是写一个使用普通迭代器构造const迭代器的构造函数
		typedef HTIterator<K, T, T&, T*, KeyOfT, HashFunc> iterator;

		typedef HashTable<K, T, KeyOfT, HashFunc> HT;


		//节点指针
		Node* _node;

		//哈希表的指针
		//这里HT*一定要加上const修饰,因为下面的构造函数的第二个参数是const HT* pth,所以
		//在初始化列表赋值的时候是const类型的指针赋值给这个_pht,如果这里不用const类型,就
		//等于是权限放大,会报错,所以这两个pht都必须是const类型的
		const HT* _pht;

		//这里的HT一定要加上const修饰,否则会报错,因为传过来的pht指针可能是const修饰的
		//如果这里不用const HT* 接收,那么HT* pht无法接收const修饰的指针,等于是const
		//类型赋值给了普通类型,权限放大,这里会报错
		HTIterator(Node* node,const HT* pht)
			:_node(node)
			,_pht(pht)
		{}

		//重点:类模板传不同的模板参数得到的是不同的类型

		//1、当HTIterator构造的是const迭代器对象时,这个函数是一个构造函数。因为iterator本身是一个普通迭代器,
		// 当HTIterator构造的对象是一个const迭代器的时候,这两个迭代器的类型并不相同,所以这是一个构造函数,
		//即该函数是用一个普通迭代器构造一个const迭代器。
		// 
		//2、当HTIterator构造的是一个普通迭代器对象时,这个函数是一个拷贝构造函数。因为iterator本身是一个普通迭代器,
		//当HTIterator构造的对象也是一个普通迭代器时,这两个迭代器的类型是相同的,所以这是一个拷贝构造函数,即该函
		// 数用一个普通迭代器拷贝构造一个普通迭代器
		HTIterator(const iterator& it)
			:_node(it._node)
			,_pht(it._pht)
		{}


		Self& operator++()
		{
			KeyOfT kot;
			HashFunc hf;
			Node* cur = _node;
			//如果当前桶的链表还没有遍历完,那就遍历链表的下一个节点就好了
			if (_node && _node->_next)
			{
				_node = _node->_next;
			}
			//如果当前桶的链表已经遍历完了,就要在哈希表中往后找下一个不为空的链表的节点
			else
			{
				//重点
				if (_node)
				{
					//利用两层仿函数把_node->_data的key以及key对应的整形取出来,并且通过
					//HashTable的指针取出哈希表的size,通过除留余数法计算出当前桶的位置。
					size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
					//然后++hashi从哈希表的下一个桶开始往后找不为空的链表节点
					hashi++;
					for (size_t i = hashi; i < _pht->_table.size(); i++)
					{
						//找到不为空的节点,那么这个节点就是迭代器下一个要访问的链
						// 表的头节点,即把_node更新为这个节点即可
						if (_pht->_table[i] != nullptr)
						{
							_node = _pht->_table[i];
							return *this;
						}
					}
					//来到这里说明找完了整个哈希表都没有找到一个不为空的链表,说明哈希表
					//已经遍历完了,这时要把_node设置为nullptr,表示到达了结尾
					_node = nullptr;
				}
			}

			return *this;
		}

		//*解引用返回的是节点存储的数据的引用
		Ref operator*()
		{
			return _node->_data;
		}

		//->是返回的是节点存储的数据的地址
		Ptr operator->()
		{
			return &_node->_data;
		}

		bool operator!=(const Self& it)
		{
			return _node != it._node;
		}

		bool operator==(const Self& it)
		{
			return _node == it._node;
		}

	};

二、封装unordered_set

template <class K>
class Unordered_Set
{
public:
	//仿函数,用于取出节点存放的数据的类型
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};

	//和红黑树的原理一样,这里的unordered_set的key值也是不能被修改的,所以只向外面提供了const_iterator,
	//因为这里的iterator本质也是const_iterator
	typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator iterator;
	typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator const_iterator;

	void Print() const
	{
		_ht.SetPrint();
	}

	pair<const_iterator,bool> insert(const K& key)
	{
		//Insert返回的是普通迭代器,无法直接转化成const迭代器。因为就算是同一个类模板,当我们传的模板参数不同时
		//它们是两个不同的类型,不同类型无法直接转化,需要先用普通迭代器的pair接收Insert的返回值,然后再在迭代器
		// 类中增加一个由普通迭代器构造const迭代器的构造函数,这样我们才能构造出一个const迭代器作为该insert函数
		//的返回值
		pair<typename hash_bucket::HashTable<K, K, SetKeyOfT>::iterator, bool> ret = _ht.Insert(key);

		//ret.first是普通迭代器,而pair的第一个参数是const迭代器,所以这里实际是利用一个普通迭代器构造出
		//一个const迭代器,所以会调用iterator的构造函数构造出一个const迭代器,再用const迭代器构造pair
		//所以在迭代器中必须提供一个由iterator构造const_iterator的构造函数,否则会报错的
		return pair<const_iterator, bool>(ret.first, ret.second);
	}

	bool erase(const K& key)
	{
		return _ht.Erase(key);
	}

	//因为set的key是不可修改的,所以只需要提供一个const版本的迭代器就可以了
	const_iterator begin() const
	{
		return _ht.begin();
	}

	const_iterator end() const
	{
		return _ht.end();
	}

	iterator find(const K& key)
	{
		return _ht.Find(key);
	}

private:
	//虽然对于unordered_set来说只存储一个K,但是为了能够和unordered_map更好地复用
	//同一份代码,所以这里的unordered_set也是采用了和unordered_map同样的K-V模型存
	//储数据,所以需要传两个K,第一个K表示节点的key值,第二个K表示节点存储的数据的类型
	//两个K的含义是不一样的
	hash_bucket::HashTable<K, K, SetKeyOfT> _ht;
};

三、封装unordered_map

template <class K,class V>
class Unordered_Map
{
public:
	//仿函数,用于取出节点存放的数据的类型
	struct MapKeyOfT
	{
		const K& operator()(const pair<const K,V>& kv)
		{
			return kv.first;
		}
	};

	//普通迭代器,因为对于unordered_map来说,key值是不能修改的,但是value值是可以被修改的,所以我们不能像
	//unordered_set那样直接把两个迭代器都设计成const迭代器,那样的话这里的value也不能修改了,所以我们在
	//pair的key前加上const修饰,这样的话pair的key就不能被修改,而value是可以被修改的
	// 
	//同时这里要加上typename告诉编译器hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::iterator
	//是一个类型,否则会报错
	typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
	//const迭代器
	typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;

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

	bool erase(const K& key)
	{
		return _ht.Erase(key);
	}

	iterator begin()
	{
		return _ht.begin();
	}

	iterator end()
	{
		return _ht.end();
	}

	const_iterator begin() const
	{
		return _ht.begin();
	}

	const_iterator end() const
	{
		return _ht.end();
	}

	void Print() const
	{
		_ht.MapPrint();
	}

	iterator find(const K& key)
	{
		return _ht.Find(key);
	}

	V& operator[](const K& key)
	{
		pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
		return ret.first->second;
	}

private:
	//同时这里也要的pair也要加上const
	//这里的K代表存放的数据的类型,pair<const K,V>代表节点存放的数据的类型
	hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT> _ht;
};

以上就是利用哈希表封装unordered_map以及unordered
_set的内容啦,因为unordered_map和unordered的接口太多了,这里只是把插入接口和迭代器封装出来,其它的接口就不一一封装了,感兴趣的老铁可以自己尝试一下封装其它有用的接口哟。以上就是今天想要跟大家分享的内容咯,你学会了吗?如果你感觉到有所收获,可以点点小心心,点点关注哦,后期还会持续更新C++的相关知识哦,我们下期见!!!!!!!!!!!

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

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

相关文章

扬帆配资:首个国家层面电力现货市场 建设规则出炉

9月18日&#xff0c;国家发改委、国家动力局发布了已于近日印发的《电力现货商场底子规则&#xff08;试行&#xff09;》&#xff08;下称《规则》&#xff09;&#xff0c;作为国家层面的首个电力现货商场制作规则文件&#xff0c;推进构建全国一致电力商场系统。 扬帆配资&…

VB过程的递归调用,辗转相除法求最大公约数

VB过程的递归调用&#xff0c;辗转相除法求最大公约数 过程的递归调用&#xff0c;辗转相除法求最大公约数 Private Function gys(ByVal m%, ByVal n%) As IntegerDim r%r m Mod n m大或者n大都无所谓&#xff0c;这个不影响计算&#xff0c;由于辗转相除法的算法&#xff0c…

高阶数据结构——图

图 图的基本概念 图的基本概念 图是由顶点集合和边的集合组成的一种数据结构&#xff0c;记作 G ( V , E ) G(V, E)G(V,E) 。 有向图和无向图&#xff1a; 在有向图中&#xff0c;顶点对 < x , y >是有序的&#xff0c;顶点对 < x , y > 称为顶点 x 到顶点 y 的…

Weblogic反序列化漏洞(CVE-2018-2628/CVE-2023-21839复现)

内容目录 Weblogic反序列化漏洞(CVE-2018-2628/CVE-2023-21839)weblogic中间件CVE-2018-2628漏洞描述影响版本漏洞复现修复方案 CVE-2023-21839漏洞描述影响版本漏洞复现修复方案 Weblogic反序列化漏洞(CVE-2018-2628/CVE-2023-21839) weblogic中间件 WebLogic是美国Oracle公司…

Day 00 python基础认识与软件安装

1、基础认识 首先&#xff0c;我们先来区分、了解一些知识点 编程&#xff0c;编程语言 编程&#xff1a;用代码写一个程序 编程语言&#xff1a;用那种语法规则编写程序 &#xff08;人与计算机之间进行交流的工具&#xff1a;c、c、java、python、php、go……&am…

win系统环境搭建(四)——Windows安装mysql8压缩包版本

windows环境搭建专栏&#x1f517;点击跳转 win系统环境搭建&#xff08;四&#xff09;——Windows安装mysql8压缩包版本 本系列windows环境搭建开始讲解如何给win系统搭建环境&#xff0c;本人所用系统是腾讯云服务器的Windows Server 2022&#xff0c;你可以理解成就是你用…

openGauss学习笔记-74 openGauss 数据库管理-创建和管理视图

文章目录 openGauss学习笔记-74 openGauss 数据库管理-创建和管理视图74.1 背景信息74.2 管理视图74.2.1 创建视图74.2.2 查询视图74.2.3 查看某视图的具体信息74.2.4 删除视图 openGauss学习笔记-74 openGauss 数据库管理-创建和管理视图 74.1 背景信息 当用户对数据库中的一…

64位Ubuntu20.04.5 LTS系统安装32位运行库

背景&#xff1a; 在ubutu&#xff08;版本为20.04.5 LTS&#xff09;中运行./arm-none-linux-gnueabi-gcc -v 后提示“no such device”。 经多方查证&#xff0c;是ubutu的版本是64位的&#xff0c;而需要运行的编译工具链是32位的&#xff0c;因此会不兼容。 解决方法就是在…

ScheduledThreadPoolExecutor源码分析-延时线程池是如何实现延时执行的

ScheduledThreadPoolExecutor 线程池可以实现任务延时执行&#xff0c;那么它是怎么实现的呢&#xff1f;下面笔者进行详细分析 先看看它是怎么使用的 目录 1、延时执行使用 2、源码分析 2.1、ScheduledThreadPoolExecutor 初始化分析 2.2、ScheduledThreadPoolExecutor 执…

java项目之咖啡馆管理系统ssm+jsp

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的咖啡馆管理系统。技术交流和部署相关看文章末尾&#xff01; 开发环境&#xff1a; 后端&#xff1a; 开发语言&#xff1a;Java 框架&am…

Netty2

文章目录 Netty2Netty入站与出站机制Netty的handler链的调用机制 Netty2 Netty入站与出站机制 基本说明&#xff1a; 1&#xff09;netty的组件设计&#xff1a;Netty的主要组件有Channel&#xff0c;EventLoop&#xff0c;ChannelFuture&#xff0c;ChannelHandler&#xff…

[golang gui]fyne框架代码示例

1、下载GO Go语言中文网 golang安装包 - 阿里镜像站(镜像站使用方法&#xff1a;查找最新非rc版本的golang安装包) golang安装包 - 中科大镜像站 go二进制文件下载 - 南京大学开源镜像站 Go语言官网(Google中国) Go语言官网(Go团队) 截至目前&#xff08;2023年9月17日&#x…

中秋猜灯谜小游戏

中秋猜灯谜小游戏是一个基于HTML制作的互动游戏&#xff0c;旨在增添中秋节的欢乐氛围&#xff0c;通过猜灯谜来娱乐和挑战玩家。 目录 前言简介游戏规则 制作过程HTML 结构CSS 样式JavaScript 交互 功能实现题目和答案的存储游戏逻辑设计 前言 简介 游戏开始时&#xff0c;玩…

SpringBoot Admin监控平台《二》基础报警设置

一、前置准备 首先搭建监控一个平台和连个客户端&#xff0c;搭建流程见SpringBoot Admin监控平台《一》平台搭建及基础介绍 &#xff0c;搭建完毕之后&#xff0c;启动各个项目&#xff0c;监控平台的界面如下所示&#xff1a; 二、邮件报警 2.1.邮箱授权码获取 授权码主要…

5.5V-65V Vin同步降压控制器,具有线路前馈SCT82630DHKR

描述&#xff1a; SCT82630是一款65V电压模式控制同步降压控制器&#xff0c;具有线路前馈。40ns受控高压侧MOSFET的最小导通时间支持高转换比&#xff0c;实现从48V输入到低压轨的直接降压转换&#xff0c;降低了系统复杂性和解决方案成本。如果需要&#xff0c;在低至6V的输…

天猫全店商品采集教程,天猫店铺所有商品接口(详解天猫店铺所有商品数据采集步骤方法和代码示例)

随着电商行业的快速发展&#xff0c;天猫已成为国内的电商平台之一&#xff0c;拥有着海量的商品资源。对于一些需要大量商品数据的商家或者需求方来说&#xff0c;天猫全店采集是非常必要的。本文将详细介绍天猫全店采集的步骤和技巧&#xff0c;帮助大家更好地完成数据采集任…

使用Visual Leak Detector排查内存泄漏问题

目录 1、VLD工具概述 2、下载并安装VLD 2.1、下载VLD 2.2、安装VLD 3、VLD安装目录及文件说明 3.1、安装目录及文件说明 3.2、关于32位和64位版本的详细说明 4、在工程中引入VLD 5、内存泄漏检测实例讲解 5.1、程序启动报错 5.2、启动调试&#xff0c;查看内存泄漏报…

二维码生成器

二维码生成器 二维码生成器_二维码在线制作_应用方案提供商_互联二维码 使用方式 先知道自己电脑端口 然后运行你要生成页面 拼接自己的端口和页面路径

四川天蝶电子商务有限公司正规吗?

近年来&#xff0c;随着短视频平台的兴起&#xff0c;抖音成为了中国最受欢迎的社交媒体之一。许多企业看到了抖音带货的巨大商机&#xff0c;纷纷涌入这个领域。然而&#xff0c;一些不法分子也乘机滋生&#xff0c;伪装成合法的商家&#xff0c;进行各种欺诈行为。所以&#…

这些提高摸鱼效率的自动化测试技巧,提高打工人幸福感~

最近有许多小伙伴都在吐槽打工好难。 每天都是执行许多重复的任务 例如阅读新闻、发邮件、查看天气、打开书签、清理文件夹等等&#xff0c; 使用自动化脚本&#xff0c;就无需手动一次又一次地完成这些任务&#xff0c; 非常方便啊有木有&#xff1f;&#xff01; 今天就…