【C++】哈希开散列 | unordered系列容器的封装

文章目录

  • 一.开散列
    • 1. 开散列的概念
    • 2. 开散列结构
    • 3. Insert 插入
    • 4. Find 查找
    • 5. Insert 扩容
    • 6. Erase 删除
    • 7. 析构函数
    • 8. 其它函数接口
    • 9. 性能测试
  • 二.封装
    • 1. 封装内部结构
    • 2. 实现接口
  • 三.代器器
    • 1. 迭代器的定义
    • 2. 常用接口
    • 3. 迭代器++
    • 4. begin()、end()
    • 5. find的改动
    • 6. 下标访问[ ]重载
  • 四.源码与测试用例
    • 1. 底层HashTable
    • 2. unordered_set/map
    • 3. 测试用例

前言: 上一篇博客我们使用闭散列的方式实现了Hash,其实在STL库unordered_set、unordered_map中底层是开散列的方式实现的Hash,所以,本篇博客就再使用开散列的方式实现Hash,并将unordered_set、unordered_map进行封装。

一.开散列

1. 开散列的概念

开散列法又称链地址法(拉链法、哈希桶),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个字集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中

在这里插入图片描述

2. 开散列结构

首先我们要使用vector来存储每个链表的节点,然后每个节点中有数据域和指针next域。然后我们可以将HashNode的构造函数写一下,使用pair类型构造处一个HashNode。

template <class K, class V>
struct HashNode
{HashNode(const pair<K, V>& kv):_kv(kv), _next(nullptr){}pair<K, V> _kv;HashNode<K, V>* _next;
};template <class K, class V, class Hash = HashFunc<K>>
class HashTable
{
public:typedef HashNode<K, V> Node; 
private:vector<Node*> _table;size_t _size = 0;
};

3. Insert 插入

首先我们实现插入的主逻辑,然后对其进行逐步优化。

我们根据 kv 创建一个节点,然后根据仿函数进行取模求出映射位置,然后进行链表的头插。

bool Insert(const pair<K, V>& kv)
{        Hash hash;size_t hashi = hash(kv.first) % _table.size();//头插Node* newNode = new Node(kv);newNode->_next = _table[hashi];_table[hashi] = newNode;++_size;return true;
}

像哈希表中插入数据首先要保证数据的唯一性,所以我们要先进行去重处理,此时我们顺带实现Find函数。

4. Find 查找

根据key值求出映射位置,如果该位置不为空,则进行链表的遍历,如果找到key值,则返回cur节点,如果找不到则向后遍历,直到cur为空。

Node* Find(const K& key)
{if (_table.size() == 0) return nullptr;Hash hash;size_t hashi = hash(key) % _table.size();//向桶中进行查找 Node* cur = _table[hashi];while (cur){if (cur->_kv.first == key){return cur;}cur = cur->_next;}return nullptr;
}

5. Insert 扩容

插入的主逻辑实现了,去重判断也实现了,接下来就是表的扩容。

如果哈希表的大小为0或达到了哈希的负载因子,则要进行扩容。

我们看一下STL库中负载因子控制的多少:

在这里插入图片描述

STL库中设计的负载因子为:当表中插入的元素个数>哈希表的大小,即负载因为为1的时候进行扩容,将表的大小扩容到 next_size.

扩容的挪动数据要注意,因为开散列的每个桶上的数据个数不同。进行扩容后,桶中每个元素都可能映射到不同的新位置处,所以我们不能像闭散列那样复用Insert,要重新将结点链接到新表中。

挪动时要让原表中的结点一个一个链接到新表中

//扩容  ---  如果插入的数据等于表的大小
if (_size == _table.size())
{size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;vector<Node*> newTable;newTable.resize(newSize, nullptr);//将旧表中的节点移动映射到新表Hash hash;for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;size_t hashi = hash(cur->_kv.first) % _table.size();cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}//将旧表i位置处结点清空_table[i] = nullptr;}_table.swap(newTable);
}

在这里插入图片描述
在这里插入图片描述

发现源码中进行扩容时调用了next_size函数,扩容直接将size乘以2不就行了吗,为什么要特殊计算 size ?

因为hash表的大小最好是素数,如果是素数,映射的结果冲突几率就小,因为非素数因子多,进行映射后相同位置冲突大。将hash表的大小设计为素数后,其实就可以做到hash表中个别桶的冲突次数过多而过分的大。

详细可以看这篇文章:算法分析:哈希表的大小为何是素数

现在我们也添加这个功能:

库中使用lower_bound(返回第一个大于等于n的下标)/upper_bound(返回第一个大于n的下标),其实直接使用for循环遍历就行了.

inline size_t __stl_next_prime(size_t n)
{static const size_t __stl_num_primes = 28;static const size_t  __stl_prime_list[__stl_num_primes] ={53,         97,         193,       389,       769,1543,       3079,       6151,      12289,     24593,49157,      98317,      196613,    393241,    786433,1572869,    3145739,    6291469,   12582917,  25165843,50331653,   100663319,  201326611, 402653189, 805306457,1610612741, 3221225473, 4294967291};//取下一次扩容的大小:for (size_t i = 0; i < __stl_next_prime; i++){if (__stl_prime_list[i] > n)return __stl_prime_list[i];}return (size_t)-1;
}

6. Erase 删除

虽然我们实现了Find函数,但是单单使用Find是无法完成删除功能的。

例如下面这种情况,单链表删除中间结点我们还需要知道 prev 结点。

在这里插入图片描述

bool Erase(const K& key)
{if (_table.size() == 0) return false;Hash hash;size_t hashi = hash(key) % _table.size();Node* pre = nullptr;Node* cur = _table[hashi];while (cur){if (cur->_kv.first == hash(key)){//如果删除的是链中第一个元素 --- 即头删if (pre == nullptr){_table[hashi] = cur->_next;}//2.中间删除else{pre->_next = cur->_next;}delete cur;--_size;return true;}pre = cur;cur = cur->_next;}return false;
}

7. 析构函数

注意了,当哈希表生命周期结束后会调用析构函数,我们使用的vector会自动释放表中的内容,可是vector中存放的是链表,我们释放时还要对桶(链表)进行释放,所以我们要手动写一个析构函数。

~HashTable()
{for (size_t i = 0; i < _table.size(); ++i){Node* cur = _table[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_table[i] = nullptr;}
}

8. 其它函数接口

//表的长度
size_t BucketSize()
{return _table.size();
}
//数据个数
size_t Size()
{return _size;
}
//桶的数量
size_t BucketNum()
{size_t Num = 0;for (size_t i = 0; i < BucketSize(); i++){if (_table[i]) Num++;}return Num;
}
//最长的桶
size_t MaxBucketLenth()
{size_t Max_len = 0;size_t temp = 0;for (size_t i = 0; i < BucketSize(); i++){if (_table[i]){size_t len = 1;Node* cur = _table[i]->_next;while (cur){len++;cur = cur->_next;}if (len > Max_len){Max_len = len;temp = i;}}}printf("Max_len_i:[%u]\n", temp);return Max_len;
}

9. 性能测试

void TestHT()
{int n = 18000000;vector<int> v;v.reserve(n);srand((unsigned int)time(0));for (int i = 0; i < n; ++i){v.push_back(rand()+i);  // 重复少//v.push_back(rand());  // 重复多}size_t begin1 = clock();HashTable<int, int> ht;for (auto e : v){ht.Insert(make_pair(e, e));}size_t end1 = clock();cout << "数据个数:" << ht.Size() << endl;cout << "表的长度:" << ht.BucketSize() << endl;cout << "桶的个数:" << ht.BucketNum() << endl;cout << "平均每个桶的长度:" << (double)ht.Size() / (double)ht.BucketNum() << endl;cout << "最长的桶的长度:" << ht.MaxBucketLenth() << endl;cout << "负载因子:" << (double)ht.Size() / (double)ht.BucketSize() << endl;
}

在这里插入图片描述

发现,将哈希表的大小设置为素数后,即使负载因子到了0.9,最长的桶也不过才是 2。所以hash表的查找效率为O(1)。

接下来我们对比红黑树和hash表其查找效率(查找1千万个数据)

在这里插入图片描述

哈希表插入效率较低,是因为扩容挪动数据非常消耗时间。

接下来我们使用set、onordered_set(底层对应的就是红黑树和hash表),向其中插入1千万的随机数,对比其性能,并对onordered_set进行直接插入和提前扩容再进行插入的效率对比。

在这里插入图片描述

测试代码如下:

void test_op()
{int n = 10000000;   //1千万个数据vector<int> v;v.reserve(n);srand((unsigned int)time(0));for (int i = 0; i < n; ++i){//v.push_back(i);v.push_back(rand()^ 1311 * 144+i);}size_t begin1 = clock();set<int> s;for (auto e : v){s.insert(e);}size_t end1 = clock();size_t begin2 = clock();unordered_set<int> us;us.reserve(n);for (auto e : v){us.insert(e);}size_t end2 = clock();cout << "有效数据个数:" << s.size() << endl;cout << "\nInsert 插入:" << endl;cout << "set : " << end1 - begin1 << endl;cout << "unordered_set : " << end2 - begin2 << endl;size_t begin3 = clock();for (auto e : v){s.find(e);}size_t end3 = clock();size_t begin4 = clock();for (auto e : v){us.find(e);}size_t end4 = clock();cout << "\nFind 查找:" << endl;cout << "set :" << end3 - begin3 << endl;cout << "unordered_set :" << end4 - begin4 << endl;size_t begin5 = clock();for (auto e : v){s.erase(e);}size_t end5 = clock();size_t begin6 = clock();for (auto e : v){us.erase(e);}size_t end6 = clock();cout << "\nErase 删除:" << endl;cout << "set erase:" << end5 - begin5 << endl;cout << "unordered_set erase:" << end6 - begin6 << endl;
}

以上就是我们hash开散列的基本实现了,实现了以上功能我们就可以封装unordered_map/unordered_set了。

二.封装

1. 封装内部结构

首先是改变HashTable中每个结点存储的数据类型,如unordered_set中存放的是key,unordered_map中存放的是pair类型,所以我们将结点中存储的类型改为T,如果是set,T对应就是key,如果是map,那T就对应pair结构

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

所以,Insert插入的类型也应改为T模板类型,在使用到类型中的值时,使用仿函数取出该比较的数据。

然后我们就来编写unordered_set(map)类

unordered中底层就是调用我们写的HashTable,所以直接使用HashTable定义成员变量,并传入模板参数。(以下简写的set、map都对应的Hash方法实现的unordered_set(map))

注意,因为set是Key模型,设置一个模板参数即可;而map是KV模型,需要设置两个模板参数对应pair的中的两个数据类型。所以,在底层我们统统传入HashTalbe两个模板参数,并以第二个模板参数为准决定底层存储什么类型,如果是set,就使用仿函数取出key,如果是map就使用仿函数取出pair.first。

所以,在传入参数前我们要先编写好仿函数set(map)KeyOfT,以便于底层取出数据。

//****   set   *********
template<class K, class Hash = HashFunc<K>>
class unordered_set
{
public:
private:struct setKeyOfT{const K& operator()(const K& key){return key;}};//两个模板参数都传入KHashTable<K, K, Hash, setKeyOfT> _ht;
};
//****   map   *********
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
public:
private://让HashTable取出pair中的K  --- 内部类struct mapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};HashTable<K, pair<K, V>, Hash, mapKeyOfT> _ht;
};

2. 实现接口

接下来就是为我们封装的map、set 设计成员函数,其实我们只是封装了一层,本质还是调用HashTable中的Insert、Erase等函数。

// ******  set  ********
bool insert(const K& kv)
{return _ht.Insert(kv);
}bool erase(const K& kv)
{return _ht.Erase(kv);
}// ******  map  ********
bool insert(const pair<K, V>& kv)
{return _ht.Insert(kv);
}
bool erase(const K& k)
{return _ht.Erase(k);
}

注意,Insert、Erase的底层中,涉及到key值操作的,我们要进行使用两层仿函数进行取值。

三.代器器

1. 迭代器的定义

在HashTable中有迭代器的接口(begin()、end()),而迭代器中也会使用到HashTable的结构,所以,在实现迭代器之前我们要先进行HashTable的声明(注意:模板类的声明要加上模板参数一起声明)。

我们来看看源码中迭代器是如何定义的

在这里插入图片描述

接下来是我们的定义:

//前置声明
template <class K, class T, class Hash, class keyOfT>
class HashTable;template<class K, class T, class Hash, class keyOfT>
class __Hash_Iteartor
{
public:typedef HashNode<T> Node;typedef HashTable<K, T, Hash, keyOfT> HT;typedef __Hash_Iteartor<K, T, Hash, keyOfT> Self;__Hash_Iteartor(Node* node, HT* pht):_node(node), _pht(pht){}__Hash_Iteartor(){}
private://成员变量Node* _node;   //指向结点HT* _pht;	   //指向当前表
};

2. 常用接口

接下来实现一些常用的接口:

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

3. 迭代器++

STL中迭代器++的实现:

在这里插入图片描述

思路如下:

  1. 判断_node的_next是否存在存在结点,如果存在直接让_node = _node->_next即可
  2. 如果不存在结点,则当前桶遍历结束,要寻找下一个有数据的桶。
  3. 根据_node中的data域求出映射位置,然后从映射位置向后遍历哈希表,直到talbe[i]处有数据,有数据则跳出循环
  4. 当 i 等于哈希表的大小,则表示不存在下一个数据,则将_node赋值为nullptr
  5. 返回*this,即返回当前对象。
Self& operator++()
{//在当前桶中进行++if (_node->_next){_node = _node->_next;}else //找下一个有效的桶{Hash hash;keyOfT kft;size_t i = hash(kft(_node->_data)) % _pht->_table.size();for (i += 1; i < _pht->_table.size(); i++){if (_pht->_table[i]){_node = _pht->_table[i];break;}}//如果不存在有数据的桶if (i == _pht->_table.size())_node = nullptr;}return *this;
}

注意,此时我们使用了哈希表,具体访问了其中的元素,所以我们要让迭代器作为HashTable的友元类(也要带上模板参数进行声明噢)。

在这里插入图片描述

4. begin()、end()

begin就是返回HashTable中第一个存储了数据的桶。如果表中没有存储数据,直接返回end(),而end()迭代器中的_node为nullptr构造的。

typedef __Hash_Iteartor<K, T, Hash, keyOfT> iterator;iteratorbegin()
{for (size_t i = 0; i < _table.size(); i++){if (_table[i])return iterator(_table[i], this);}return end();
}
iterator end()
{return iterator(nullptr, this);
}

5. find的改动

find中,我们返回是直接返回迭代器,在return的地方使用匿名对象进行返回即可。

在这里插入图片描述

6. 下标访问[ ]重载

如果要实现map中的下标访问操作符重载,我们要对Insert进行改造,让其返回值为pair结构,其中first为迭代器,second为bool类型,表示插入成功与否(虽然不改变也能实现)。

在这里插入图片描述
在这里插入图片描述

Insert的改动完成后,接下来就可以在map中添加 [] 下标访问操作符重载了。

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

面试题:
一个类型K去做 set 和 unordered_set 的模板参数有什么要求?

  1. set :
    set要求支持能进行小于号比较,或者显示提供比较的仿函数

  2. unordered_set:

    • 要求K类型对象能转化为整形取模,或提供能装化为整形的仿函数
    • K类型对象要支持等于比较,或提供等于比较的仿函数 (set有小于,就可以通过左小右大的方式找到数据;而unordered_set会出现冲突,使用key值只能找到映射的桶,遍历桶的时候,就需要进行等于比较了)

四.源码与测试用例

1. 底层HashTable

template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct HashFunc<string>
{size_t operator()(const string& key){size_t val = 0;for (auto ch : key)val = val * 131 + ch;return val;}
};template <class T>
struct HashNode
{HashNode(const T& data):_data(data), _next(nullptr){}T _data;HashNode<T>* _next;
};// 对哈希表进行前置声明
template <class K, class T, class Hash, class keyOfT>
class HashTable;template<class K, class T, class Hash, class keyOfT>
class __Hash_Iteartor
{
public:typedef HashNode<T> Node;typedef HashTable<K, T, Hash, keyOfT> HT;typedef __Hash_Iteartor<K, T, Hash, keyOfT> Self;__Hash_Iteartor(Node* node, HT* pht):_node(node), _pht(pht){}__Hash_Iteartor():_node(nullptr), _pht(nullptr){}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}Self& operator++(){//在当前桶中进行++if (_node->_next){_node = _node->_next;}else //找下一个有效的桶{Hash hash;keyOfT kft;size_t i = hash(kft(_node->_data)) % _pht->_table.size();for (i += 1; i < _pht->_table.size(); i++){if (_pht->_table[i]){_node = _pht->_table[i];break;}}//如果不存在有数据的桶if (i == _pht->_table.size())_node = nullptr;}return *this;}bool operator!=(const Self& self){return _node != self._node;}bool operator==(const Self& self){return _node == self._node;}private://成员Node* _node;   //指向结点HT* _pht;	   //指向当前表};template <class K, class T, class Hash, class keyOfT>
class HashTable
{
public:typedef HashNode<T> Node;//将迭代器设为友元template<class K, class T, class Hash, class keyOfT>friend class  __Hash_Iteartor;typedef __Hash_Iteartor<K, T, Hash, keyOfT> iterator;iterator begin(){for (size_t i = 0; i < _table.size(); i++){if (_table[i])return iterator(_table[i], this);}return end();}iterator end(){return iterator(nullptr, this);}//析构要进行特殊处理,遍历整个表,再删除桶中的数据。~HashTable(){for (size_t i = 0; i < _table.size(); ++i){Node* cur = _table[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_table[i] = nullptr;}}inline size_t __stl_next_prime(size_t n){static const size_t __stl_num_primes = 28;static const size_t  __stl_prime_list[__stl_num_primes] ={53,         97,         193,       389,       769,1543,       3079,       6151,      12289,     24593,49157,      98317,      196613,    393241,    786433,1572869,    3145739,    6291469,   12582917,  25165843,50331653,   100663319,  201326611, 402653189, 805306457,1610612741, 3221225473, 4294967291};//取下一次扩容的大小:for (size_t i = 0; i < __stl_num_primes; i++){if (__stl_prime_list[i] > n)return __stl_prime_list[i];}return (size_t)-1;}pair<iterator, bool> Insert(const T& data){Hash hash;keyOfT koft;//去重iterator ret = Find(koft(data));if (ret != end()){return make_pair(ret, false);}//扩容  ---  如果插入的数据等于表的大小if (_size == _table.size()){//size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;vector<Node*> newTable;size_t newSize = __stl_next_prime(_table.size());newTable.resize(newSize, nullptr);//将旧表中的节点移动映射到新表for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;size_t hashi = hash(koft(cur->_data)) % newSize;cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}//将旧表i位置处结点清空_table[i] = nullptr;}_table.swap(newTable);}size_t hashi = hash(koft(data)) % _table.size();//头插Node* newNode = new Node(data);newNode->_next = _table[hashi];_table[hashi] = newNode;++_size;return make_pair(iterator(newNode, this), true);}iterator Find(const K& key){if (_table.size() == 0) return end();Hash hash;keyOfT koft;size_t hashi = hash(key) % _table.size();//向桶中进行查找 Node* cur = _table[hashi];while (cur){if (koft(cur->_data) == key){return iterator(cur, this);}cur = cur->_next;}return end();}//单链表不能直接找到该节点并删除bool Erase(const K& key){if (_table.size() == 0) return false;Hash hash;keyOfT koft;size_t hashi = hash(key) % _table.size();Node* pre = nullptr;Node* cur = _table[hashi];while (cur){if (koft(cur->_data) == hash(key)){//如果删除的是链中第一个元素 --- 即头删if (pre == nullptr){_table[hashi] = cur->_next;}//2.中间删除else{pre->_next = cur->_next;}delete cur;--_size;return true;}pre = cur;cur = cur->_next;}return false;}//表的长度size_t BucketSize(){return _table.size();}//数据个数size_t Size(){return _size;}//桶的数量size_t BucketNum(){size_t Num = 0;for (size_t i = 0; i < BucketSize(); i++){if (_table[i]) Num++;}return Num;}//最长的桶size_t MaxBucketLenth(){size_t Max_len = 0;size_t temp = 0;for (size_t i = 0; i < BucketSize(); i++){if (_table[i]){size_t len = 1;Node* cur = _table[i]->_next;while (cur){len++;cur = cur->_next;}if (len > Max_len){Max_len = len;temp = i;}}}printf("Max_len_i:[%u]\n", temp);return Max_len;}void Print_map(){cout << "Print_map:" << endl;for (int i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){cout << "i:" << i << " [" << cur->_data.first << " " << cur->_data.second << "] " << endl;cur = cur->_next;}}}void Print_set(){cout << "Print_set:" << endl;for (int i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){cout << "i:" << i << " [" << cur->_data << "] " << endl;cur = cur->_next;}}}private:vector<Node*> _table;size_t _size = 0;
};

2. unordered_set/map

unordered_set:

template<class K, class Hash = HashFunc<K>>
class unordered_set
{
public:struct setKeyOfT;typedef typename dianxia::HashTable<K, K, Hash, setKeyOfT>::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}pair<iterator, bool> insert(const K& kv){return _ht.Insert(kv);}bool erase(const K& kv){return _ht.Erase(kv);}void print(){_ht.Print_set();}private:struct setKeyOfT{const K& operator()(const K& key){return key;}};HashTable<K, K, Hash, setKeyOfT> _ht;
};

unordered_map:

template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
public:struct mapKeyOfT;typedef typename dianxia::HashTable<K, pair<K, V>, Hash, mapKeyOfT>::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.Insert(kv);}bool erase(const K& k){return _ht.Erase(k);}V& operator[](const K& key){pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;}void print(){_ht.Print_map();}private://取出pair中的K值  --- 内部类struct mapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};HashTable<K, pair<K, V>, Hash, mapKeyOfT> _ht;
};

3. 测试用例

封装测试:

void test_unordered01()
{Brant::unordered_map<int, int> mp1;mp1.insert({ 1,1 });mp1.insert({ 54,54 });mp1.insert({ 2,2 });mp1.insert({ 3,3 });mp1.insert({ 4,4 });mp1.insert({ 6,6 });mp1.insert({ 6,6 });mp1.print();cout << "Erase:---------------" << endl;mp1.erase(1);mp1.erase(54);mp1.print();cout << endl << "--------------------------------------" << endl;Brant::unordered_set<int> st1;st1.insert(1);st1.insert(54);st1.insert(2);st1.insert(3);st1.insert(4);st1.insert(6);st1.insert(6);st1.print();cout << "Erase:---------------" << endl;st1.erase(1);st1.erase(54);st1.print();
}

迭代器测试:

void test_iterator01()
{Brant::unordered_map<string, string> dict;dict.insert({ "sort","排序" });dict.insert({ "left","左边" });dict.insert({ "right","右边" });dict.insert({ "string","字符串" });Brant::unordered_map<string, string>::iterator it = dict.begin();while (it != dict.end()){cout << it->first << " : " << it->second << endl;++it;}cout << endl;
}void test_iterator02()
{Brant::unordered_map<string, int> countMap;string arr[] = { "苹果","西瓜","菠萝","草莓","菠萝","草莓" ,"菠萝","草莓", "西瓜", "菠萝", "草莓", "西瓜", "菠萝", "草莓","苹果" };for (auto e : arr){countMap[e]++;}for (auto kv : countMap){cout << kv.first << " " << kv.second << endl;}
}

本文到此结束,码文不易,还请多多支持哦!!!

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

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

相关文章

检查网站是HTTP那种协议与获取域名的ipv6地址

前言 最近在做HTTPS的应用&#xff0c;可能需要使用ipv6的地址做SLB&#xff0c;但是怎么检查配置正确&#xff0c;总不能每次都看日志吧&#xff0c;实际上客户端也很容易查看&#xff0c;总结工作经验。 检查HTTP协议版本 笔者想到了使用浏览器方式&#xff0c;或者抓包&a…

Java Selenium WebDriver 网页填报

一、windows环境安装配置 1.安装chrome浏览器 在“关于chrome”界面&#xff0c;查看浏览器版本号 2.下载chromeDriver 在https://registry.npmmirror.com/binary.html?pathchromedriver/下载对应版本的驱动&#xff08;如果浏览器版本过新&#xff0c;建议下载最接近的版…

Docker环境下MySQL备份恢复工具XtraBackup使用详解 | Spring Cloud 62

一、XtraBackup 简介 Percona XtraBackup是一个开源的MySQL和MariaDB数据库备份工具&#xff0c;它能够创建高性能、一致性的备份&#xff0c;并且对生产环境的影响很小。Percona XtraBackup通过在不停止MySQL服务器的情况下&#xff0c;复制InnoDB存储引擎的数据文件和事务日…

fetch-github-hosts间隔一年大更新v2.6发布,多端支持

前言 fetch-github-hosts是一款同步 github hosts 的工具&#xff0c;用于帮助您解决github时而无法访问的问题。在间隔了一年之久的时间&#xff0c;最近抽空将fetch-github-hosts的依赖及UI进行了一波大更新&#xff0c;同时也增加了一些实用的功能。 主要更新 更新了基础依…

Linux 中利用设备树学习Ⅳ

系列文章目录 第一章 Linux 中内核与驱动程序 第二章 Linux 设备驱动编写 &#xff08;misc&#xff09; 第三章 Linux 设备驱动编写及设备节点自动生成 &#xff08;cdev&#xff09; 第四章 Linux 平台总线platform与设备树 第五章 Linux 设备树中pinctrl与gpio&#xff08;…

【传统视觉】C#创建、封装、调用类库

任务 因为实现代码相对简单&#xff0c;然后又没有使用Opencv&#xff0c;所以就直接用C#实现&#xff0c;C#调用。 1.创建类库 1.1新建一个类库 vs2015 > 文件 > 新建 > 项目 using System; using System.Collections.Generic; using System.Linq;namespace Yo…

【go-zero】docker镜像直接部署API与RPC服务 如何实现注册发现?docker network 实现 go-zero 注册发现

一、场景&问题 使用docker直接部署go-zero微服务会发现API无法找到RPC服务 1、API无法发现RPC服务 用docker直接部署 我们会发现API无法注册发现RPC服务 原因是我们缺少了docker的network网桥 2、系统内查看 RPC服务运行正常API服务启动,通过docker logs 查看日志还是未…

python#django数据库一对一/一对多/多对多

一对一OneToOneField 用户和用户信息 搭建 # 一对一 class TestUser(models.Model): usernamemodels.CharField(max_length32) password models.CharField(max_length32) class TestInfo(models.Model): mick_namemodels.CharField(max_length32) usermode…

Session与Cookie的区别(五)

储存状态的方式 小明的故事说完了&#xff0c;该来把上面这一段变成网络的实际案例了。其实在网络世界中问题也是一样的。 前面已经提到过我们会把状态存在 Cookie 里面&#xff0c;让 Request 之间能够变得有关联。 假设我们今天要来做一个会员系统&#xff0c;那我要怎么知道…

云道资本:2023中国氢能源产业-氢制备深度研究报告(附下载)

关于报告的所有内容&#xff0c;公众【营销人星球】获取下载查看 核心观点 中国可再生能源消纳能力提升远远滞后于发电占比的提升。大规模的可再生能源发电是实现碳中和的关键一步&#xff0c;但风电、光伏发电间歌性、波动性强&#xff0c;电网消纳压力较大&#xff0c;且电…

【C++】哈希闭散列

一.哈希的概念 在前面学习了二叉搜索树、AVL树、红黑树之后&#xff0c;我们得知顺序结构以及平衡树中&#xff0c;元素关键码与其存储位置之间没有对应的关系&#xff0c;因此在查找一个元素时&#xff0c;必须经过关键码的多次比较。顺序查找的时间复杂度为 O(N)&#xff0c…

【MapGIS精品教程】010:空间叠置分析案例教程

文章目录 一、叠置分析介绍(一) 什么是叠加分析(二)叠加分析的分类二、叠加分析操作一、叠置分析介绍 (一) 什么是叠加分析 叠加分析是依靠把分散在不同层上的空间属性信息按相同的空间位置加到一起,合为新的一层。该层的属性由被叠加层各自的属性组合而成,这种组合可…