unordered_set和unordered_map的封装

一:对unordered_set与unordered_map的认识

二:改造哈希桶

           2.1:改造迭代器

           2.2:类中相互typedef前置的声明

           2.3:友元关系

三:封装unordered_set与unordered_map

           3.1:封装unordered_set/map

           3.2:unordered_map的operator[]

           3.3:const_iterator

           3.4:仿函数

四;总结

//////

一:对unordered_set与unordered_map的认识:

 如上所示,我们可以看到,unordered_set和unordered_map是C++11时才有的,如同set/map一样,unordered_set/map也通过mult系列的接口,允许键值冗余,

那么unordered_set/map和普通的set/map不同什么地方呢?

其实从名字就可以看出来,unordered的中文意思是无序的,我们知道,set/map有着排序+去重的作用,那么unordered_set/map就只有去重的作用了,具体如下所示:

 通过测试用例我们可以看到,以set与unordered_set为例,set做到排序+去重,而unordered_set却只有去重,没有排序,那么看到这,我们不禁要问,unorder_set/map能做的set/map也能做,为什么要在C++11中加入unordered_set/map呢?为了解答这个问题,我们可以测一下它们的性能,具体如下所示:

通过随机数种子,生成1000000个数,分别测试他们插入,查找,删除所花费的时间;

 

 通过上述三种用例的测试,我们可以得出结论,unordered_set/map在插入删除查找等方面都强于set/map,所花费的时间都相较于少,所以,unordered_set/map除了不能进行排序外,其他的效率都比map/set效率要高,在乱序的场景下,unordered_set/map的使用频率更高。而效率之所以高,是因为unordered_set/map的底层是哈希桶,哈希结构,而非set/map底层的树形结构;而这些,就是unordered_set/map存在的意义!

unordered_set/map的成员函数:

unordered_set/map和set/map有大量的重新接口,这正是STL库设计的优点,一通则百通,相同的成员函数就不再介绍,主要介绍与set/map不同的成员函数;

 这是一些关于哈希桶的成员函数,比如,获取桶的个数,最大的桶的个数,桶的大小等等;

 这是一些关于荷载因子的成员函数,比如获取荷载因子,改变荷载因子等等,

 这是一些其他的成员函数,比如获取哈希函数等等,

以上的这些成员函数是和set/map不同的,但是在现实中使用的情况较少,稍微了解了解即可;

 需要注意这里的迭代器,set/map的迭代器是双向迭代器,而unordered_set/map中,迭代器只是单向迭代器,因为unordered_set/map的底层是哈希桶,哈希桶的结构是数组+链表,迭代器在对链表进行迭代时,只能单向迭代;

//////

二:改造哈希桶:

 如set/map共用一颗红黑树那样,unordered_set/map也共用一个哈希桶,

所以我们需要将之前的哈希桶(【C++】哈希表/哈希桶_KL4180的博客-CSDN博客)改造成为泛型,使其满足K,KV模型的不同需求,存储的数据只有一个类型,

 将节点中,原本两个模板参数的K,KV模型变成一个模板参数T,并且在哈希桶中将节点的类型也做相应的改变。

///

2.1:改造迭代器:

在之前的哈希桶的博客中,我们没有分析和实现迭代器,但是封装unordered_set/map需要迭代器,所以在此分析实现一下,同时因为哈希桶只有单向迭代,就只能向后遍历++,那么哈希桶中的迭代器是如何实现++的呢?

 第一种情况很容易实现,但是第二种情况有点复杂,大概实现思路应该是:当现在的桶下面的链表访问完后,我们先记录一下当前桶映射的位置,然后映射位置+1,就找到后面的第一个桶,然后在让迭代器it指向后面一个桶的头结点;具体迭代器的搭建如下所示:

 这是迭代器的大致结构,下面开始具体实现++操作:

 使用KeyOfT仿函数获取当前数据的key值(因为不知道是map还是set在调用)。
再使用Hash仿函数将key值转换成可以模的整形(因为不知道key是整形还是字符串再或者其他自定义类型)。

然后开始寻找下一个桶:
从当前哈希表下标开始向后寻找,直到找到下一个桶,将桶的头节点地址赋值给_node。
如果始终没有找到,说明没有桶了,也就是没有数据了,it指向end,这里使用空指针来代替end。

其中,这里面辨别K,KV模型的仿函数keyOFT和将类型转为整数的Hash函数都在前面的博客中实现过,这里就不做过多的赘述;

同时,这里面的keyOFT和Hash都没有给缺省值,因为你不能在哈希桶里面将其写死,具体原因等下面封装的时候再说,

///

2.2:类中相互typedef前置的声明:

 因为迭代器中有一个成员变量是哈希桶的指针,如上图所示,所以我们需要创建一个哈希桶指针,所以在迭代器中typedef了HashBucket成为 HB,方便我们使用。同时我们使用迭代器,我们又在哈希桶里面typedef了迭代器,

 这样就造成了你中有我,我中有你的局面,即哈希桶需要用迭代器,但是迭代器需要哈希桶指针,为了避免其争议,我们在迭代器的前面加上前置声明,

///

2.3:友元关系:

 虽然,上面解决了类中相互typedef的问题,但是迭代器++又出现问题,因为在迭代器的类中,要实现迭代器++就有可能重新映射,而重新映射,我们必须要访问哈希桶指针,但是哈希桶指针是私有属性的,此时,我们有两种方法:

1:在哈希桶中通过公有成员函数,get_tables(),通过这个公用函数去获取哈希桶指针,

2:将迭代器类变成哈希桶类的友元类,这样就可以访问到哈希桶内的私有成员;

这里,我们以第二种,也就是通过友元类解决此类问题,(虽然可能会破坏封装);具体如下所示:

 类模版的友元声明需要加上其模版参数,要不然会报错;

至此,迭代器的改造就基本上完成了,剩下的如operator*,operator->等等这些的运算符,和以前的大差不差,将在最后的源码给出,这里不做过多的赘述;

//////

三:封装unordered_set与unordered_map:

封装的步骤和用红黑树封装set,map一样,下面直接给出:

3.1:封装unordered_set/map:

unordered_map的封装:

	//这里给Hash函数的缺省值,因为要将仿函数的比较权放在封装的unordered_set中
    template<class K,class V,class Hash= MKL::HashFunc<K>>
	class un_map
	{
	public:
        //仿函数比较,告诉底层的哈希桶,比较的值,将其key取出;
		struct mapkeyOFT
		{
			K operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};

        //迭代器是封装哈希桶的迭代器;
		typedef typename MKL::HashBucket<K, pair<const K, V>, mapkeyOFT,Hash>::iterator iterator;

        //剩下的成员函数全部调用哈希桶中的成员函数;
		iterator begin()
		{
			return umap1.begin();
		}

		iterator end()
		{
			return umap1.end();
		}

    
		bool Insert(const pair<K, V>& kv)
		{
			return umap1.Insert(kv);
		}


		bool Find(const K& key)
		{
			return umap1.Find(key);
		}

		bool Erase(const K& key)
		{
			return umap1.Erase(key);
		}
		
	private:
        //底层用哈希桶创建
		MKL::HashBucket<K, pair<const K, V>, mapkeyOFT,Hash> umap1;
	};

 unordered_set的封装:

    //这里给Hash函数的缺省值,因为要将仿函数的比较权放在封装的unordered_set中
	template<class K,class Hash=MKL::HashFunc<K>>
	class un_set
	{
	public:
         //仿函数比较,告诉底层的哈希桶,比较的值,将其key取出;
		struct setkeyOFT
		{
			K operator()(const K& key)
			{
				return key;
			}
		};
         //迭代器是封装哈希桶的迭代器;
		typedef typename MKL::HashBucket<K, K, setkeyOFT,Hash>::const_iterator iterator;


        //剩下的成员函数全部调用哈希桶中的成员函数;
		bool Insert(const K& key)
		{
			return uset1.Insert(key);
		}

		iterator begin()
		{
			return uset1.begin();
		}

		iterator end()
		{
			return uset1.end();
		}


		 Find(const K& key)
		{
			return uset1.Find(key);
		}

		bool Erase(const K& key)
		{
			return uset1.Erase(key);
		}

	private:
        //底层用哈希桶创建
		MKL::HashBucket<K, K, setkeyOFT,Hash> uset1;
	};

同时注意,用哈希桶的迭代器封装unordered_set/map的迭代器时,要记得加typename,告诉编译器这是一个类型而不是静态变量。

///

3.2:unordered_map的operator[]:

和map一样,既然要实现operator[],那么我们需要改一下插入和查找,让插入的返回类型是键值对pair<iterator,bool>;让查找的返回类型为迭代器;

 

 首先,修改哈希表中的Find,让其返回迭代器,如果存在,返回key所在位置的迭代器,如果不存在,返回末尾的迭代器。

然后修改哈希表的插入接口,返回值改为键值对pair<iterator,bool>;

同时,既然底层的插入查找的返回值改变了,那么unordered_set/map的插入查找也要改:

 

这样我们就可以实现unordered_mapd的operator[],具体如下所示:

下面给出一个示例看看能不能跑通:

 通过测试用例可以看出,operator[]基本上没有问题了;

///

3.3:const_iterator:

当我们在自己封装的unordered_set中试着修改迭代器的值时,发现可以修改,这与我们的要求的相悖的,set的值是不能被修改的,所以,我们去学一学源码是如何做的,

 从STL源码中可以看到,const迭代器和普通迭代器不是同一个类,而是又重新创建了一个const_iterator类,并不是和我们之前红黑树封装set,map那样,复用一个类,这是为什么呢?先来看看和之前一样复用一个类会发生什么?

 

 显然,又发生了和set,map封装时,一样的问题,底层哈希桶的iterator是普通迭代器,而unordered_set中,K值不允许修改,所以普通迭代器是const迭代器,const迭代器也是const迭代器,返回值与返回类型不一样,普通迭代器不能转换为const迭代器;

因此,我们现在有两种解决方法:

1:像STL库中那样,const迭代器与普通迭代器分开写,当是普通迭代器时,用的就是普通迭代器,当是const迭代器时,用的就是const迭代器,

2:像红黑树中那样的解决,再创建一个构造函数,当普通迭代器调用时,就是拿普通迭代器去拷贝构造,当是const迭代器时,就是拿普通迭代器去构造const迭代器;

这里,我们还是和红黑树那样,用普通迭代器构建const迭代器的方法解决这个问题,具体如下所示:

 //

3.4:仿函数:

前面说过,底层的类模板和迭代器的类模板中的仿函数keyOFT,Hash函数都没有给缺省值,这是为什么呢?

因为,我们不能在底层的哈希桶里面写死,要不然当传自定义类型的类时,如日期类时,无法通过底层写死的仿函数去比较,

我们应该将仿函数的实现与比较权放在封装的unordered_set/map中,这样,不论当你封装的unordered_set/map处理什么样的类型,在unordered_set/map写相应的仿函数,就可以通过仿函数进行比较;这里就不给示例了,有兴趣可以自己搞一个自定义类型试一试;

//////

四;总结:

在封装unordered_set/map时,一定要特别注意类模板的使用,因为封装时 类模板被大量使用,而且迭代器也是我们的重点,了解迭代器的构造以及++操作,同时仿函数也举足轻重,

总结出仿函数取key的结论:

        一个类型要做unordered_set/map的key,要满足:有支持取模的仿函数+支持==的运算符
        一个类型要做set/map的key,要满足:支持<的比较;,

还要知道两个类在互相typedef时,需要有类的前置声明。一个类访问其他类中的私有成员的两种方法(公有成员函数和友元),总而言之,对于现阶段来说,很多的细节和知识点是非常有意义的;

至此,封装unordered_set/map的全过程就到此结束,我将我封装时的代码放在下面;

底层哈希桶:

#pragma once
#include <iostream>
#include <vector>

using namespace std;



namespace MKL
{
	template<class T>//只有一个模板参数
	struct HashData
	{
		HashData<T>* _next;//指向下一个节点
		T _Data;//桶中的数据

		//节点构造
		HashData(const T& data)
			:_next(nullptr)
			, _Data(data)
		{

		}
	};

    //默认支持的仿函数比较,整形
	template<class K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return key;
		}
	};

    //模版特化,默认支持的仿函数比较,字符串;
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& s)
		{
			size_t hashi = 0;
			for (auto e : s)
			{
				hashi = hashi + e;
				hashi *= 31;
			}
			return hashi;
		}
	};
	

    //这是显示调用的字符串仿函数,因为需要显示调用,不如将其模板特化,就不用显示调用了
	//struct HashFuncstring
	//{
	//	size_t operator()(const string& s)
	//	{
	//		size_t hashi = 0;
	//		for (auto e : s)
	//		{
	//			hashi = hashi + e;
	//			hashi *= 31;
	//		}
	//		return hashi;
	//	}
	//};


    //迭代器:
	template<class K, class T, class keyOFT, class Hash>
	class HashBucket;

	template<class K, class T,class Ref,class Ptr, class keyOFT, class Hash>
	struct hash_iterator
	{
		typedef HashData<T> node;
		typedef hash_iterator<K, T,Ref,Ptr, keyOFT, Hash> self;
		typedef HashBucket<K, T, keyOFT, Hash> HB;

		typedef hash_iterator<K, T,T&,T*, keyOFT, Hash> iterator;//始终构建一个普通迭代器

		//node* _node;
		//const HB* _hb;

		//hash_iterator(node* node, const HB* hb)
		//	:_node(node)
		//	, _hb(hb)
		//{

		//}

		node* _node;
		HB* _hb;

		hash_iterator(node* node, HB* hb)
			:_node(node)
			,_hb(hb)
		{

		}

	    hash_iterator(const iterator& it)//用普通迭代器构造const迭代器
			:_node(it._node)
			, _hb(it._hb)
		{

		}

		Ref operator*()
		{
			return _node->_Data;
		}

		Ptr operator->()
		{
			return &_node->_Data;
		}

		bool operator!=(const self& s)
		{
			return _node != s._node;
		}

		self& operator++()
		{
			if (_node->_next != nullptr)//如果当前位置的下一个位置不为空;
			{
				_node = _node->_next;//那么迭代器it++就指向其下一个节点;
			}
			else//如果当前位置的下一个位置为空,需要算出下一个桶的位置;
			{
				keyOFT kot;//仿函数,取出传过来的数据的K值;
				Hash hash;//通过哈希函数取整数,方便除留取余;
				size_t hashi = hash(kot(_node->_Data)) % _hb->_tables.size();//算出当前桶的位置;
				++hashi;//位置+1,找到下一个桶;
				while (hashi < _hb->_tables.size())
				{
					if (_hb->_tables[hashi] != nullptr)//如果后面不是一个空桶,
					{
						_node = _hb->_tables[hashi];//让迭代器it变到这个位置上;然后退出
						break;
					}
					else//如果后面是空桶
					{
						++hashi;//向后再继续找桶;
					}
				}
				if (hashi == _hb->_tables.size())//如果找到最后全是空桶;
				{
					_node = nullptr;//迭代器就直接指向空,迭代器结束;
				}
			}
			return *this;//返回此时迭代器的位置;
		}
	};



    //哈希桶:
	template<class K, class T, class keyOFT,class Hash >
	class HashBucket
	{
		typedef HashData<T> node;
	public:

		template<class K, class T, class Ref, class Ptr, class keyOFT, class Hash>
		friend struct hash_iterator;

		typedef hash_iterator<K, T, T& ,T*, keyOFT, Hash> iterator;
		typedef hash_iterator<K, T, const T&,const T*, keyOFT, Hash> const_iterator;

		iterator begin()
		{
			node* cur = nullptr;
			for (size_t i = 0; i < _tables.size(); ++i)//找到哈希桶中第一个不为空的节点,
			{
				if (_tables[i] != nullptr)
				{
					cur = _tables[i];
					break;
				}
			}
			return iterator(cur, this);//返回第一个不为空的节点,同时迭代器需要哈希桶指针,所以将this指针传过去
		}

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

		const_iterator begin() const
		{
			node* cur = nullptr;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i] != nullptr)
				{
					cur = _tables[i];
					break;
				}
			}
			return const_iterator(cur, this);
		}

		const_iterator end() const 
		{
			return const_iterator(nullptr, this);
		}

		pair<iterator,bool> Insert(const T& data)//operator[]需要插入的时候返回键值对
		{
			keyOFT kot;//仿函数,取出传过来的数据的K值;

			iterator it = Find(kot(data));//是否已经存在
			//如果要找的值已经存在于桶中
			if (it != end())
			{
				return make_pair(it,false);//返回当前的迭代器
			}

			Hash hash;//通过哈希函数取整数,方便除留取余;
			if (_tables.size() == n)
			{
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				vector<node*> newhb;
				newhb.resize(newsize, nullptr);
				for (auto& cur : _tables)
				{
					while (cur != nullptr)
					{
						node* next = cur->_next;
						size_t hashi = hash(kot(cur->_Data)) % newhb.size();
						cur->_next = newhb[hashi];
						newhb[hashi] = cur;
						cur = next;
					}
				}
				_tables.swap(newhb);
			}
			size_t hashi = hash(kot(data)) % _tables.size();
			node* newnode = new node(data);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++n;
			return make_pair(iterator(newnode,this), true);
		}

		iterator Find(const K& key)
		{
			if (_tables.size() == 0)//如果桶中没有节点
			{
				return end();//直接返回空
			}
			keyOFT kot;//仿函数,取出传过来的数据的K值;

			Hash hash;//通过哈希函数取整数,方便除留取余;
			size_t hashi = hash(key) % _tables.size();//找到它映射在桶里面的位置;
			node* cur = _tables[hashi];//找到映射到桶里的头节点
			while (cur != nullptr)
			{
				if (kot(cur->_Data) == key)//如果找到了
				{
					return iterator(cur,this);//返回当前的位置,
				}
				else///没有找到,继续向后找
				{
					cur = cur->_next;
				}
			}
			return end();
		}

		bool Erase(const K& key)
		{
			keyOFT kot;//获取K值,map,与set的后一个类型不同;
			Hash hash;//仿函数比较;
			size_t hashi = hash(key) % _tables.size();
			node* prev = nullptr;
			node* cur = _tables[hashi];
			while (cur != nullptr)
			{
				if (kot(cur->_Data) == key)
				{
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					cur = nullptr;
					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			--n;
			return false;
		}

	private:
		vector<node*>_tables;//指针数组
		size_t n = 0;//记录数组中的元素个数
	};
}

封装的unordered_set:

#pragma once
#include "hash.h"
#include "hash1.h"


namespace un_set
{


	template<class K,class Hash=MKL::HashFunc<K>>
	class un_set
	{
	public:
		struct setkeyOFT
		{
			K operator()(const K& key)
			{
				return key;
			}
		};

		typedef typename MKL::HashBucket<K, K, setkeyOFT,Hash>::const_iterator iterator;
		typedef typename MKL::HashBucket<K, K, setkeyOFT, Hash>::const_iterator const_iterator;

		//返回值为键值对
		pair<iterator,bool> Insert(const K& key)
		{
			return uset1.Insert(key);
		}

		//返回值为迭代器
		iterator Find(const K& key)
		{
			return uset1.Find(key);
		}


		iterator begin()
		{
			return uset1.begin();
		}

		iterator end()
		{
			return uset1.end();
		}

		const_iterator begin() const
		{
			return uset1.begin();
		}

		const_iterator end() const
		{
			return uset1.end();
		}

		bool Erase(const K& key)
		{
			return uset1.Erase(key);
		}

	private:
		MKL::HashBucket<K, K, setkeyOFT,Hash> uset1;
	};

	void un_settest1()
	{
		un_set<int> us1;
		us1.Insert(1);
		us1.Insert(2);
		us1.Insert(4);
		us1.Insert(3);
		us1.Insert(5);

		un_set<int>::iterator it = us1.begin();
		while (it != us1.end())
		{
			cout << *(it) << " ";
			++it;
		}
		cout << endl;
	}


	void un_settest2()
	{
		un_set<int> us1;
		us1.Insert(2);
		us1.Insert(3);
		us1.Insert(1);
		us1.Insert(2);
		un_set<int>::iterator it = us1.begin();
		while (it != us1.end())
		{
			//*(it) = 1;
			cout << *(it) << " ";
			++it;
		}
	}
}

封装的unordered_map:

#pragma once
#include "hash.h"
#include "hash1.h"

namespace un_map
{

	template<class K,class V,class Hash= MKL::HashFunc<K>>
	class un_map
	{
	public:
		struct mapkeyOFT
		{
			K operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};

		typedef typename MKL::HashBucket<K, pair<const K, V>, mapkeyOFT,Hash>::iterator iterator;
		typedef typename MKL::HashBucket<K, pair<const K, V>, mapkeyOFT, Hash>::const_iterator const_iterator;


		iterator begin()
		{
			return umap1.begin();
		}

		iterator end()
		{
			return umap1.end();
		}

		const_iterator begin() const 
		{
			return umap1.begin();
		}

		const_iterator end() const
		{
			return umap1.end();
		}

		//返回值为键值对
		pair<iterator, bool> Insert(const pair<K, V>& kv)
		{
			return umap1.Insert(kv);
		}

		//返回值为迭代器
		iterator Find(const K& key)
		{
			return umap1.Find(key);
		}

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

		bool Erase(const K& key)
		{
			return umap1.Erase(key);
		}
		
	private:
		MKL::HashBucket<K, pair<const K, V>, mapkeyOFT,Hash> umap1;
	};

	void un_maptest1()
	{
		un_map<int, int> um1;
		um1.Insert(make_pair(1, 1));
		um1.Insert(make_pair(2, 2));
		um1.Insert(make_pair(4, 4));
		um1.Insert(make_pair(3, 3));
		um1.Insert(make_pair(5, 5));
		um1.Insert(make_pair(11, 11));
		um1.Insert(make_pair(12, 12));


		un_map<int, int>::iterator it = um1.begin();
		while (it != um1.end())
		{
			cout << it->first << ":" << it->second << " ";
			++it;
		}
		cout << endl;
	}

	void un_maptest2()
	{
		un_map<string, int> um2;
		string  arr[] = { "西瓜","苹果","菠萝","梨","西瓜", "西瓜", "菠萝","苹果","梨","梨" };
		for (auto& e : arr)
		{
			um2[e]++;
		}
		for (auto e : um2)
		{
			cout << e.first << ":" << e.second << " ";
		}
		cout << endl;
	}



	class Date
	{
		friend struct Datefunc;
	public:
		Date(int year = 1900, int month = 1, int day = 1)
			: _year(year)
			, _month(month)
			, _day(day)
		{}
		bool operator<(const Date& d)const
		{
			return (_year < d._year) ||
				(_year == d._year && _month < d._month) ||
				(_year == d._year && _month == d._month && _day < d._day);
		}
		bool operator>(const Date& d)const
		{
			return (_year > d._year) ||
				(_year == d._year && _month > d._month) ||
				(_year == d._year && _month == d._month && _day > d._day);
		}

		//一个类型要做unordered_set/map的key,要满足:有支持取模的仿函数+支持==的运算符
		//一个类型要做set/map的key,要满足:支持<的比较;
		bool operator==(const Date& d)const
		{
			return _year == d._year 
				&& _month == d._month 
				&& _day == d._day;
		}

		friend ostream& operator<<(ostream& _cout, const Date& d)
		{
			_cout << d._year << "-" << d._month << "-" << d._day;
			return _cout;
		}

	private:
		int _year;
		int _month;
		int _day;
	};


	struct Datefunc
	{
		size_t operator()(const Date& d)
		{
			size_t hashi = 0;
			hashi = hashi + d._year;
			hashi *= 31;
			hashi = hashi + d._month;
			hashi *= 31;
			hashi = hashi + d._day;
			hashi *= 31;
			return hashi;
		}
	};

	void un_maptest3()
	{
		Date d1(2023, 7, 1);
		Date d2(2023, 7, 2);
		Date d3(2023, 7, 3);
		Date d4(2023, 7, 2);
		Date d5(2023, 7, 2);
		Date d6(2023, 7, 1);

		Date arr[] = { d1,d2,d3,d4,d5,d6 };

		un_map<Date, int, Datefunc> um3;
		for (auto& e : arr)
		{
			um3[e]++;
		}
		for (auto& e : um3)
		{
			cout << e.first << ":" << e.second << " ";
		}
		cout << endl;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值