【C++ 第十七章】封装 unordered_map / unordered_set


声明:上一章讲解了 哈希结构的原理与实现,本章主要将上一章学过的拉链法的哈希结构封装进 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值