C++ 关联容器set | map | multiset | multimap

Posted WhiteShirtI

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ 关联容器set | map | multiset | multimap相关的知识,希望对你有一定的参考价值。

前情提要

根据应用场景的不桶,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树) 作为其底层结果,容器中的元素是一个有序的序列。他们都是底层都是通过<key, value>结构的键值对来存储的

C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构操作。vector封装数组,list封装了链表,map和set封装了二叉树等,在封装这些数据结构的时候,STL按照程序员的使用习惯,以成员函数方式提供的常用操作,如:插入、排序、删除、查找等。让用户在STL使用过程中,并不会感到陌生

set认识及使用

set中的key就是value,就是key=value的一种关联容器。也就是说存的内容不能一样,set具有天然的去重功能

特性

  1. set容器中不能存在重复元素,底层二叉树不允许存在重复元素
  2. 使用set的迭代器遍历set中的元素,可以得到有序序列,底层通过中序遍历得到
  3. set中插入元素时,只需要插入value即可,不需要构造键值对
  4. set中的元素不允许修改,二叉搜索树不允许修改

模板参数

在这里插入图片描述
T:set存放的数据类型
Compare:set元素存储顺序,默认less,为从小到大
Alloc:set中元素空间的管理方式,使用STL提供的空间配置器管理

构造函数

  1. explicit set (const key_compare& comp = key_compare(),
    const allocator_type& alloc = allocator_type());
  2. template < class InputIterator >
    set (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());
void test()
{
	vector<int> vec = { 4, 3, 9, 6, 3 };
	set<int> set1; //空set
	set<int> set2 = { vec.begin(), vec.end()};//通过迭代器创建set容器

	set<int> ::iterator it = set1.begin();//set迭代器
	while (it != set1.end())
	{
		cout << *it << " ";
		//*it = 10; error 不允许修改
		++it;
	}
}

测试:保存5个元素,实际保存4个元素,重复元素不保存,迭代器的遍历就是按照树的中序遍历得到的
在这里插入图片描述

大小容量

  • size() 求set元素个数
  • empty() 判断set是否为空

插入

pair<iterator,bool> insert (const value_type& val);
返回值pair<iterator,bool>插入数据的位置以及插入是否成功的数据

void test()
{
	vector<int> vec = { 4, 3, 9, 6, 3 };
	set<int> set2 = { vec.begin(), vec.end() };//通过迭代器创建set容器

	//ret.second表示pair对象第二个数据,ret.first表示pair对象第一个数据
	pair<set<int>::iterator, bool> ret = set2.insert(7);
	cout << ret.second << " " << *ret.first << endl;//插入成功,pair第一个数据是新数据的迭代器

	ret = set2.insert(7);
	cout << ret.second << " " << *ret.first << endl;//插入失败,pair第一个数据是已经存在的数据的迭代器
}

测试:
在这里插入图片描述
iterator insert (iterator position, const value_type& val);
该接口并非是说在该迭代器的位置前插入数据,其实和上一个插入是一样的

void printSet(const set<int>& set)
{
	for (auto& s : set)
		cout << s << " ";
	cout << endl;
}

void test()
{
	vector<int> vec = { 4, 3, 9, 6, 3 };
	set<int> set2 = { vec.begin(), vec.end() };
	printSet(set2);
	auto it = set2.end();
	set2.insert(it, 1);
	printSet(set2);
}

测试:
在这里插入图片描述

删除

size_type erase (const value_type& val);
返回值是删除的个数,存在则删除并返回1,不存在返回0

void erase (iterator position);

void erase (iterator first, iterator last);

void test()
{
	vector<int> vec = { 4, 3, 9, 6, 3, 10, 12, 50, 1 };
	set<int> set2 = { vec.begin(), vec.end() };
	set<int> ::iterator it = set2.begin();
	int ret = set2.erase(6);//删除成功返回1
	printSet(set2);
	cout << ret << endl;
	ret = set2.erase(5);//删除失败返回0
	printSet(set2);
	cout << ret << endl;
	set2.erase(it);//注意不能传非法位置:end()
	printSet(set2);
	set2.erase(++set2.begin(), --set2.end());//删除只剩头和尾两个数据
	printSet(set2);
}

测试:
在这里插入图片描述

查找

iterator find (const value_type& val) const;
找到返回数据的迭代器,找不到返回set容器的end()迭代器

void test()
{
	vector<int> vec = { 4, 3, 9, 6, 3, 10, 12, 50, 1 };
	set<int> set2 = { vec.begin(), vec.end() };
	set<int> ::iterator it = set2.begin();
	auto it = set2.find(3);
	cout << (it != set2.end()) << endl;
	auto it = set2.find(2);
	cout << (it != set2.end()) << endl;
}

测试:
在这里插入图片描述
size_type count (const value_type& val) const;
查看是否存在某个数据

void test()
{
	vector<int> vec = { 4, 3, 9, 6, 3, 10, 12, 50, 1 };
	set<int> set2 = { vec.begin(), vec.end() };
	cout << set2.count(4) << endl;
	cout << set2.count(40) << endl;
}

测试:
在这里插入图片描述

其他

set默认是一个递增的序列,我们可以手动修改成递减的序列

void printSet(const set<int, greater<int>>& set)
{
	for (auto& s : set)
		cout << s << " ";
	cout << endl;
}

void test()
{
	vector<int> vec = { 4, 3, 9, 6, 3, 10, 12, 50, 1 };
	set<int, greater<int>> set2 = { vec.begin(), vec.end() };
	printSet(set2);
}

测试:
在这里插入图片描述

map

map是STL的一个关联容器,它提供一对一的hash。自动建立key - value的对应。key 和 value可以是任意你需要的类型,包括自定义类型。
特性

  1. 并且在map的内部,key与value通过成员类型value_type绑定在一起, 为其取别名称为pair
  2. map中的元素总是按照键值key进行比较排序的,默认为递增排序
  3. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value
  4. map中的key不可以重复,但是value可以重复。不能修改key,但是可以修改value

模板参数

在这里插入图片描述
Key:key值的类型
T:value值的类型

构造函数

explicit map (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()); 空构造

template < class InputIterator>
map (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), 通过迭代器创建map容器

template <class T1, class T2>
void printMap(const map<T1, T2>& m)
{
	//map中的数据pair,不能直接解引用获得值
	typename map<T1, T2>:: const_iterator it = m.begin();
	while (it != m.end())
	{
		cout << it->first << "-->" << it->second << endl;
		++it;
	}
}

void test()
{
	map<int, int> map1;//空的map容器
	pair<int, int> arr[] = {pair<int ,int>(1, 3), pair<int ,int>(1, 2) ,
		pair<int ,int>(3, 3) , pair<int ,int>(0, 0), pair<int ,int>(5, 5) };
	map<int, int> map2(arr, arr+sizeof(arr) / sizeof(arr[0]));//通过数据创建迭代器
	printMap(map2);
}

测试:key值一样不能存入。迭代器的访问底层也是一个中序遍历,按照key的递增序列的遍历
在这里插入图片描述
map的迭代器可以修改value,但是不可以修改key

void test()
{
	pair<int, int> arr[] = { pair<int ,int>(1, 3), pair<int ,int>(1, 2) ,
		pair<int ,int>(3, 3) , pair<int ,int>(0, 0), pair<int ,int>(5, 5) };
	map<int, int> map1(arr, arr+sizeof(arr) / sizeof(arr[0]));
	printMap(map1);
	cout << "修改第一个数据的value" << endl;
	auto it = map1.begin();//第一个数据
	it->second = 10;
	//it->first = 2; error
	printMap(map1);
}

测试:
在这里插入图片描述

[]方括号

mapped_type& operator[] (const key_type& k);
和我们平时使用数组时使用方法类似,key相当于索引,value就是索引对应的值 map[key] == value

void test()
{
	pair<int, int> arr[] = { pair<int ,int>(1, 3), pair<int ,int>(1, 2) ,
		pair<int ,int>(3, 3) , pair<int ,int>(0, 0), pair<int ,int>(5, 5) };
	map<int, int> map1(arr, arr + sizeof(arr) / sizeof(arr[0]));
	printMap(map1);
	cout << map1[1] << endl;
	map1[1] = 100;
	cout << map1[1] << endl;
}

测试:
在这里插入图片描述
但是map中的[]具有新的功能,即使key不存在,也可以通过[]来插入新的一个键值对并输出对应的value值

void test()
{
	pair<int, int> arr[] = { pair<int ,int>(1, 3), pair<int ,int>(1, 2) ,
		pair<int ,int>(3, 3) , pair<int ,int>(0, 0), pair<int ,int>(5, 5) };
	map<int, int> map1(arr, arr + sizeof(arr) / sizeof(arr[0]));
	map1[10] = 100;//插入一个key为10,value为100的数据
	cout << map1[10] << endl;
	printMap(map1);
}

测试:
在这里插入图片描述
我们通过这个接口底层实现来理解为什么可以通过[]来插入数据
其实map进行[]操作就等于进行对下面这行代码进行操作

(*((this->insert(make_pair(k,mapped_type()))).first)).second

我们来分解一下
在这里插入图片描述

1.先执行mapped_type(),也就是value类型的缺省值

2.执行make_pair(k,mapped_type()),创建一个键值对pair对象

3.执行insert(make_pair(k,mapped_type())), 插入这个创建好的pair对象,并返回pair<iterator,bool>,其中iterator是新插入的key对应迭代器

4.执行(this->insert(make_pair(k,mapped_type()))).first,调用返回的pair<iterator,bool>第一个元素iterator

5.执行*((this->insert(make_pair(k,mapped_type()))).first),对迭代器进行解引用获得pair对象pair<int, int>

6.执行(*((this->insert(make_pair(k,mapped_type()))).first)).second,调用pair对象的第二个元素,也就是value

如果key值已经存在,insert插入失败会返回已经存在的pair< iterator, bool>,其中iterator是已经存在的键为key的pair的迭代器

at接口

mapped_type& at (const key_type& k);

void test()
{
	pair<int, int> arr[] = { pair<int ,int>(1, 3), pair<int ,int>(1, 2) ,
		pair<int ,int>(3, 3) , pair<int ,int>(0, 0), pair<int ,int>(5, 5) };
	map<int, int> map1(arr, arr + sizeof(arr) / sizeof(arr[0]));
	//at接口和operator[]的区别就在于at执行插入对象存在时抛异常
	cout << map1.at(10) << endl;
}

insert

pair<iterator,bool> insert (const value_type& val);

void test()
{
	map<int, int> map1;
	auto ret = map1.insert(pair<int, int>(1, 1));
	cout << ret.first->first << "-->" << ret.first->second << "insert:" << ret.second << endl;
	ret = map1.insert(make_pair(2, 2));
	cout << ret.first->first << "-->" << ret.first->second << "insert:" << ret.second << endl;
	//key值存在插入失败
	ret = map1.insert(make_pair(2, 3));
	cout << ret.first->first << "-->" << ret.first->second << "insert:" << ret.second << endl;
C++——map和set的简介及使用

C++从入门到入土第二十篇:关联式容器-map和set

C++从入门到入土第二十篇:关联式容器-map和set

C++从入门到入土第二十篇:关联式容器-map和set

C++ 关联容器set | map | multiset | multimap

c++基础——关联容器