STL库 —— unordered_set与unordered_map的封装

这里要对 unordered_set 与 unordered_map 进行封装,封装时使用的是上一篇中学的 HashBucket 。不仅要完成封装,同时要写入迭代器。

一、HashBucket 的修改

1.1 节点的修改 T

首先来认识一下使用 unordered_set 和 ordered_map 时的区别:

unordered_set 存储唯一的键值。你只需要传入要插入的值。

#include <unordered_set>
#include <iostream>int main() {std::unordered_set<int> mySet;mySet.insert(10);mySet.insert(20);for (const auto& elem : mySet) {std::cout << elem << " ";}return 0;
}

unordered_map 存储键值对。你需要传入键和值。 

#include <unordered_map>
#include <iostream>int main() {std::unordered_map<int, std::string> myMap;myMap.insert({1, "one"});myMap.insert({2, "two"});for (const auto& pair : myMap) {std::cout << pair.first << ": " << pair.second << " ";}return 0;
}

因为 unordered_set 只存储值,而 unordered_map 存储键值对并提供键到值的映射,所以当它们底层使用同一容器进行封装时,要求该容器可以兼容这两种数据类型

template<class T>//
struct HashNode
{T _data;//HashNode* _next;HashNode(const T& data) :_data(data), _next(nullptr)//{}
};

节点模板的修改是为了满足两者的需要,满足双方的供求

1.2 类的修改 KeyOfT

因为 unordered_map 传入的是键值对,但是并不知道键值对的键值是什么,所以在定义类模板时,会多传入一个参数 KeyOfT ,作为键值。但肯定有很多人会疑惑,传入的本身就是键值对了,  pair<K, V> 中的 K 不就是键值对的键值吗?这么做不是多此一举吗?

事实上,传入 KeyOfT 而不是直接使用键 Key 的主要原因是为了提高代码的灵活性和通用性,尤其是在存储复杂对象时。以下是一个具体的例子来说明这种情况:

示例场景

假设我们有一个存储复杂对象的哈希表,这些对象有多个属性,其中一个属性作为键。

struct Employee 
{int employee_id;std::string name;std::string department;Employee(int id, const std::string& n, const std::string& d) : employee_id(id), name(n), department(d) {}
};
KeyOfT 提取键的机制

我们需要一种机制来从 Employee 对象中提取 employee_id 作为键。为此,我们定义一个 KeyOfEmployee 函数对象。

struct KeyOfEmployee
{int operator()(const Employee& emp) const{return emp.employee_id;}
};
键值对
  • employee_id(例如,1)
  • Employee 对象(例如,Employee(1, "Alice", "HR")
传入示例
bool Insert(const T& obj)
{K key = KeyOfT()(obj);size_t index = Hash()(key) % _bucket.size();_bucket[index] = new T(obj);return true;
}
HashBucket<int, Employee, KeyOfEmployee, HashFunc> hb;
hb.Insert(Employee(1, "Alice", "HR"));
hb.Insert(Employee(2, "Bob", "IT"));

由上面的案例就不难看出,传入的对象不一定是键值对,有可能是自定义的类对象,此时就只需要添加 KeyOfT 的模板,就可以在类内部找到键值。

类模板添加KeyOfT

template<class K, class T, class KeyOfT, class Hash = Hashfunc<K>>
class HashBucket
{
private:vector<Node*> _bucket;size_t _n;
};

unordered_set 来说,KeyOfT 的方式有些多此一举。unordered_set 本质上是一个存储唯一元素的集合,没有键值对的概念。所以这里的修改是为了将就 unordered_map

    //unordered_settemplate<class K>class unordered_set{struct SetKeyOfT{const K& operator()(const K& Key){return Key;}};public:private:HashBucket<K, K, SetKeyOfT> _ht;//注意传参要对应HashBucket};
    //unordered_maptemplate<class K, class V>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:private:HashBucket<K, pair<K, V>, MapKeyOfT> _ht;//注意传参要对应HashBucket};

1.3 类的修改 HashFunc

下面先来看一下这三个类与其对应的类模板:

template<class K>
class unordered_set	
{
private:HashBucket<K, K, SetKeyOfT> _ht;};template<class K, class V>
class unordered_map
{
private:HashBucket<K, pair<K, V>, MapKeyOfT> _ht;
};template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
class HashBucket
{};

 在使用时,用户是直接使用 unordered_set 与 unordered_map ,所以应该希望在 unordered_set 与 unordered_map 层有一个默认的 HashFunc ,这样用户不仅可以自定义,也可以使用默认的 HashFunc ,提高了代码的灵活性,而在 HashBucket 层,只需要按照上层的指令来即可,所以就需要把默认的 HashFunc 提前到上层。

template<class K, class Hash = HashFunc<K>>
class unordered_set
{
private:HashBucket<K, K, SetKeyOfT, Hash> _ht;
};template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
private:HashBucket<K, pair<K, V>, MapKeyOfT, Hash> _ht;
};template<class K, class T, class KeyOfT, class Hash>
class HashBucket;
{};

二、迭代器

2.1 定义迭代器成员

首先,迭代器要知道自己的位置,这就需要定义一个节点指针,另外,当在哈希桶中使用自增直到遍历完 vector 的某一节点时,因为迭代器另一个单独的类,所以需要让迭代器直到自己所处的哈希桶的结构,才好寻找下一个存在值的节点,这就需要定义一个哈希桶的指针

这样不仅得到了迭代器的成员,也得到了迭代器的构造函数。 

template<class K, class T, class KeyOfT, class Hash>
struct __HtIterator
{typedef HashNode<T> Node;Node* _node;HashBucket<K, T, KeyOfT, Hash>* _pht;__HtIterator(Node* node, HashBucket<K, T, KeyOfT, Hash>* pht):_node(node), _pht(pht){}
};

问题1:

因为迭代器中存在了哈希桶的指针来指向哈希桶,那么当遍历哈希桶的数组时,不可避免地会使用到哈希桶的 _bucket ,但是这又是个私有成员,如何解决呢?

可以使用友元来帮助解决(省略不必要的部分):

template<class K, class T, class KeyOfT, class Hash>
class HashBucket
{
public:template<class K, class T, class KeyOfT, class Hash>friend struct __HtIterator;//友元
};

问题2:

在迭代器中,存在了哈希桶;在哈希桶中,又用到了迭代器。那么又有一个问题,编译器访问某一个的时候,必然会访问不到另一个,这是代码顺序的问题,这个问题怎么解决呢?和函数声明类似,可以在迭代器前加上哈希桶的类声明:

template<class K, class T, class KeyOfT, class Hash>//类声明
class HashBucket;template<class K, class T, class KeyOfT, class Hash>
struct __HtIterator
{};

2.2 begin 与 end 函数

2.2.1 begin 函数

如下图, begin 返回的是哈希桶第一个存值的迭代器,所以只需要挨个遍历即可,但是返回节点的地址容易,那么哈希桶的地址怎么办呢?这可是在哈希桶类中返回自己的地址,应该怎么办?

return iterator(cur, this);

其实答案很久之前就已经学过了,this指针代表的不就是本身吗?

若没有找到,可以直接返回 end ,下面只需要对 end 继续做优化即可。

template<class K, class T, class KeyOfT, class Hash>
class HashBucket
{typedef HashNode<T> Node;
public:typedef __HtIterator<K, T, KeyOfT, Hash> iterator;iterator begin(){for (size_t i = 0; i < _bucket.size(); i++){Node* cur = _bucket[i];if (cur){return iterator(cur, this);}}return end();}
private:vector<Node*> _bucket;size_t _n;
};

2.2.2 end 函数

end 返回的是最后一个存值的节点的下一个位置,所以直接可以使用空指针来构造:

	iterator end(){return iterator(nullptr, this);}

2.3 operator重载

2.3.1 自增的重载

这里有两种情况:

1.当前桶还为遍历完,那么迭代器可以直接指向当前节点的下一个。

2.当前桶已经遍历完,那么就需要遍历整个 vector ,直到找到下一个不为空的桶。

template<class K, class T, class KeyOfT, class Hash>
struct __HtIterator
{typedef HashNode<T> Node;typedef __HtIterator<K, T, KeyOfT, Hash> Self;Node* _node;HashBucket<K, T, KeyOfT, Hash>* _pht;__HtIterator(Node* node, HashBucket<K, T, KeyOfT, Hash>* pht):_node(node), _pht(pht){}Self& operator++(){if (_node->_next){//当前桶未遍历完,取桶的下一个节点_node = _node->_next;}else{//当前桶已遍历完,找下一个不为空的桶KeyOfT kot;Hash hs;size_t i = hs(kot(_node->_data)) % _pht->_bucket.size();++i;for (; i < _pht->_bucket.size(); i++){if (_pht->_bucket[i])break;}if (i == _pht->_bucket.size()) _node = nullptr;//没找到下一个不为空的桶else _node = _pht->_bucket[i];//找到了下一个不为空的桶}return *this;}
};

2.3.2 解引用和不等于的重载

这两个比较简单,就直接放在一起上代码了:

    T& operator*(){return _node->_data;}bool operator!=(const Self& s){return _node != s._node;}

2.4 完整代码

下面就可以来测试一下迭代器了,测试之前先看一下完整的代码:

2.4.1 完整的迭代器

template<class K, class T, class KeyOfT, class Hash>//
class HashBucket;template<class K, class T, class KeyOfT, class Hash>
struct __HtIterator
{typedef HashNode<T> Node;typedef __HtIterator<K, T, KeyOfT, Hash> Self;Node* _node;HashBucket<K, T, KeyOfT, Hash>* _pht;__HtIterator(Node* node, HashBucket<K, T, KeyOfT, Hash>* pht):_node(node), _pht(pht){}Self& operator++(){if (_node->_next){//当前桶未遍历完,取桶的下一个节点_node = _node->_next;}else{//当前桶已遍历完,找下一个不为空的桶KeyOfT kot;Hash hs;size_t i = hs(kot(_node->_data)) % _pht->_bucket.size();++i;for (; i < _pht->_bucket.size(); i++){if (_pht->_bucket[i])break;}if (i == _pht->_bucket.size()) _node = nullptr;//没找到下一个不为空的桶else _node = _pht->_bucket[i];//找到了下一个不为空的桶}return *this;}T& operator*(){return _node->_data;}bool operator!=(const Self& s){return _node != s._node;}
};

2.4.2 完整的HashBucket

template<class K, class T, class KeyOfT, class Hash>//
class HashBucket
{typedef HashNode<T> Node;//
public:template<class K, class T, class KeyOfT, class Hash>friend struct __HtIterator;typedef __HtIterator<K, T, KeyOfT, Hash> iterator;iterator begin(){for (size_t i = 0; i < _bucket.size(); i++){Node* cur = _bucket[i];if (cur){return iterator(cur, this);}}return end();}iterator end(){return iterator(nullptr, this);}HashBucket(){_bucket.resize(10, nullptr);_n = 0;}~HashBucket(){for (size_t i = 0; i < _bucket.size(); i++){Node* cur = _bucket[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_bucket[i] = nullptr;}}bool Insert(const T& data){KeyOfT kot;if (Find(kot(data))) return false;//Find(kv.first)->Find(kot(data))Hash hs;if (_n == _bucket.size()){vector<Node*> newBucket(_bucket.size() * 2, nullptr);for (size_t i = 0; i < _bucket.size(); i++){Node* cur = _bucket[i];while (cur){Node* next = cur->_next;size_t index = hs(kot(cur->_data)) % newBucket.size();//cur->_next = newBucket[index];newBucket[index] = cur;cur = next;}_bucket[i] = nullptr;}_bucket.swap(newBucket);}size_t index = hs(kot(data)) % _bucket.size();Node* newnode = new Node(data);newnode->_next = _bucket[index];_bucket[index] = newnode;++_n;return true;}bool Erase(const K& Key){KeyOfT kot;Hash hs;size_t index = hs(kot(Key)) % _bucket.size();Node* cur = _bucket[index];Node* prev = nullptr;while (cur){if (kot(cur->_data) == Key){//删除的是第一个节点if (prev == nullptr){_bucket[index] = cur->_next;}else{prev->_next = cur->_next;}delete cur;return true;}else{prev = cur;cur = cur->_next;}}return false;}Node* Find(const K& Key){KeyOfT kot;if (_bucket.empty()) return nullptr;Hash hs;size_t index = hs(Key) % _bucket.size();Node* cur = _bucket[index];while (cur){if (kot(cur->_data) == Key)/**/return cur;else cur = cur->_next;}return nullptr;}private:vector<Node*> _bucket;size_t _n;
};

三、迭代器的测试

3.1 重命名

迭代器的测试其实就是在 unordered_set 与 ordered_map 中复用 HashBucket 的函数,在两个类中对迭代器进行重命名,注意一定不要错了!

//unordered_set中
typedef typename HashBucket<K, K, SetKeyOfT, Hash>::iterator iterator;
//unordered_map中
typedef typename HashBucket<K, pair<K, V>, MapKeyOfT, Hash>::iterator iterator;

此外,说明一下 typename 在这里的作用:明确指出某个标识符是一个类型,从而避免编译器将其解释为非类型名称。而且,迭代器的重命名要定义在 public 域中。

3.2 unordered_set

    template<class K, class Hash = HashFunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& Key){return Key;}};public:typedef typename HashBucket<K, K, SetKeyOfT, Hash>::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}bool insert(const K& Key){return _ht.Insert(Key);}private:HashBucket<K, K, SetKeyOfT, Hash> _ht;};void Test_unordered_set(){unordered_set<int> s;s.insert(31);s.insert(23);s.insert(19);s.insert(6);s.insert(22);s.insert(37);for (auto e : s){cout << e << endl;}}

3.3 unordered_map

	template<class K, class V, class Hash = HashFunc<K>>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename HashBucket<K, pair<K, V>, MapKeyOfT, Hash>::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}bool insert(const pair<K, V>& kv){return _ht.Insert(kv);}private:HashBucket<K, pair<K, V>, MapKeyOfT, Hash> _ht;};void Test_unordered_map(){unordered_map<int, int> m;m.insert(make_pair(31, 31));m.insert(make_pair(23, 23));m.insert(make_pair(19, 19));m.insert(make_pair(6, 6));m.insert(make_pair(22, 22));m.insert(make_pair(37, 37));for (auto e : m){cout << e.first << ":" << e.second << endl;}cout << endl;}

在 main 函数中进行测试时,可以看到两者都可以跑起来:

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

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

相关文章

深入浅出MySQL事务实现底层原理

重要概念 事务的ACID 原子性&#xff08;Atomicity&#xff09;&#xff1a;即不可分割性&#xff0c;事务中的操作要么全不做&#xff0c;要么全做一致性&#xff08;Consistency&#xff09;&#xff1a;一个事务在执行前后&#xff0c;数据库都必须处于正确的状态&#xf…

RobotFramework测试框架(1)--官网示例

示例 项目 RF官网提供了几个例子 Examples Overview | ROBOT FRAMEWORK Vehicle Insurance App 根据下面的例子可以看到&#xff0c;RF的测试文件&#xff0c;包含 *** Settings ***-用来引入库和资源 *** Variables *** 用来指定变量&#xff0c;在测试用例中可使用${}来…

Java开发大厂面试第17讲:MySQL 的优化方案有哪些?数据库设计、查询优化、索引优化、硬件和配置优化等

性能优化&#xff08;Optimize&#xff09;指的是在保证系统正确性的前提下&#xff0c;能够更快速响应请求的一种手段。而且有些性能问题&#xff0c;比如慢查询等&#xff0c;如果积累到一定的程度或者是遇到急速上升的并发请求之后&#xff0c;会导致严重的后果&#xff0c;…

字母异位词分组-力扣

首先想到的解法是调用上道题写好的 有效的字母异位词 函数&#xff0c;来对strs中的字符串进行两两判断&#xff0c;然后添加到不同的vector&#xff0c;但转眼一想这样写无疑过于麻烦。在想到上提另一种解法排序后&#xff0c;本题也可以采用排序的方法来做&#xff0c;遍历st…

变分自动编码器(VAE)深入理解与总结

本文导航 0 引言1 起源1.1 自编码器的任务定义1.2 自编码器存在的问题1.3 VAE的核心思路 2 VAE的建模过程2.1 VAE的任务定义2.2 真实分布 ϕ \phi ϕ是什么&#xff0c;为什么要逼近这个分布的参数&#xff0c;如何做&#xff1f;2.3 “重参数化&#xff08;Reparameterization…

互联网十万个为什么之 什么是Kubernetes(K8s)?

Kubernetes&#xff08;通常简称为K8s&#xff09;是一款用于自动部署、扩缩和管理容器化应用程序的开源容器编排平台。Kubernetes已发展为现代企业实现敏捷开发、快速迭代、资源优化及灵活扩展的关键技术组件之一。它拥有庞大的开源社区和丰富的生态系统。围绕Kubernetes已经形…

.lib .a .dll库互转

编译 mingw工具&#xff0c;gendef.exe转换dll为a&#xff0c;reimp转换lib为adlltool.exe --dllname python38.dll --def python38.def --output-lib libpython38.adlltool -k -d crypto.lib -l crypto.a 创作不易&#xff0c; 小小的支持一下吧&#xff01;

koa使用ws,scoket.io建立websocket连接,断开重连

1.使用ws建立socket连接&#xff0c;ws兼容性比socket.io要好一些 koa.js const Koa require(koa); // 引入 Koa 框架 const http require(http); // 引入 Node.js 的 http 模块 const { WebSocketServer } require(ws); // 引入 ws 模块中的 WebSocketServer const cors…

QT之常用控件

一个图形化界面当然需要有各种各样的控件&#xff0c;QT也不例外&#xff0c;在QT designer中就有提供各种各样的控件&#xff0c;用以开发图形化界面。 而想使用好一个QT控件&#xff0c;就需要了解这些控件。 QWidget 在QT中&#xff0c;所有控件都继承自 QWidget 类&…

推荐10款优秀的组件库(一)

1.Ant Desgin UI 网址&#xff1a; https://ant-design-mobile.antgroup.com/zh Ant Design - 一套企业级 UI 设计语言和 React 组件库 "Ant Design Mobile"是一个在线的移动端Web体验平台&#xff0c;让你探索移动端Web的体验极限。 添加图片注释&#xff0c;不…

622.设计循环队列

typedef struct {int* a;int head;int tail;int k; } MyCircularQueue;bool myCircularQueueIsEmpty(MyCircularQueue* obj); bool myCircularQueueIsFull(MyCircularQueue* obj);//初始化 MyCircularQueue* myCircularQueueCreate(int k) {MyCircularQueue* obj(MyCircularQue…

CSS3开发实践难点

目录 响应式设计与流体布局动画与交互性能优化CSS预处理器与后处理器CSS-in-JSCSS高级技巧视觉与滤镜效果工具与工作流实验性响应式设计与流体布局 媒体查询(M

.cc和.cpp文件的区别

在C编程中&#xff0c;文件扩展名为.cpp和.cc的文件实际上没有本质的区别&#xff0c;它们都用于存储C源代码。两种扩展名都可以用于编写C程序&#xff0c;并且在大多数情况下&#xff0c;它们可以互换使用。 一般来说&#xff0c;.cpp扩展名是最常见的用于C源代码文件的标准扩…

linux内核调试技巧四:gdb调试+vmlinux

vmlinux是个elf文件&#xff0c;它的符号表中包含了所有内核符号。 注意linux中很多文件是没有后缀的&#xff0c;比如我见到的这个elf文件的文件名是“vmlinux-3.10.62”&#xff0c;没有后缀。 既然是elf文件那就可以用 点击打开链接 里面的方法直接查看符号表。 要想看得…

SW 草图偏移 先预选

因为有些不能用链全部选,可以先框选要偏移的,再点偏移命令

探索python列表处理:偶数筛选的两种方法

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言 二、不使用列表生成式的偶数筛选 1. 读取输入列表 2. 筛选偶数 三、使用列表生…

重学java 46.集合 ① Collection集合

事常与人违&#xff0c;事总在人为 —— 24.5.26 集合 知识导航 1.集合的特点以及作用 2.使用collection接口中的方法 3.使用迭代器迭代集合 4.ArrayList以及LinkedList的使用 5.使用增强for遍历集合 一、单列集合框架的介绍 1.长度可变的容器&#xff1a;集合 2.集合的特点 a.…

每日一问-如何设置VS Code 中 Markdown粘贴图片的位置

VS Code内的markdown编辑器应该算是比较好用的&#xff0c;但是有一个问题一直困扰着我&#xff0c;就是在编辑markdown文件时&#xff0c;粘贴图片的位置问题。默认情况下&#xff0c;VS Code会将粘贴的图片放在markdown文件的同级目录下&#xff0c;这样会导致markdown文件的…

OSPF多区域组网实验(华为)

思科设备参考&#xff1a;OSPF多区域组网实验&#xff08;思科&#xff09; 技术简介 OSPF多区域功能通过划分网络为多个逻辑区域来提高网络的可扩展性和管理性能。每个区域内部运行独立的SPF计算&#xff0c;而区域之间通过区域边界路由器进行路由信息交换。这种划分策略适用…

three.js中使用CameraHelper来可视化调整阴影相机的范围

1. three.js中使用CameraHelper来可视化调整阴影相机的范围 光源 const directionLight new THREE.DirectionalLight(0xffffff, 1); directionLight.position.set(100, 60, 20); directionLight.castShadow true; scene.add(directionLight);设置计算阴影的范围 direction…