【C++进阶之路】封装unordered_set 、unordered_map

文章目录

  • 前言
  • 一、基本框架
    • 1.HashTable
    • 2.unordered_set
    • 3.unordered_map
  • 二、基本实现
    • 1.类型的泛化
    • 2.仿函数
    • 3.迭代器
      • 3.1基本框架
      • 3.2 ++
      • 3.3 构造函数
      • 3.3 完整代码
    • 4.insert
    • 5. []
  • 总结

前言

 之前博主写过哈希表的原理的文章,今天我们就一起学习一下库里面的unordered_set与unordered_map,并利用之前的框架搭建出来一个简单的unordered_set与unordered_map。

一、基本框架

1.HashTable

	//KeyOfT实现类型的泛化,KeyOfF实现不同类型的映射函数的实现方式的泛化。template<class K, class T,class KeyOfT,class KeyOfF = KeyOff<K>>class HashTable{template<class K, class T, class Ref, class Ptr,class KeyOfT,class KeyOfF>friend struct HashIterator;//友元模板的迭代器的声明。typedef HashNode<T> Node;//结点的声明。public:typedef HashIterator<K, T,T&,T*, KeyOfT,KeyOfF> iterator;//普通迭代器typedef HashIterator<K, T,const T&, const T*, KeyOfT,KeyOfF> const_iterator;//const迭代器//迭代器iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//构造函数HashTable(size_t n = 17);//析构函数~HashTable();//插入函数pair<iterator,bool> insert(const T& data);//删除函数bool erase(const K& key);private:vector<Node*> _table;size_t _n = 0;//有效结点的个数};
}

2.unordered_set

template<class K>class unordered_set{typedef K key_type;typedef K val_type;struct SetOfT{//取Set里面的key};typedef HashTable<K, K, SetOfT> hash_type;typedef typename hash_type::const_iterator iterator;typedef iterator const_iterator;public:const_iterator begin() const;const_iterator end() const;bool insert(const key_type& key);bool erase(const key_type& key);private:hash_type _hash;};

3.unordered_map

	template<class K,class V>class unordered_map{typedef K key_type;typedef pair<const K, V> val_type;typedef V map_type;struct MapOfT{key_type operator()(const val_type& x){return x.first;}};typedef HashTable<key_type, val_type, MapOfT> hash_type;typedef typename hash_type::iterator iterator;typedef typename hash_type::const_iterator const_iterator;public:iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;bool insert(const pair<K, V>& data);map_type& operator[](const key_type& key );bool erase(const K& key);private:hash_type _hash;};

二、基本实现

1.类型的泛化

由于unordered_map与unordered_set的底层用的都是哈希表,但是:

  • unordered_set的val_type是key
  • unordered_map的val_type是pair<key,value>

哈希表的val_type为pair<key,value>,因此需要对哈希表再泛化,将哈希表的val_type改为T,实现泛化,并且对Node结点的结构也要进行修改,将其变为:

    template<class T>struct HashNode{HashNode(const T& val = T()):_data(val){}T _data;HashNode* _next = nullptr;};

插入接口也变为:

    bool insert(const T& data);//此处是返回值是未实现迭代器之前的接口函数。

如此,unordered_map与unordered_set 通过传参进行控制T的类型,进而实现泛化。

2.仿函数

 但是由于对哈希表来说我们需要取出key来进行哈希映射,由于:

  • unordered_set传入的模板参数T就是key
  • unordered_map传入的模板参数T是pair<key,value>

因此为了取出unordered_map的key,需要在unordered_map与unordered_set传参时实现一个能取出key的功能,也就是仿函数。

因此

  • HashTable的模板参数:
								//取出T里面的keytemplate<class K, class T,class KeyOfT,class KeyOfF = KeyOff<K>>class HashTable;
  • unordered_set的仿函数
	struct SetOfT{key_type operator()(const val_type& key){return key;}};
  • unordered_map的仿函数
	struct MapOfT{key_type operator()(const val_type& x){return x.first;}};

3.迭代器

  • 说明:迭代器为正向迭代器,库里面不存在反向迭代器,因为单链表不支持倒着走,并且没有意义。

3.1基本框架

    template<class K, class T, class KeyOfK,class KeyOfF>class HashTable;template<class K, class T,class Ref, class Ptr, class KeyOfT ,class KeyOfF>struct HashIterator{typedef HashNode<T> Node;typedef HashIterator<K, T,Ref,Ptr, KeyOfT,KeyOfF> self;typedef HashTable<K, T, KeyOfT, KeyOfF> Table;typedef HashIterator<K, T, T&, T*, KeyOfT,KeyOfF> iterator;HashIterator(Node* node,const Table* table):_node(node),_hash(table){}HashIterator(const iterator& it):_node(it._node),_hash(it._hash){}self operator ++();Ref operator*();Ptr operator->();bool operator != (const self& it);bool operator == (const self& it);private:Node* _node;const Table* _hash;//哈希表的指针};

 看到这样的结构,其实会感觉奇怪,因为里面竟然多了一个指针,深入思考不难理解,其实我们在遍历完一条链上的数据时,需要跳到下一条链上,这时该如何跳呢?是不是该用到哈希表?这里的指针就是哈希表,用来帮助我们寻找下一条链。

因此:这里的HashTable的前置声明,和在哈希表中出现的友元模板的声明就可以解释通了。

3.2 ++

  • 核心逻辑:如果当前链,当前结点的下一个元素,不为空,跳到下一个结点,为空,则找到哈希表中不为空的下一条链的第一个结点。
 self operator ++(){Node* cur = _node;assert(cur);if (cur->_next){_node = cur->_next;return *this;}else{KeyOfF getkey;KeyOfT kot;K key = kot(cur->_data);int innode = getkey(key) % _hash->_table.size();++innode;while (innode < (int)_hash->_table.size()){if (_hash->_table[innode]){_node = _hash->_table[innode];return *this;}++innode;}//空。_node = nullptr;return *this;}}

3.3 构造函数

  • 细节1
     HashIterator(Node* node,const Table* table):_node(node),_hash(table){}
  • 这里的Table* 前面加的const修饰不是没有原因的,因为在哈希表中的const迭代器:
const_iterator end() const;

 这里的const是修饰的this指针,也就是 Table*, 但是由于是被const修饰表明其指向的内容不可以被修改,因此如果Table* 不加const,表明权限放大,会报错的,因此,这里为了实现const迭代器需要在Table* 前const。

  • 细节2
	//哈希表的定义typedef HashIterator<K, T,T&,T*, KeyOfT,KeyOfF> iterator;typedef HashIterator<K, T,const T&, const T*, KeyOfT\,KeyOfF> const_iterator;//迭代器的定义typedef HashIterator<K, T, T&, T*, KeyOfT,KeyOfF> iterator;HashIterator(const iterator& it):_node(it._node),_hash(it._hash){}
  • 迭代器的类是struct,默认成员公有可以突破类域进行访问。
  • 当迭代器为const迭代器时,这里的iterator为const迭代器,可以实现不同类型之间进行转换的效果。
  • 迭代器的参数多一个T,看似没有用,其实在这里定义iterater时有奇效。

3.3 完整代码

    template<class K, class T, class KeyOfK,class KeyOfF>class HashTable;template<class K, class T,class Ref, class Ptr, \class KeyOfT ,class KeyOfF>struct HashIterator{typedef HashNode<T> Node;typedef HashIterator<K, T,Ref,Ptr, KeyOfT,KeyOfF> self;typedef HashTable<K, T, KeyOfT, KeyOfF> Table;typedef HashIterator<K, T, T&, T*, KeyOfT,KeyOfF> iterator;HashIterator(Node* node,const Table* table):_node(node),_hash(table){}HashIterator(const iterator& it):_node(it._node),_hash(it._hash){}self operator ++(){Node* cur = _node;assert(cur);if (cur->_next){_node = cur->_next;return *this;}else{KeyOfF getkey;KeyOfT kot;K key = kot(cur->_data);int innode = getkey(key) % _hash->_table.size();++innode;while (innode < (int)_hash->_table.size()){if (_hash->_table[innode]){_node = _hash->_table[innode];return *this;}++innode;}//空。_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;}private:Node* _node;const Table* _hash;};

4.insert

 在实现了之前的仿函数与迭代器我们的insert的最终版本即可出来了:

  pair<iterator,bool> insert(const T& data){KeyOfF getkey;KeyOfT kot;size_t loadfactor = _n * 10 / _table.size();if (loadfactor >= 10){//换新表size_t newsize = 2 * _table.size();HashTable<K, T,KeyOfT> new_table(newsize);//只能移数据for (int i = 0; i < (int)_table.size(); i++){Node* node = _table[i];int innode = i % newsize;while (node){node->_next = new_table._table[innode];new_table._table[innode] = node;node = node->_next;}}//交换数据swap(_table, new_table._table);}int innode = getkey(kot(data)) % _table.size();Node* head = _table[innode];while (head){if (head->_data == data)//排除重复数据{return make_pair(iterator(head,this),false);}head = head->_next;}Node* newnode = new(Node)(data);//指向头结点,更新头结点。newnode->_next = _table[innode];_table[innode] = newnode;_n++;return make_pair(iterator(newnode,this),true);}
  • unordered_set的实现
	bool insert(const key_type& key){return _hash.insert(key).second;}//返回迭代器的版本const_iterator insert(const key_type& key){pair<typename hash_type::iterator, bool> x = \_hash.insert(key);//利用之前模板写的构造函数,进行不同类型之间的转化。pair<const_iterator, bool> data(x.first, x.second);return data.first;}
  • unordered_map的实现
	bool insert(const pair<K, V>& data){return _hash.insert(data).second;}

5. []

  • 只有map才能实现
	map_type& operator[](const key_type& key ){pair<iterator, bool> x = _hash.insert(make_pair(key,\map_type()));return x.first->second;}

总结

 有之前的封装map与set的经验,想必这里按部就班一步一步进行实现,一步一步进行纠错是不难实现的,最后如果觉得文章有所帮助的话,不妨点个赞鼓励一下吧!

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

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

相关文章

【数据结构】【C++】哈希表的模拟实现(哈希桶)

【数据结构】&&【C】哈希表的模拟实现(哈希桶&#xff09; 一.哈希桶概念二.哈希桶模拟实现①.哈希结点的定义②.数据类型适配③.哈希表的插入④.哈希表的查找⑤.哈希表的删除⑥.哈希表的析构 三.完整代码 一.哈希桶概念 哈希桶这种形式的方法本质上是开散列法&#x…

天选之子Linux是如何发展起来的?为何对全球IT行业的影响如此之大?

天选之子Linux是如何发展起来的&#xff1f;为何对全球IT行业的影响如此之大&#xff1f; 前言一、UNIX发展史二、Linux发展历史三、开源四、官网五、 企业应用现状六、发行版本 前言 上面这副图是博主历时半小时完成的&#xff0c;给出了Linxu的一些发展背景。球球给位看官老…

Java获取给定月份的前N个月份和前N个季度

描述&#xff1a; 在项目开发过程中&#xff0c;遇到这样一个需求&#xff0c;即&#xff1a;给定某一月份&#xff0c;得到该月份前面的几个月份以及前面的几个季度。例如&#xff1a;给定2023-09&#xff0c;获取该月份前面的前3个月&#xff0c;即2023-08、2023-07、2023-0…

机器学习算法基础--层次聚类法

文章目录 1.层次聚类法原理简介2.层次聚类法基础算法演示2.1.Single-linkage的计算方法演示2.2.Complete-linkage的计算方法演示2.3.Group-average的计算方法演示 3.层次聚类法拓展算法介绍3.1.质心法原理介绍3.2.基于中点的质心法3.3.Ward方法 4.层次聚类法应用实战4.1.层次聚…

Linux ❀ 进程出现process information unavailable时的消除方法

[rootmaster ~]# jps 74963 -- process information unavailable 78678 Jps [rootmaster ~]# cd /tmp/hsperfdata_redhat/ # redhat为启动该java进程的用户ps -ef | grep $pid查找 [rootmaster hsperfdata_redhat]# ll total 32 -rw------- 1 redhat redhat 32768 Sep 27 15:…

“宣布暂停加息!通胀和利率前景如何?“

美联储在 9 月会议上再次暂停加息&#xff0c;但为 2023 年至少加息一次敞开了大门。 然而&#xff0c;现在投资者最重要的问题不是利率会涨到多高&#xff0c;而是高利率会持续多久&#xff1f; 答案将取决于数据。当然&#xff0c;最重要的指标是通货膨胀。截至 8 月 31 日…

同步、异步

何为同步、异步&#xff1f; 同步任务&#xff08;synchronous&#xff09; 同步任务指的是&#xff0c;在主线程上排队执行的任务&#xff0c;只有前一个任务执行完毕&#xff0c;才能执行后一个任务&#xff1b;同步任务进栈顺序&#xff1a;先进后出&#xff0c;后进先出&…

rust生命期

一、生命期是什么 生命期&#xff0c;又叫生存期&#xff0c;就是变量的有效期。 实例1 {let r;{let x 5;r &x;}println!("r: {}", r); }编译错误&#xff0c;原因是r所引用的值已经被释放。 上图中的绿色范围’a表示r的生命期&#xff0c;蓝色范围’b表示…

Java进阶篇--网络编程

​​​​​​​ 目录 计算机网络体系结构 什么是网络协议&#xff1f; 为什么要对网络协议分层&#xff1f; 网络通信协议 TCP/IP 协议族 应用层 运输层 网络层 数据链路层 物理层 TCP/IP 协议族 TCP的三次握手四次挥手 TCP报文的头部结构 三次握手 四次挥手 …

整理mongodb文档:副本集二

个人博客 整理mongodb文档:副本集二 个人博客&#xff0c;求推荐&#xff0c;本片内容较为乱 文章概叙 本文章主要讲在MongoDB的副本集中的一些注意点&#xff0c;主要是如何对seconadry进行数据操作&#xff0c;以及对更新数据的一些介绍 查看当前节点 上一集讲了关于搭…

Windows下安装MySQL8详细教程

Windows下安装MySQL8详细教程 因为需要在Windows下安装MySQL8的数据库&#xff0c;做一个临时数据库环境。 1.准备软件 使用社区版本&#xff0c;下载地址如下&#xff1a; https://dev.mysql.com/downloads/mysql/ 使用8.0.16版本&#xff0c;需要在归档中查找 选择版本&a…

《Upload-Labs》01. Pass 1~13

Upload-Labs 索引前言Pass-01题解 Pass-02题解总结 Pass-03题解总结 Pass-04题解 Pass-05题解总结 Pass-06题解总结 Pass-07题解总结 Pass-08题解总结 Pass-09题解 Pass-10题解 Pass-11题解 Pass-12题解总结 Pass-13题解 靶场部署在 VMware - Win7。 靶场地址&#xff1a;https…

【数据结构】队列和栈

大家中秋节快乐&#xff0c;玩了好几天没有学习&#xff0c;今天分享的是栈以及队列的相关知识&#xff0c;以及栈和队列相关的面试题 1.栈 1.1栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作…

嵌入式学习笔记(35)外部中断

6.9.1什么是外部中断 (1)内部中断就是指中断源来自于SoC内部&#xff08;一般是内部外设&#xff09;&#xff0c;譬如串口、定时器等部件产生的中断&#xff1b;外部中断是SoC外部的设备&#xff0c;通过外部中断对应的GPIO引脚产生的中断。 (2)按键在SoC中就使用了外部中断…

【CMU15-445 Part-14】Query Planning Optimization I

Part14-Query Planning & Optimization I SQL is Declarative&#xff0c;只告诉想要什么而不需要说怎么做。 IBM System R是第一个实现query optimizer查询优化器的系统 Heuristics / Rules 条件触发 静态规则&#xff0c;重写query来remove 低效或者愚蠢的东西&#xf…

No156.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

驱动开发:STM32F7控制AD5663模拟量输出

AD5663是ADI公司的一款DAC模块&#xff0c;用以实现两路模拟量信号输出。该芯片通过SPI通信来驱动。下面讲解使用STM32F7主控芯片来控制AD5663模拟量输出的流程。 配置STM32F7 SPI通信管脚 STM32CubeMX生成SPI驱动代码 /* SPI3 init function */ void MX_SPI3_Init(void) {/*…

阿里巴巴OceanBase介绍

前言 官网地址&#xff1a;https://www.oceanbase.com/ OceanBase是由蚂蚁集团完全自主研发的国产原生分布式数据库&#xff0c;始创于2010年。是全球唯一在 TPC-C 和 TPC-H 测试上都刷新了世界纪录的国产原生分布式数据库。 2010年&#xff0c;创始人阳振坤加入阿里巴巴&…

UE5屏幕适配

一、本程序设计发布在手机上&#xff0c;首先确定屏幕的设计分辨率&#xff0c;这里我们选择iphone6s&#xff0c;750x1334。 二、设置DPI Scale为1.0的比例&#xff0c;点击齿轮标志 因为我们这个程序是手机竖屏使用的&#xff0c;所以DPI Scale Rule选择Shortest Side&#…

博弈论中静态博弈经典场景案例

博弈论中静态博弈经典场景案例 1、齐威王田忌赛马 田忌赛马是中国家喻户晓的故事&#xff0c;故事讲述的是齐国大将田忌的谋士孙膑如何运用计谋帮助田忌在与齐威王赛马时以弱胜强的故事&#xff0c;这个故事其实本质也是一个博弈的过程。     齐威王要和田忌赛马&#xff…