声明:上一章讲授了 哈希结构的原理与实现,本章告急将上一章学过的拉链法的哈希结构封装进 unordered_map / unordered_set,以是须要先学过相干知识,才气更好吸取本章知识
上一章的链接:【C++ 第十六章】哈希
1. unordered_map / unordered_set 的 根本结构
之前封装 map 和 set 时,由于 map 的数据范例为 pair<key, value>,set 的数据范例为 key
则底层红黑树节点的数据范例为 T:T 可以是 pair<key, value>,也可以是 key,以此同时适配 map 和 set
在比力步调中,须要将节点数据的 key 和 其他节点的 key 比力,data < key
当 T 为 key 时,变量 T data,data 就是 key 范例数据:可以 data < key
当 T 为 pair<key, value> 时,变量 T data,data.first 才是 key 范例数据:不可以直接 data < key
因此须要各安闲 map 和 set 结构中,操持仿函数 KeyOfT
(1) unordered_set 的 根本结构
- namespace my
- {
- template<class K>
- class unordered_set
- {
- // 仿函数:将 data 转换成 key
- struct set_KeyOfT {
- const K& operator()(const K& key) {
- return key;
- }
- };
- public:
-
- private:
- hash_bucket::HashTable<K, K, set_KeyOfT> _ht; // 直接封装一个哈希结构
- };
- }
复制代码
(2) unordered_map 的 根本结构
- namespace my
- {
- template<class K, class V>
- class unordered_map
- {
- // 仿函数:将 data 转换成 key
- struct map_KeyOfT
- {
- const K& operator()(const pair<K, V>& kv) {
- return kv.first;
- }
- };
- public:
- private:
- // 注意要加上类域指定,否则编译器报奇怪的错误
- hash_bucket::HashTable<K, pair<K, V>, map_KeyOfT> _ht; m // 直接封装一个哈希结构
- };
- }
复制代码
2. 操持哈希表迭代器
由于我们哈希表利用 拉链法的结构,因此迭代器实际上是封装链表节点指针
2.1 前置++ 功能
实现思绪:哈希表迭代器 ++,即从当前有用数据节点到下一个有用数据节点,这有两种情况,一是当前链表节点不是尾节点,next 即为下一个有用数据节点;二是当前节点为尾节点,next为空,则须要今后遍历,跳到下一个单链表,继续探求有用数据节点
怎样从当前链表尾节点,到下一条链表?
我们这里接纳的方法:将 “哈希表” 传入迭代器类中,通过 除留余数法 获取当前哈希桶位置 hashi(这就是哈希表数组下标),hashi ++ 就可以直接跳到下一个哈希桶中了,
伪代码:讲授思绪
- if (下一个节点不为空) {
- 直接到下一个节点
- }
- else if (下一个节点为空) {
- 除留余数法 获取当前哈希桶位置 hashi
- hashi++ 跳到下一个桶
- while (hashi < 哈希表的size) {
- 如果下一个桶是空的,就需要一直循环,直到找到非空桶
- }
- if (hashi >= 哈希表的size:说明到最后都没有非空桶) {
- 指针直接指向 nullptr
- }
- else if(找到非空桶){
- 指针指向新链表
- }
- }
复制代码
实际代码
- // HT 是 哈希表指针
- Self& operator++() {
- assert(_pNode);
- // 逻辑是 ++ 到下一个有效元素位置:因此需要判断下一个位置是否是空节点
- if (_pNode->_next != nullptr) {
- _pNode = _pNode->_next;
- }
- else {
- // 因为一个链表遍历完,需要重新到另一个哈希桶,因此还需要重新更新迭代器指向,需要获取哈希表信息,则传一个哈希表指针过来
- KeyOfT kot;
- Hash hash;
- size_t hashi = hash(kot(_pNode->_data)) % (HT->_table.size());
- hashi++; // 这样就跳到下一个桶了
- // 如果下一个桶是空的,就继续跳
- while (hashi < (HT->_table.size())) {
- if (HT->_table[hashi]) break;
- hashi++; // 这样就跳到下一个桶了
- }
- if (hashi >= (HT->_table.size())) {
- _pNode = nullptr;
- }
- else {
- _pNode = HT->_table[hashi];
- }
- }
- return *this;
- }
复制代码
2.2 迭代器 完备代码
一些根本的功能接口这里就不赘诉,告急是明确 前置++ 函数
- // 前置声明,迭代器类中使用哈希表,程序会向上查找是否有哈希表代码实现,否则识别不了,需要提前声明
- template<class K, class T, class KeyOfT, class Hash>
- class HashTable;
- // 迭代器
- template <class K, class T, class Ref, class Ptr, class KeyOfT, class Hash = HashFunc<K>>
- class HashIterator
- {
- typedef HashNode<T> Node;
- typedef HashIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;
- public:
- Node* _pNode; // 链表节点指针
- HashTable<K, T, KeyOfT, Hash>* HT; // 获取哈希表指针
-
- // 基本的功能接口
- Ref operator*() {
- return _pNode->_data;
- }
- Ptr operator->() {
- return &(_pNode->_data);
- }
- bool operator!=(const Self& it) {
- return _pNode != it._pNode;
- }
- bool operator==(const Self& it) {
- return _pNode == it._pNode;
- }
- // 前置++
- Self& operator++() {
- assert(_pNode);
- // 逻辑是 ++ 到下一个有效元素位置:因此需要判断下一个位置是否是空节点
- // 因为一个链表遍历完,需要重新到另一个哈希桶,因此还需要重新更新迭代器指向,需要获取哈希表信息,则传一个哈希表指针过来
- if (_pNode->_next != nullptr) {
- _pNode = _pNode->_next;
- }
- else {
- KeyOfT kot;
- Hash hash;
- size_t hashi = hash(kot(_pNode->_data)) % (HT->_table.size());
- hashi++; // 这样就跳到下一个桶了
- // 如果下一个桶是空的,就继续跳
- while (hashi < (HT->_table.size())) {
- if (HT->_table[hashi]) break;
- hashi++; // 这样就跳到下一个桶了
- }
- if (hashi >= (HT->_table.size())) {
- _pNode = nullptr;
- }
- else {
- _pNode = HT->_table[hashi];
- }
- }
- return *this;
- }
- HashIterator(Node* pNode, HashTable<K, T, KeyOfT, Hash>* pHT)
- :_pNode(pNode)
- , HT(pHT)
- {}
- };
复制代码
3. 在哈希表中封装与应用迭代器
- template<class K, class T, class KeyOfT, class Hash>
- class HashTable
- {
- // 将迭代器类设置成 友元,以便迭代器类可以访问该类的私有:迭代器类的 operator++ 函数实现中,需要访问哈希表类的 私有成员_table
- template <class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
- friend class HashIterator;
- typedef HashNode<T> Node;
- public:
- // 定义迭代器类型 Iterator 和 Const_Iterator
- typedef HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
- typedef HashIterator<K, T, const T&, const T*, KeyOfT, Hash> Const_Iterator;
- // 迭代器
- Iterator begin() {
- // 先找到有效数据节点:不是每个哈希桶都有数据的
- for (size_t i = 0; i < _table.size(); ++i) {
- if (_table[i]) return Iterator(_table[i], this);
- }
- }
- Iterator end() {
- return Iterator(nullptr, this);
- }
- Const_Iterator begin() const {
- // 先找到
- for (size_t i = 0; i < _table.size(); ++i) {
- if (_table[i]) return Const_Iterator(_table[i], this);
- }
- }
- Const_Iterator end() const {
- return Const_Iterator(nullptr, this);
- }
- // ..... 其他函数
- private:
- //也可以使用 list:双向带头循环链表
- //vector<list<pair<K, V>>> _table;
- vector<Node*> _table;
- size_t _n = 0; // 负载因子
- Hash hash;
- KeyOfT kot;
- };
复制代码
参考STL库的写法,以及为了后续实现 operator[] ,将 find 函数的返回值改成 Iterator,将 insert 函数的返回值改成 pair<Iterator, bool> ,而且两个函数内部一些步调对应轻微修改,这里不赘述
哈希表类完备代码
实现函数:封装迭代器 begin / end 、const_begin / const_end 、插入 insert、删除 erase、查询 find
- template<class K, class T, class KeyOfT, class Hash>
- class HashTable
- {
- // 将迭代器类设置成 友元,以便迭代器类可以访问该类的私有:迭代器类的 operator++ 函数实现中,需要访问哈希表类的 私有成员_table
- template <class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
- friend class HashIterator;
- typedef HashNode<T> Node;
- public:
- typedef HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
- typedef HashIterator<K, T, const T&, const T*, KeyOfT, Hash> Const_Iterator;
- HashTable() {
- // 一开始vector里面是随机值,不是 nullptr!! 要自己初始化
- _table.resize(10, nullptr);
- }
- // 本哈希桶需要显式写析构:因为 Node* 是内置类型,只会默认析构成 nullptr,链表节点不会被处理
- ~HashTable() {
- Node* cur = nullptr;
- Node* next = nullptr;
- for (size_t i = 0; i < _table.size(); ++i) {
- cur = _table[i];
- while (cur) {
- next = cur->_next;
- delete cur;
- cur = next;
- }
- }
- }
- // 迭代器
- Iterator begin() {
- // 先找到
- for (size_t i = 0; i < _table.size(); ++i) {
- if (_table[i]) return Iterator(_table[i], this);
- }
- }
- Iterator end() {
- return Iterator(nullptr, this);
- }
- Const_Iterator begin() const {
- // 先找到
- for (size_t i = 0; i < _table.size(); ++i) {
- if (_table[i]) return Const_Iterator(_table[i], this);
- }
- }
- Const_Iterator end() const {
- return Const_Iterator(nullptr, this);
- }
- Iterator find(const K& key) {
- size_t hashi = hash(key) % _table.size();
- Node* cur = _table[hashi];
- while (cur) {
- if (kot(cur->_data) == key) return Iterator(cur, this);
- cur = cur->_next;
- }
- return end();
- }
- pair<Iterator, bool> insert(const T& data) {
- // 考虑冗余
- Iterator ret = find(kot(data));
- if (ret != end()) {
- cout << "该数据已存在" << '\n';
- return make_pair(ret, false);
- }
- // 考虑扩容:当负载比率为 1 时,扩容(即 n == size)
- // 甚至可以大于 1,即理想情况下,就是平均每个桶有 1个以上
- if (_n == _table.size()) {
- HashTable<K, T, KeyOfT, Hash> newTable;
- newTable._table.resize(2 * _table.size());
- // 若直接遍历每个节点,取数值 data insert 插入新表,会导致频繁的 new 节点,造成一定消耗
- // 我们可以直接将旧表的节点 直接转接到 新表,省去new节点的消耗
- for (size_t i = 0; i < _table.size(); ++i) {
- Node* cur = _table[i];
- /*while (cur) {
- newTable.insert(cur->_data);
- cur = cur->_next;
- }*/
- while (cur) {
- Node* Next = cur->_next;
- size_t hashi = hash(kot(cur->_data)) % newTable._table.size();
- cur->_next = newTable._table[hashi];
- newTable._table[hashi] = cur;
- cur = Next;
- }
- _table[i] = nullptr;
- }
- _table.swap(newTable._table);
- }
- size_t hashi = hash(kot(data)) % _table.size();
- // 头插和尾插都行:头插最方便
- Node* newNode = new Node(data);
- newNode->_next = _table[hashi];
- _table[hashi] = newNode;
- _n++;
- return make_pair(Iterator(newNode, this), true);
- }
- bool erase(const K& key) {
- Iterator ret = find(kot(data));
- if (ret._pNode == nullptr) {
- cout << "该节点不存在" << '\n';
- return false;
- }
- // 删除函数需要自己找目标节点,因为底层是单链表,没有prev,删除不好搞
- size_t hashi = hash(key) % _table.size();
- Node* cur = _table[hashi];
- Node* prev = nullptr;
- while (cur) {
- if (kot(cur->_data) == key) {
- if (prev == nullptr) _table[hashi] = cur->_next;
- else prev->_next = cur->_next;
- delete cur;
- cur = nullptr;
- --_n;
- return true;
- }
- prev = cur;
- cur = cur->_next;
- }
- return false;
- }
- private:
- //也可以使用 list:双向带头循环链表
- //vector<list<pair<K, V>>> _table;
- vector<Node*> _table;
- size_t _n = 0; // 负载因子
- Hash hash;
- KeyOfT kot;
- };
复制代码
4. 美满 unordered_map / unordered_set 结构
前面实现了 哈希表迭代器 与 哈希表
因此,在 unordered_map / unordered_set 类中添加这些相干利用以美满。 【迭代器 begin/end、const_begin/const_end、插入insert、删除erase、查询find 】
4.1 u_map 的 operator[] 功能
前面实现了 insert 函数的返回值为 pair<iterator, bool>
(1)若插入乐成,则返回的 pair<iterator, bool> 中的 iterator 迭代器指向 新元素,bool == true
(2)若插入失败,则返回的 pair<iterator, bool> 中的 iterator 迭代器指向 已存在且数值雷同的谁人元素,bool == false
operator[] 返回迭代器 iterator 指向节点中数据 data.second == value
- // operator[]
- V& operator[](const K& key) {
- pair<iterator, bool> ret = _ht.insert({ key, V() });
- return ret.first->second;
- }
复制代码
4.2 关于处理处罚 u_map / u_set 的 key 值不能修改
由于 unordered_map / unordered_set 底层利用 拉链法的哈希结构,须要通过 除留余数法 盘算 key 值映射的下标位置,确定该数据的存放位置
假如修改了 u_map / u_set 的 key,则 删除或查询利用时,通过 除留余数法 盘算 key 值映射的下标位置 就会出现严肃错误!!好比原来 key == 19,映射的位置 hashi == 19/10 == 9,将key修改,key == 23,映射的位置就厘革 hashi == 23/10 == 3,位置就变了!!
操持步调:将参数 key 利用 const 修饰,从源头上限定该范例的参数不能被修改
对于 unordered_set,须要将 哈希表 模板范例参数修改成 const K
- HashTable<K, const K, set_KeyOfT, Hash> _ht;
复制代码 对于 unordered_map,须要将 哈希表 模板范例参数修改成 pair<const K, V>
- HashTable<K, pair<const K, V>, map_KeyOfT, Hash> _ht;
复制代码 其他某些部门也要对应修改,自行调解(通常会报错来提示你,假如不改 doge)
4.3 unordered_map 的完备代码
- #pragma once#include"HashTable.h"namespace my{ template<class K, class V, class Hash = hash_bucket::HashFunc<K>> class unordered_map { struct map_KeyOfT { const K& operator()(const pair<K, V>& kv) { return kv.first; } }; public: // 重定名哈希表的迭代器 typedef typename hash_bucket::HashTable<K, pair<const K, V>, map_KeyOfT, Hash>::Iterator iterator; typedef typename hash_bucket::HashTable<K, pair<const K, V>, map_KeyOfT, Hash>::Const_Iterator const_iterator; // 迭代器 iterator begin() { return _ht.begin(); } iterator end() { return _ht.end(); } const_iterator begin() const { return _ht.begin(); } const_iterator end() const { return _ht.end(); } // 其他利用:插入删除+查找 pair<iterator, bool> insert(const pair<K, V>& kv) { return _ht.insert(kv); } bool erase() { return _ht.erase(); } iterator find(const K& key) { return _ht.find(); } // operator[] V& operator[](const K& key) { pair<iterator, bool> ret = _ht.insert({ key, V() }); return ret.first->second; } private: // 注意要加上类域指定,否则编译器报希奇的错误 hash_bucket::HashTable<K, pair<const K, V>, map_KeyOfT, Hash> _ht; };}
复制代码
4.4 unordered_set 的完备代码
- #pragma once#include"HashTable.h"namespace my{ template<class K, class Hash = hash_bucket::HashFunc<K>> class unordered_set { struct set_KeyOfT { const K& operator()(const K& key) { return key; } }; public: typedef typename hash_bucket::HashTable<K, const K, set_KeyOfT, Hash>::Iterator iterator; typedef typename hash_bucket::HashTable<K, const K, set_KeyOfT, Hash>::Const_Iterator const_iterator; // 迭代器 iterator begin() { return _ht.begin(); } iterator end() { return _ht.end(); } const_iterator begin() const { return _ht.begin(); } const_iterator end() const { return _ht.end(); } // 其他利用:插入删除+查找 pair<iterator, bool> insert(const K& key) { return _ht.insert(key); } bool erase() { return _ht.erase(); } iterator find(const K& key) { return _ht.find(); } private: hash_bucket::HashTable<K, const K, set_KeyOfT, Hash> _ht; };}
复制代码
5. 哈希表结构 完备代码:节点+迭代器+哈希函数+哈希表
- namespace hash_bucket
- {
- template<class T>
- struct HashNode {
- typedef HashNode<T> Node;
- T _data;
- Node* _next;
- HashNode(const T& data)
- :_data(data)
- ,_next(nullptr)
- {}
- };
- template<class K>
- class HashFunc
- {
- public:
- size_t operator()(const K& key) {
- return (size_t)key;
- }
- };
- template<>
- class HashFunc<string>
- {
- public:
- size_t operator()(const string& s) {
- size_t n = 0;
- for (auto& ch : s) {
- n += ch; // 将字符串的每个字符的ASCLII码值相加起来,但这样还是不可完全避免冲突,如 abc 和 cba 的 ASCII码值总和是相等的,则 n 相等,取模之后也就冲突
- // 缓解冲突的一个方法:每一个字符都相乘一个 31
- n *= 31;
- }
- return n;
- }
- };
- // 前置声明,迭代器类中使用哈希表,会向上查找是否有哈希表,否则不匹配,需要提前声明
- template<class K, class T, class KeyOfT, class Hash>
- class HashTable;
- // 迭代器
- template <class K, class T, class Ref, class Ptr, class KeyOfT, class Hash = HashFunc<K>>
- class HashIterator
- {
- typedef HashNode<T> Node;
- typedef HashIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;
- public:
- Node* _pNode;
- HashTable<K, T, KeyOfT, Hash>* HT; // 获取哈希表指针
- Ref operator*() {
- return _pNode->_data;
- }
- Ptr operator->() {
- return &(_pNode->_data);
- }
- bool operator!=(const Self& it) {
- return _pNode != it._pNode;
- }
- bool operator==(const Self& it) {
- return _pNode == it._pNode;
- }
- Self& operator++() {
- assert(_pNode);
- // 逻辑是 ++ 到下一个有效元素位置:因此需要判断下一个位置是否是空节点
- // 因为一个链表遍历完,需要重新到另一个哈希桶,因此还需要重新更新迭代器指向,需要获取哈希表信息,则传一个哈希表指针过来
- if (_pNode->_next != nullptr) {
- _pNode = _pNode->_next;
- }
- else {
- KeyOfT kot;
- Hash hash;
- size_t hashi = hash(kot(_pNode->_data)) % (HT->_table.size());
- hashi++; // 这样就跳到下一个桶了
-
- // 如果下一个桶是空的,就继续跳
- while (hashi < (HT->_table.size())) {
- if (HT->_table[hashi]) break;
- hashi++; // 这样就跳到下一个桶了
- }
- if (hashi >= (HT->_table.size())) {
- _pNode = nullptr;
- }
- else {
- _pNode = HT->_table[hashi];
- }
- }
- return *this;
- }
- HashIterator(Node* pNode, HashTable<K, T, KeyOfT, Hash>* pHT)
- :_pNode(pNode)
- , HT(pHT)
- {}
- };
- template<class K, class T, class KeyOfT, class Hash>
- class HashTable
- {
- // 将迭代器类设置成 友元,以便迭代器类可以访问该类的私有
- template <class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
- friend class HashIterator;
- typedef HashNode<T> Node;
- public:
- typedef HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
- typedef HashIterator<K, T, const T&, const T*, KeyOfT, Hash> Const_Iterator;
-
- HashTable() {
- // 一开始vector里面是随机值,不是 nullptr!! 要自己初始化
- _table.resize(10, nullptr);
- }
- // 本哈希桶需要显式写析构:因为 Node* 是内置类型,只会默认析构成 nullptr,链表节点不会被处理
- ~HashTable() {
- Node* cur = nullptr;
- Node* next = nullptr;
- for (size_t i = 0; i < _table.size(); ++i) {
- cur = _table[i];
- while (cur) {
- next = cur->_next;
- delete cur;
- cur = next;
- }
- }
- }
- // 迭代器
- Iterator begin() {
- // 先找到
- for (size_t i = 0; i < _table.size(); ++i) {
- if (_table[i]) return Iterator(_table[i], this);
- }
- }
- Iterator end() {
- return Iterator(nullptr, this);
- }
- Const_Iterator begin() const {
- // 先找到
- for (size_t i = 0; i < _table.size(); ++i) {
- if (_table[i]) return Const_Iterator(_table[i], this);
- }
- }
- Const_Iterator end() const {
- return Const_Iterator(nullptr, this);
- }
- Iterator find(const K& key) {
- size_t hashi = hash(key) % _table.size();
-
- Node* cur = _table[hashi];
- while (cur) {
- if (kot(cur->_data) == key) return Iterator(cur, this);
- cur = cur->_next;
- }
- return end();
- }
- pair<Iterator, bool> insert(const T& data) {
- // 考虑冗余
- Iterator ret = find(kot(data));
- if (ret != end()) {
- cout << "该数据已存在" << '\n';
- return make_pair(ret, false);
- }
- // 考虑扩容:当负载比率为 1 时,扩容(即 n == size)
- // 甚至可以大于 1,即理想情况下,就是平均每个桶有 1个以上
- if (_n == _table.size()) {
- HashTable<K, T, KeyOfT, Hash> newTable;
- newTable._table.resize(2 * _table.size());
- // 若直接遍历每个节点,取数值 data insert 插入新表,会导致频繁的 new 节点,造成一定消耗
- // 我们可以直接将旧表的节点 直接转接到 新表,省去new节点的消耗
- for (size_t i = 0; i < _table.size(); ++i) {
- Node* cur = _table[i];
- /*while (cur) {
- newTable.insert(cur->_data);
- cur = cur->_next;
- }*/
- while (cur) {
- Node* Next = cur->_next;
- size_t hashi = hash(kot(cur->_data)) % newTable._table.size();
- cur->_next = newTable._table[hashi];
- newTable._table[hashi] = cur;
- cur = Next;
- }
- _table[i] = nullptr;
- }
- _table.swap(newTable._table);
- }
- size_t hashi = hash(kot(data)) % _table.size();
- // 头插和尾插都行:头插最方便
- Node* newNode = new Node(data);
- newNode->_next = _table[hashi];
- _table[hashi] = newNode;
- _n++;
- return make_pair(Iterator(newNode, this), true);
- }
- bool erase(const K& key) {
- Iterator ret = find(kot(data));
- if (ret._pNode == nullptr) {
- cout << "该节点不存在" << '\n';
- return false;
- }
- // 删除函数需要自己找目标节点,因为底层是单链表,没有prev,删除不好搞
- size_t hashi = hash(key) % _table.size();
- Node* cur = _table[hashi];
- Node* prev = nullptr;
- while (cur) {
- if (kot(cur->_data) == key) {
- if (prev == nullptr) _table[hashi] = cur->_next;
- else prev->_next = cur->_next;
- delete cur;
- cur = nullptr;
- --_n;
- return true;
- }
- prev = cur;
- cur = cur->_next;
- }
- return false;
- }
- private:
- //也可以使用 list:双向带头循环链表
- //vector<list<pair<K, V>>> _table;
- vector<Node*> _table;
- size_t _n = 0; // 负载因子
- Hash hash;
- KeyOfT kot;
- };
- }
复制代码
unordered_map / unordered_set 的 测试代码
- void test_map() {
- my::unordered_map<string, string> dict;
- dict.insert({ "sort", "排序" });
- dict.insert({ "left", "左边" });
- dict.insert({ "right", "右边" });
- //dict["left"] = "左边,剩余";
- dict["insert"] = "插入";
- dict["string"];
- my::unordered_map<string, string>::iterator it = dict.begin();
- while (it != dict.end())
- {
- // 不能修改first,可以修改second
- //it->first += 'x';
- it->second += 'x';
- cout << it->first << ":" << it->second << endl;
- ++it;
- }
- cout << endl;
- }
- void test_set() {
- my::unordered_set<string> st;
- st.insert({ "sort" });
- st.insert({ "left" });
- st.insert({ "right" });
- /*dict["left"] = "左边,剩余";
- dict["insert"] = "插入";
- dict["string"];*/
- my::unordered_set<string>::iterator it = st.begin();
- while (it != st.end())
- {
- // 不能修改*it
- //*it += 'x';
- cout << *it << endl;
- ++it;
- }
- cout << endl;
- }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金 |