【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)

⭐️今天我要给大家介绍两个新的容器,它们都是关联式容器——map和set,我会先介绍它们的使用方法,然后带大家用上一篇博客中的红黑树封装出map和set。
⭐️博客代码已上传至gitee:https://gitee.com/byte-binxin/cpp-class-code

目录

  • 关联式容器
  • 键值对
  • set
    • set的介绍
    • set的使用
      • set的几个构造函数
      • set的迭代器
      • set的大小和容量
      • set的插入和删除
      • 非成员函数
  • map
    • map的介绍
    • map的用法
      • map的几个构造函数
      • map的迭代器
      • 大小和容量
      • 插入和删除
      • operator[](重点)
  • multiset
    • 介绍
    • 用法
  • multimap
    • 介绍
    • 用法
  • 用一颗红黑树封装出map和set
    • 对红黑树进行改造
    • 封装map和set
  • 总结


关联式容器

关联式容器也是用来存储数据的,与序列式容器(如vector、list等)不同的是,其里面存储的是结构的键值对,在数据检索时比序列式容器效率更高。今天要介绍的的四种容器是树形关联式容器:map、set、multimap和multiset。它们的底层都是用红黑树来实现的,容器中的元素是一个有序的序列。

键值对

键值对: 用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。
【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第1张图片
STL中键值对定义如下:

template <class T1, class T2>
struct pair
{
	typedef T1 first_type;
	typedef T2 second_type;
	T1 first;
	T2 second;
	pair(): first(T1()), second(T2())
	{}
	pair(const T1& a, const T2& b): first(a), second(b)
	{}
};

一般的两种方式创建键值对对象:
第一种: pair(x, y) 使用构造函数的方式构造一个匿名对象
第二种: make_pair(x, y) 是一个函数模板,其中返回的是一个pair的匿名对象
【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第2张图片
实例演示:

void test()
{
	// pair(T1(), T2()) 通过构造函数构造一个匿名对象
	// make_pair(T1() , T2())  是一个模板函数,返回的是pair的匿名对象,用起来更方便
	pair<int, int>(1, 1);
	make_pair(1, 1);
}

set

set的介绍

【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第3张图片
总结几点:

  1. set是按照一定次序存储元素的容器
  2. 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
  3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
  4. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代。
  5. set在底层是用红黑树实现的。

set的使用

set的几个构造函数

【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第4张图片

  1. 构造函数: set (const Compare& comp = Compare(), const Allocator& =Allocator() ); 构造空的set容器
  2. 拷贝构造: set (const set& x);

set的迭代器

和之前几个容器一样,有正向迭代器和反向迭代器,还有const迭代器。这里用法也和之前的类似,不过多介绍。下面会给大家演示。
【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第5张图片

set的大小和容量

在这里插入图片描述

  1. empty: 判断set是否为空
  2. size: 返回set中元素的个数

set的插入和删除

【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第6张图片

  1. insert: pair insert (const value_type& val); 插入元素,返回值是键值对,其中如果第二个参数为true,那么第一个参数是插入元素的迭代器的位置,为false的话,第一个参数就是已经存在元素的迭代器的位置
  2. erase: void erase (iterator position); 删除position位置的元素

非成员函数

【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第7张图片
这里只介绍find一个。

find 查找某个元素。这里find的时间复杂度为O(logN),比算法中的find(时间复杂是O(N))更高效,所以set容器一般室友自己的find进行查找。
在这里插入图片描述

实例演示:
实例1 插入、删除、查找和迭代器遍历

void test_set1()
{
	
	set<int> s;

	s.insert(5);
	s.insert(1);
	s.insert(6);
	s.insert(3);
	s.insert(6);
	s.insert(s.begin(), 10);

	set<int>::iterator pos = s.find(15);// 底层是搜索二叉树,时间复杂度是O(logN)
	// set::iterator pos = find(s.begin(), s.end(), 3);// 遍历查找,时间复杂度是O(N)
	if (pos != s.end())
	{
		// cout << *pos << endl; 
		s.erase(pos);// 没有会报错
	}
	//s.erase(1); // 没找到不会报错

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

	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
}

代码运行结果如下:
【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第8张图片
实例2 下面是对算法中的find和set中的find进行效率比较的小测试

void test_set2()
{
	srand((size_t)time(nullptr));
	set<int> s;
	for (size_t i = 0; i < 10000; ++i)
	{
		s.insert(rand());
	}
	cout << "个数:" << s.size() << endl;
	int begin1 = clock();
	for (auto e : s)
	{
		s.find(e);
	}
	int end1 = clock();

	int begin2 = clock();
	for (auto e : s)
	{
		find(s.begin(), s.end(), e);
	}
	int end2 = clock();

	cout << "用时1:" << end1 - begin1 << "ms" << endl;
	cout << "用时2:" << end2 - begin2 << "ms" << endl;
}

代码运行结果如下:
【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第9张图片

map

map的介绍

【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第10张图片
总结以下几点:

  1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
  2. 在内部,map中的元素总是按照键值key进行比较排序的。
  3. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
  4. map支持下标访问符,支持operator[],即在[]中放入key,就可以找到与key对应的value。
  5. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))

map的用法

map的几个构造函数

【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第11张图片

  1. 构造函数: map() 构造一个空的map容器
  2. 拷贝构造: map(const map& m);

map的迭代器

和set是类似的,不过多介绍,后面有实例演示。
【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第12张图片

大小和容量

在这里插入图片描述

  1. empty 判断绒是否为空
  2. size 返回容器中元素个数

插入和删除

【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第13张图片

  1. insert: pair insert (const value_type& x ); 返回的是一个键值对,和set的原理一样
  2. erase: void erase (iterator position); 在pos删除元素

operator[](重点)

在这里插入图片描述

operator[]函数的定义如下:

mapped_type& operator[] (const key_type& k) 
{ 
	return (*((this->insert(make_pair(k,mapped_type()))).first)).second; 
}

其中,mapped_type是KV模型中V的类型,也就是返回value值得引用。我们可以对这个value进行修改。
分析:((this->insert(make_pair(k,mapped_type()))).first这是一个迭代器,迭代器指向键值对中的第二个元素就是value。所以operato[]的底层是用到了插入,同时可以对value进行修改和访问。
总结: operator[]的三个用处:插入、修改和访问。

实例演示:
实例1 用map统计水果个数,以下用了3种方式,同时还对operator的几种作用进行了说明

void test_map2()
{
	map<string, int> countMap;

	string fruitArray[] = { "西瓜","桃子","香蕉","桃子","苹果","西瓜", "香蕉","苹果", "香蕉","西瓜","桃子", "西瓜", "西瓜","桃子",
	"桃子", "桃子", "西瓜","桃子","香蕉","桃子","苹果","西瓜" };

	// 方法一
	//for (auto& e : fruitArray)
	//{
	//	map::iterator ret = countMap.find(e);
	//	if (ret != countMap.end())// 找到了,说明容器里有,第二个参数加1即可
	//	{
	//		++ret->second;
	//	}
	//	else
	//	{
	//		// 没有就插入,第二个参数记为1
	//		countMap.insert(make_pair(e, 1));
	//	}
	//}

	// 方法二
	//for (auto& e : fruitArray)
	//{
	//	
	//	// countMap无此元素,pair的第一个参数返回新的迭代器,第二个参数返回true
	//	// countMap有此元素,pair的第一个参数返回旧的迭代器,第二个参数返回false
	//	pair::iterator, bool> ret = countMap.insert(make_pair(e, 1));

	//	// 插入失败,只需要++value即可 

	//	if (ret.second == false)
	//	{
	//		++ret.first->second;
	//	}
	//}
	// 方法三
	for (auto& e : fruitArray)
	{
		// mapped_type& operator[] (const key_type& k) ;
		// mapped_type& operator[] (const key_type& k) { return (*((this->insert(make_pair(k,mapped_type()))).first)).second; }
		// ((this->insert(make_pair(k,mapped_type()))).first  迭代器
		// (*( (this->insert(make_pair(k,mapped_type()))).first )).second   返回value的值的引用 operator[]的原型
		countMap[e]++;// 有插入、查找和修改的功能   返回value的值的引用 
	}

	countMap["梨子"];// 插入
	countMap["梨子"] = 5;// 修改
	cout << countMap["梨子"] << endl;// 查找  一般不会用 operator[] 来进行查找,因为没找到会进行插入
	countMap["哈密瓜"] = 3;// 插入+修改

	for (auto& e : countMap)
	{
		cout << e.first << ":" << e.second << endl;
	}
}

代码运行结果如下:
【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第14张图片

实例2 测试map的插入、删除和迭代器的使用

void test_map1()
{
	map<int, int> m;

	// 键值对
	// pair(T1(), T2()) 通过构造函数构造一个匿名对象
	// make_pair(T1() , T2())  是一个模板函数,返回的是pair的匿名对象,用起来更方便
	//m.insert(pair(1, 1));
	m.insert(make_pair(1, 1));
	m.insert(pair<int, int>(2, 2));
	m.insert(pair<int, int>(3, 3));
	m.insert(pair<int, int>(4, 4));

	map<int, int>::iterator it = m.begin();
	while (it != m.end())
	{
		// *it  返回 值得引用
		cout << (*it).first << ":" << (*it).second << endl;
		// it-> 返回 值的地址  -> 解引用访问两个元素
		// cout << it->first << ":" << it->second << endl;
		++it;
	}
	// e是自定义类型,传引用防止有拷贝构造发生
	for (auto& e : m)
	{
		cout << e.first << ":" << e.second << endl;
	}
}

代码运行结果如下:
【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第15张图片

multiset

介绍

【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第16张图片

总结几点:

  1. multiset是按照特定顺序存储元素的容器,其中元素是可以重复的。
  2. 底层是红黑树,和set的特点基本类似,只是multiset可以存放多个相同的值。

用法

与set的接口基本相似,直接上演示。
实例演示:

void test_multiset()
{
	multiset<int> ms;
	// multiset 和 set 的接口基本一致,multiset可以插入重复的
	ms.insert(1);
	ms.insert(5);
	ms.insert(3);
	ms.insert(2);
	ms.insert(3);

	multiset<int>::iterator pos = ms.find(3);// 返回的是第一个3
	cout << *pos << endl;
	++pos;
	cout << *pos << endl;
	++pos;
	cout << *pos << endl;
	++pos;

	for (auto e : ms)
	{
		cout << e << " ";
	}
	cout << endl;
}

代码运行结果如下:
【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第17张图片

multimap

介绍

【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第18张图片
总结几点:

  1. multimaps是关联式容器,它按照特定的顺序,存储由key和value映射成的键值对,其中多个键值对之间的key是可以重复的。
  2. 底层也是红黑树,和map的性质基本类似

用法

实例演示:

void test_multimap()
{
	// multimap 和 map 的区别:可以有不同的key 
	// 不支持operator[]  因为有多个key时,不知道返回哪个key对应的value的引用
	multimap<int, int> mm;

	mm.insert(make_pair(1, 1));
	mm.insert(make_pair(1, 2));
	mm.insert(make_pair(1, 3));
	mm.insert(make_pair(2, 1));
	mm.insert(make_pair(2, 2));

	for (auto& e : mm)
	{
		cout << e.first << ":" << e.second << endl;
	}

}

代码运行结果如下:
【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第19张图片

用一颗红黑树封装出map和set

对红黑树进行改造

这里是我上一篇关于红黑树的博客——红黑树
这里红黑树完整代码——红黑树完整代码
大概框架:

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
private:
	Node* _root = nullptr;
};

这里的红黑树是一个KV模型,我们要用这个红黑树同时封装出map和set两个容器,直接使用这棵红黑树显然是不行的,set属于是K模型的容器,我们要做怎样的改造才能够同时封装出这两个容器呢?
这里我们参考STL源码的处理方式,下面是源码的部分截图:
【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第20张图片
可以看出这里,红黑树的第一个类模板参数和之前是一样的,但是第二个参数value和之前是不一样的,这里的直接把value存放在节点里面,通过map和set构造红黑树可以看出value存的是pair或K,对于map而言,value存的是pair;对于set而言,value存的是K。所以这里的红黑树暂时可以这样改造:

template<class K, class T>
class RBTree
{
	typedef RBTreeNode<T> Node;// 根据T的类型判断是map还是set 可能是pair或K
public:
private:
	Node* _root = nullptr;
};

同时,我们还会发现,上面的红黑树的类模板中有第三个参数是什么呢?
为了获取value中的key值,我们可以让map和set各自传一个仿函数过来,以便获得各自的key值。
两个仿函数如下:

template<class K, class V>
class map
{
	struct MAPOFV
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
};
template<class K>
class set
{
	struct SETOFV
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
};

第四个类模板参数是一个空间配置器,这里也不实现了,我们实现主体内容即可。后面会介绍空间配置器相关内容。
迭代器的实现
其中operato++就是通过非递归中序遍历的方式走一遍红黑树,走到空就结束

template<class T, class Ptr, class Ref>
struct __rbtree_iterator
{
	typedef __rbtree_iterator<T, Ptr, Ref> Self;
	typedef RBTreeNode<T> Node;

	Node* _node;

	__rbtree_iterator(Node* node)
		:_node(node)
	{}

	// 返回值(data)的地址
	Ptr operator->()
	{
		return &_node->_data;
	}
	// 返回值(data)的引用
	Ref operator*()
	{
		return _node->_data;
	}
	Self& operator++()
	{
		// 1.先判断右子树是否为空,不为空就去右子树找最左节点
		// 2.右子树为空,去找孩子是其左孩子的祖先
		Node* cur = _node;
		if (cur->_right)
		{
			cur = cur->_right;
			while (cur->_left)
			{
				cur = cur->_left;
			}
			
		}
		else
		{
			Node* parent = cur->_parent;
			while (parent && parent->_right == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			cur = parent;
		}
		_node = cur;
		return *this;
	}
	Self& operator--()
	{
		// 1.先判断左子树是否为空,不为空就去左子树找最右节点
		// 2.右子树为空,去找孩子是其右孩子的祖先
		Node* cur = _node;
		if (cur->_left)
		{
			cur = cur->_left;
			while (cur->_right)
			{
				cur = cur->_right;
			}
		}
		else
		{
			Node* parent = cur->_parent;
			while (parent && parent->_left == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			cur = parent;
		}
		_node = cur;
		return *this;
	}
	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}
};

红黑树修改后的代码: (留下了主体内容,有些部分删去了,不然有点长,可以去我的gitee上面看)

#pragma once
#include 
#include 
#include 
using namespace std;

enum Color
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;

	T _data;
	Color _color;

	RBTreeNode(const T& data, Color color = RED)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _color(color)
	{}
};

template<class T, class Ptr, class Ref>
struct __rbtree_iterator
{
	typedef __rbtree_iterator<T, Ptr, Ref> Self;
	typedef RBTreeNode<T> Node;

	Node* _node;

	__rbtree_iterator(Node* node)
		:_node(node)
	{}

	// 返回值(data)的地址
	Ptr operator->()
	{
		return &_node->_data;
	}
	// 返回值(data)的引用
	Ref operator*()
	{
		return _node->_data;
	}
	Self& operator++()
	{
		// 1.先判断右子树是否为空,不为空就去右子树找最左节点
		// 2.右子树为空,去找孩子是其左孩子的祖先
		Node* cur = _node;
		if (cur->_right)
		{
			cur = cur->_right;
			while (cur->_left)
			{
				cur = cur->_left;
			}
			
		}
		else
		{
			Node* parent = cur->_parent;
			while (parent && parent->_right == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			cur = parent;
		}
		_node = cur;
		return *this;
	}
	Self& operator--()
	{
		// 1.先判断左子树是否为空,不为空就去左子树找最右节点
		// 2.右子树为空,去找孩子是其右孩子的祖先
		Node* cur = _node;
		if (cur->_left)
		{
			cur = cur->_left;
			while (cur->_right)
			{
				cur = cur->_right;
			}
		}
		else
		{
			Node* parent = cur->_parent;
			while (parent && parent->_left == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			cur = parent;
		}
		_node = cur;
		return *this;
	}
	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}
}; 


// KOFV是一个仿函数,返回的是对应类型的值 map返回pair中的key  set也返回key
template<class K, class T, class KOFV>
class RBTree
{
	typedef RBTreeNode<T> Node;// 根据T的类型判断是map还是set 可能是pair或K
public:
	typedef __rbtree_iterator<T, T*, T&> iterator;
	typedef __rbtree_iterator<T, const T*, const T&> const_iterator;

	iterator begin()
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}

		return iterator(cur);
	}
	iterator end()
	{
		return iterator(nullptr);
	}
	iterator begin() const
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}

		return const_iterator(cur);
	}
	iterator end() const
	{
		return const_iterator(nullptr);
	}

	pair<iterator, bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data, BLACK);// 根节点默认给黑
			return make_pair(iterator(_root), true);
		}

		Node* cur = _root;
		Node* parent = nullptr;
		KOFV kofv;
		while (cur)
		{
			parent = cur;
			if (kofv(data) < kofv(cur->_data))
				cur = cur->_left;
			else if (kofv(data) > kofv(cur->_data))
				cur = cur->_right;
			else
				return make_pair(iterator(cur), false);;
		}
		// 节点默认给红节点,带来的影响更小
		// 给黑节点的话会影响 每条路径的黑节点相同这条规则
		cur = new Node(data);
		Node* newnode = cur;
		if (kofv(cur->_data) < kofv(parent->_data))
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
		}

		while (parent && parent->_color == RED)
		{
			Node* grandfather = parent->_parent;
			// 左边
			if (grandfather->_left == parent)
			{
				// 红黑色的条件关键看叔叔
				Node* uncle = grandfather->_right;
				// u存在且为红
				if (uncle && uncle->_color == RED)
				{
					// 调整 p和u改成黑,g改成红
					parent->_color = uncle->_color = BLACK;
					grandfather->_color = RED;

					// 迭代  向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else// u存在为黑/u不存在
				{
					// 折线用一个左单旋处理 1.p左单旋 2.g右单旋 3.把cur改成黑,g改成红   cur p g 三个是一条折线
					if (cur == parent->_right)
					{
						RotateL(parent);
						swap(parent, cur);
					}
					// 直线 cur p g 把p改成黑,g改成红
					// 右单旋  有可能是第三种情况
					RotateR(grandfather);

					parent->_color = BLACK;
					grandfather->_color = RED;
				}
			}
			// uncle在左边
			else
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					grandfather->_color = RED;

					// 迭代  向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					// 折线用一个右单旋处理  g p cur  g变红p边黑
					if (cur == parent->_left)
					{
						RotateR(parent);
						swap(parent, cur);
					}

					// 直线 g p cur 把p改成黑,g改成红
					// 左单旋  有可能是第三种情况
					RotateL(grandfather);

					parent->_color = BLACK;
					grandfather->_color = RED;
				}
			}
		}

		_root->_color = BLACK;
		return make_pair(iterator(newnode), true);
	}
	bool Erase(const K& key)
	{
		// 如果树为空,删除失败
		if (_root == nullptr)
			return false;

		KOFV kofv;
		Node* parent = nullptr;
		Node* cur = _root;
		Node* delNode = nullptr;
		Node* delNodeParent = nullptr;
		while (cur)
		{
			// 小于往左边走
			if (key < kofv(cur->_data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > kofv(cur->_data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				// 删除...
			}
		}
		
	}
	iterator Find(const K& key)
	{
		if (_root == nullptr)
			return iterator(nullptr);
		KOFV kofv;
		Node* cur = _root;
		while (cur)
		{
			// 小于往左走
			if (key < kofv(cur->_data))
			{
				cur = cur->_left;
			}
			// 大于往右走
			else if (key > kofv(cur->_data))
			{
				cur = cur->_right;
			}
			else
			{
				// 找到了
				return iterator(cur);
			}
		}

		return iterator(nullptr);
	}
private:
	Node* _root = nullptr;
};

总结(改造的几个点):

  1. 把类模板参数的value存放K或pair
  2. 第三个类模板参数是仿函数,可以获取第二个类模板参数中的key
  3. 增加了迭代器,重载了operator[],具有STL中map中的operator[]一样的特性

封装map和set

template<class K, class V>
class map
{
	struct MAPOFV
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
	typedef RBTree<K, pair<K, V>, MAPOFV> RBTree;
public:
	// typename 告诉编译器这只是一个名字,暂时不用堆模板进行实例化
	typedef typename RBTree::iterator iterator;
	typedef typename RBTree::const_iterator const_iterator;

	iterator begin()
	{
		return _rbt.begin();
	}
	iterator end()
	{
		return _rbt.end();
	}
	const_iterator begin() const
	{
		return _rbt.begin();
	}
	const_iterator end() const
	{
		return _rbt.end();
	}

	pair<iterator, bool> insert(const pair<K, V>& kv)
	{
		return _rbt.Insert(kv);
	}
	bool erase(const K& key)
	{
		return _rbt.Erase(key);
	}
	V& operator[](const K& key)
	{
		pair<iterator, bool> ret = insert(make_pair(key, V()));
		return ret.first->second;
	}
private:
	RBTree _rbt;
};
---------------------------------------------------------------------------
template<class K>
class set
{
	struct SETOFV
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
	typedef RBTree<K, K, SETOFV> RBTree;
public:
	// typename 告诉编译器这只是一个名字,暂时不用堆模板进行实例化
	typedef typename RBTree::iterator iterator;
	typedef typename RBTree::const_iterator const_iterator;

	iterator begin()
	{
		return _rbt.begin();
	}
	iterator end()
	{
		return _rbt.end();
	}
	const_iterator begin() const
	{
		return _rbt.begin();
	}
	const_iterator end() const
	{
		return _rbt.end();
	}
	pair<iterator, bool> insert(const K& key)
	{
		return _rbt.Insert(key);
	}
	bool erase(const K& key)
	{
		return _rbt.Erase(key);
	}
private:
	RBTree _rbt;
};

测试map:

void test_map()
{
	map<string, int> countMap;
	string strArr[] = { "香蕉","水蜜桃","西瓜","苹果","香蕉" ,"西瓜","香蕉" ,"苹果","西瓜","苹果","苹果","香蕉" ,"水蜜桃" };

	for (auto& e : strArr)
	{
		countMap[e]++;
	}

	countMap["芒果"] = 10;
	countMap.erase("水蜜桃");
	countMap.erase("西瓜");
	countMap.erase("芒果");
	/*countMap.erase("香蕉");
	countMap.erase("香蕉");
	countMap.erase("苹果");*/

	for (auto& e : countMap)
	{
		cout << e.first << ":" << e.second << endl;
	}

}

代码运行结果如下:
【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第21张图片
测试set:

void test_set()
{
	set<int> s;

	int arr[] = { 1,3,2,3,4,5,1,5,5,8,3,3,2 };
	for (auto e : arr)
	{
		s.insert(e);
	}


	for (auto& e : s)
	{
		cout << e << endl;
	}

}

代码运行结果如下:
【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第22张图片

总结

map和set的内容就介绍到这,这一块相对还是比较复杂的,但是理解了就不会觉得很复杂,其中用到的东西都很巧妙,体现了泛型编程的特性。今天的内容就先介绍到这,喜欢的话,欢迎点赞支持~
【C++进阶】第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)_第23张图片

你可能感兴趣的:(C++篇,c++,开发语言,后端)