【C++】set容器和map容器的基本使用

news2024/11/22 23:45:42

一、序列式容器和关联式容器

1、STL中的部分容器如:string、vector、list、deque、array、forward_list等,这些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间一般没有紧密的关联关系,比如交换一下,它依旧是序列式容器,不会破坏逻辑结构。

2、关联式容器也是用来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是非线性结构,两个位置有紧密的关联关系,交换一下,它的存储结构就被破坏了。关联式容器中的元素是按关键字(key)来保存和访问的。关联式容器有map/set系列和unordered_map/unordered_set系列。

3、map和set底层是红黑树,红黑树是一颗平衡⼆叉搜索树(时间复杂度为O(\log_{}N))。set是key搜索场景的结构,map是key/value搜索场景的结构。大家在学习set和map的时候,要结合我们上篇二叉搜索树中的两个场景的实现过程进行理解。

二、<set>

1、set容器

set是key搜索场景的结构,这里的第一个模板参数T其实就是K(key),第二个参数是仿函数,提供仿函数的目的是:保证set容器中的数据是按升序排列的还是降序排列的,第三个参数是空间配置器,set底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第三个参数,基本上每个容器都有这个东西,我们暂且用它的缺省值即可。

一般情况下,我们都不需要传后两个模版参数。

set底层是用红黑树实现,增删查效率是O(\log N),它的迭代器遍历是走的搜索树的中序,由于二叉搜索树的性质,左子树小于根,根小于右子树,所以中序遍历的结果是有序的。

我们在此之前已经学习了vector/list等容器的使用,STL容器接口的设计高度相似,所以这里我们
就不再一个接口一个接口的介绍,只挑选比较重要的接口进行介绍。

(1)构造函数和迭代器

set的支持正向和反向迭代遍历,它的迭代器是双向迭代器,双向迭代器支持++/--,但是不支持+/-,遍历默认按升序顺序,因为底层是⼆叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,set的iterator和const_iterator都不支持迭代器修改数据(修改key的值),如果修改就破坏了底层搜索树的结构。

//empty (1) 无参默认构造,第一个参数是仿函数对象
explicit set(const key_compare& comp = key_compare(),
             const allocator_type& alloc = allocator_type());

//range (2) 迭代器区间构造 
template <class InputIterator>
set(InputIterator first, InputIterator last,
    const key_compare& comp = key_compare(),
    const allocator_type& = allocator_type());
 
//copy (3) 拷⻉构造 
set(const set& x);

//initializer list (4) C++11支持: initializer 列表构造 
set(initializer_list<value_type> il,
    const key_compare& comp = key_compare(),
    const allocator_type& alloc = allocator_type());

//迭代器是一个双向迭代器 
iterator -> a bidirectional iterator to const value_type

//正向迭代器 
iterator begin();
iterator end();

//反向迭代器 
reverse_iterator rbegin();
reverse_iterator rend();

对应代码说明:

int main()
{
    set<int> s1; //无参构造
    set<int>::iterator it1 = s1.begin();
    while (it1 != s1.end())
        cout << *it1 << " ", ++it1;
    cout << endl;

    cout << "-------------------" << endl;

    vector<int> v1({ 5,7,2,8,1,9,6,3,4 }); //vector支持用初始化列表构造
    set<int> s2(v1.begin(), v1.end()); //迭代器区间构造
    set<int>::iterator it2 = s2.begin();
    while (it2 != s2.end())
        cout << *it2 << " ", ++it2;
    cout << endl;

    cout << "-------------------" << endl;

    set<int> s3(s2); //拷贝构造
    set<int>::iterator it3 = s3.begin();
    while (it3 != s3.end())
        cout << *it3 << " ", ++it3;
    cout << endl;

    cout << "-------------------" << endl;

    set<int> s4({6,5,4,3,2,1}); //初始化列表构造
    cout << "正向迭代器:";
    set<int>::iterator it4 = s4.begin();
    while (it4 != s4.end())
        cout << *it4 << " ", ++it4;
    cout << endl;

    cout << "反向迭代器:";
    set<int>::reverse_iterator rit4 = s4.rbegin();
    while (rit4 != s4.rend())
        cout << *rit4 << " ", ++rit4;
    cout << endl;

	return 0;
}

运行结果:

不难发现,无论是以什么样形式初始化,它们的正向迭代器打印结果都是从小到大, 这是因为它的底层就是key搜索场景,迭代器在走的时候会进行中序遍历,在二叉搜索树中,中序遍历的结果就是顺序的,这里的第一个模板参数的仿函数默认是less,就是用less来控制插入时比较的方式,从而达到升序的效果,如果我们想让它是降序输出,那么只需显示传第一个模板参数的仿函数为greater,如果仿函数是greater那么底层二叉搜索树就是左孩子比父亲大,右孩子比父亲小,中序遍历时就是降序。如果T是自定义类型,若库中的仿函数不支持比较T的大小或者支持比较T的大小但比较的规则不是我们想要的那样,那我们就可以单独写一个仿函数,在默认构造时传我们单独写的仿函数对象给第一个默认构造参数。需要注意的是,第一个模板参数如果是排降序类型,如果要传第一个默认构造函数的参数,那也必须是这个排降序类型的对象;第一个模板参数如果是排升序类型,如果要传第一个默认构造函数的参数,那也必须是这个排升序类型的对象。

(2)增删查

set支持增删查,但不支持修改,因为修改可能会破坏内部性质。

相关接口如下:

//注:
//key_type -> The first template parameter (T) 即第一个模板参数T
//value_type -> The first template parameter (T) 即第一个模板参数T

//在set中key_type和value_type是一样的,都是第一个模板参数T(key)
//因为map底层是key/value场景,所以set中的key_type和value_type是为了与map保持一致
           
//1.单个数据插入,如果已经存在则插入失败 
pair<iterator, bool> insert(const value_type& val);

//2.列表插入,已经在容器中存在的值不会插入 
void insert(initializer_list<value_type> il);

//3.迭代器区间插入,已经在容器中存在的值不会插入 
template <class InputIterator>
void insert(InputIterator first, InputIterator last);

//1.删除一个迭代器位置的值,erase就是"有"就删,"没有"就不删,"没有"不会报错
iterator erase(const_iterator position);

//2.删除val,val不存在返回0,存在返回1
//这么做也是因为要兼容multiset,因为在multiset中val的值可能有多个
size_type erase(const value_type& val);

//3.删除一段迭代器区间的值 
iterator erase(const_iterator first, const_iterator last);

//查找val,返回val所在的迭代器,没有找到返回end(),它的查找逻辑就是按平衡二叉搜索树进行的,时间复杂度是O(logN)
iterator find(const value_type& val);

代码说明:

int main()
{
	//set容器在插入数据时:排序+去重
	//set<int> s1;  //默认排升序
	set<int, greater<int>> s1; //排降序
	s1.insert(5);
	s1.insert(2);
	s1.insert(7);
	s1.insert(5); //插入相同的值会失败

	s1.insert({ 2,8,3,9,2 }); //支持插入初始化列表,重复的值不会插入多次

	set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		//*it = 1; //迭代器指向的内容不允许修改(set容器中的值不允许修改)
		cout << *it << " ",	++it;
	}
	cout << endl;

	cout << "------------------------------" << endl;

	set<string> strset = { "sort","insert","add" }; 
	//set容器中存放的是string类型的元素
	//string类型支持比较大小,它是按ASCII码进行比较的
	for (auto e : strset)
		cout << e << " ";
	cout << endl;

	cout << "------------------------------" << endl;
	
	set<int> s2 = { 4,2,7,2,8,5,9 };
	for (auto e : s2)
		cout << e << " ";
	cout << endl;

	//删除最小值
	s2.erase(s2.begin()); //默认是升序且无重复值,begin()位置就是最小值
	for (auto e : s2)
		cout << e << " ";
	cout << endl;

	//删除指定的值
	int x1;
	cin >> x1;
	int num = s2.erase(x1);
	if (num == 0)
		cout << x1 << "不存在!" << endl;
	else
		cout << x1 << "删除成功!" << endl;
	for (auto e : s2)
		cout << e << " ";
	cout << endl;

	//下面的效果等同与上面
	int x2;
	cin >> x2;
	auto pos = s2.find(x2);
	if (pos != s2.end())
	{
		s2.erase(pos); //删除后,pos位置的迭代器就失效了
		//pos位置分为两种情况,第一:pos位置的结点只有左孩子或右孩子或是叶子节点,那么根据二叉搜索树的删除逻辑,删除后pos就是野指针了
		//第二:如果pos位置结点左右都有孩子,就用替换法删除,删除后,pos虽然不是野指针,但它指向的意义已经发生变化
		//这两种情况都可以称为迭代器失效,所以库中的erase返回值是一个迭代器,它返回删除位置的下一位置的迭代器
		//cout << *pos << endl; //因为迭代器已经失效,贸然访问,在vs2019下就会直接报错
		cout << x2 << "删除成功!" << endl;
	}
	else
		cout << x2 << "不存在!" << endl;
	for (auto e : s2)
		cout << e << " ";
	cout << endl;

	cout << "------------------------------" << endl;

	int x3;
	cin >> x3;
	set<int> s3 = { 4,2,7,2,8,5,9 };
	//算法库中的查找
	auto pos1 = find(s3.begin(), s3.end(), x3);
	//set容器自身实现的查找
	auto pos2 = s3.find(x3);

	//算法库中的find是针对任何容器设计的,它的查找逻辑是在整个迭代区间遍历查找(暴力查找),所以它的时间复杂度是O(N)
	//set容器中的find是根据平衡二叉搜索树的查找逻辑进行查找的,所以它的时间复杂度是O(logN)
	//所以,在set容器中,尽量用它自身的find,不要用算法库中的find

	return 0;
}

运行结果:

(3)swap()

它的作用就是交换两个容器的根节点。

代码说明:

int main()
{
	set<int> s1({5,4,3,2,1});
	set<int> s2({ 10,9,8,7,6 });
	for (auto e : s1)
		cout << e << " ";
	cout << endl;

	for (auto e : s2)
		cout << e << " ";
	cout << endl;

	cout << "---------------------------" << endl;

	s1.swap(s2);
	for (auto e : s1)
		cout << e << " ";
	cout << endl;

	for (auto e : s2)
		cout << e << " ";
	cout << endl;

	return 0;
}

运行结果:

(4)clear()

它的功能就是删除容器中所有数据。

代码说明:

int main()
{
	set<int> s1({ 5,4,3,2,1 });
	for (auto e : s1)
		cout << e << " ";
	cout << endl;

	cout << "----------------------" << endl;

	s1.clear();
	for (auto e : s1)
		cout << e << " ";
	cout << endl;
	cout << "----------------------" << endl;

	return 0;
}

运行结果:

(5)count()

size_type是一个无符号整型,count的功能是查找val,返回val的个数,这里只有0和1两种情况,因为set容器中不允许有重复val,可以理解为:返回0代表该值在容器中不存在,如果为1则存在,这么做的原因还有一个就是要兼容multiset,因为在multiset中val的值可能有多个。

代码说明:

int main()
{
	set<int> s1({ 5,4,3,2,1 });
	for (auto e : s1)
		cout << e << " ";
	cout << endl;

	//不考虑删除的情况,用count看某值在不在set容器中是非常方便的
	int x;
	while (cin >> x)
	{
		if (s1.count(x))
			cout << x << "存在" << endl;
		else
			cout << x << "不存在" << endl;
	}

	return 0;
}

运行结果:

(6)lower_bound与upper_bound

lower_bound的功能是返回第一个大于等于val位置的迭代器(按照搜索树的规则找)

upper_bound的功能是返回第一个大于val位置的迭代器(按照搜索树的规则找)

一个是大于等于,一个是大于,注意不要混淆。

 这样设计方便我们去找一段区间,找到一段区间就可以针对这段区间进行相关操作。

代码说明:

int main()
{
	set<int> s1;
	for (int i = 1; i < 10; i++)
		s1.insert(i * 10); //10 20 30 40 50 60 70 80 90
	for (auto e : s1)
		cout << e << " ";
	cout << endl;

	//要求1:删除[30, 50]区间的值
	//要求2:删除[25, 55]区间的值

	//针对要求1:找到两个位置 >=30 和 >50
	auto itlow1 = s1.lower_bound(30); //返回第一个大于等于30位置的迭代器
	auto itup1 = s1.upper_bound(50); //返回第一个大于50位置的迭代器
	//这样就把set中在[30,50]这个区间的所有元素找出来了,删除即可
	s1.erase(itlow1, itup1); //删除的区间为"左闭右开"
	for (auto e : s1)
		cout << e << " ";
	cout << endl;

	cout << "----------------------------------------" << endl;

	set<int> s2;
	for (int i = 1; i < 10; i++)
		s2.insert(i * 10); //10 20 30 40 50 60 70 80 90
	for (auto e : s2)
		cout << e << " ";
	cout << endl;

	//针对要求2:找到两个位置 >=25 和 >55
	auto itlow2 = s2.lower_bound(25); //返回第一个大于等于30位置的迭代器
	auto itup2 = s2.upper_bound(55); //返回第一个大于50位置的迭代器
	//这样就把set中在[25,55]这个区间的所有元素找出来了,删除即可
	s2.erase(itlow2, itup2); //删除的区间为"左闭右开"
	for (auto e : s2)
		cout << e << " ";
	cout << endl;

	return 0;
}

运行结果:

2、multiset容器

multiset和set的使用基本完全类似,主要区别点在于multiset支持值冗余,相同的值,插入到该结点的左边还是右边都可以,没有什么区别,但是要统一,不能一会插入到左边一会插入到右边,允许数据冗余那么insert/find/count/erase这几个函数就会与set有所差异。

代码说明:

int main()
{
	//multiset容器在插入数据时:排序+不去重
	multiset<int> s = { 4,2,7,2,4,8,4,5,4,9 };
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " "; //输出:2 2 4 4 4 4 5 7 8 9
		++it;
	}
	cout << endl;

	//相比set不同的是,x可能会存在多个,find查找中序的第一个
	//如何确定x是中序第一个呢?
	//因为中序遍历遵循:左子树 根 右子树
	//所以如果找到x,就不需要到右子树找了,因为右子树如果有x不可能是第一个
	//所以如果找到x,再继续从它的左子树中找,如果没有找到,那么当前位置就是中序第一个x,如果找到,那么继续从它的左子树找,直到找不到,最后一个x就是中序第一个x,最多找高度次,所以它的时间复杂度也是:O(logN)
	//为什么要返回中序第一个呢?
	//因为找到中序第一个,迭代器就能不断++找到所有的x,所以库里面要求返回中序第一个
	int x;
	cin >> x;

	//输出所有的x
	auto pos = s.find(x);
	while (pos != s.end() && *pos == x)
	{
		cout << *pos << " ";
		++pos;
	}
	cout << endl;

	//删除所有的x
	//pos = s.find(x);
	//while (pos != s.end() && *pos == x)
	//{
		//pos = s.erase(pos); //返回删除位置的下一位置的迭代器
	//}
	//cout << endl;

	//erase不仅可以传迭代器,也可以传具体的某个值
	//上面删除所有的x的方法有点麻烦,我们也可以这样:
	s.erase(x); //删除multiset容器中所有的x
	for (auto e : s)
		cout << e << " ";
	cout << endl;

	//count会返回x的实际个数 
	cout << s.count(x) << endl;
	
	return 0;
}

运行结果: 

三、<map>

1、map容器

map是key/value搜索场景的结构,这里的前两个模板参数分别是key和value,第三个模板参数是仿函数,提供仿函数的目的是:保证map容器中的数据是按升序排列的还是降序排列的(这里的排升序是根据key的值排的),第四个参数是空间配置器。一般情况下,我们都不需要传后两个模版参数。

map底层是用红黑树实现,增删查改效率是O(\log N) ,迭代器遍历是走的中序,所以是按Key有序顺序遍历的。

(1)pair类型

map底层的红黑树结点中的数据,使用pair<const Key,T>存储键值对数据。

下面是库中pair的一部分源码:

typedef pair<const Key, T> value_type;

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)
	{}

	template<class U, class V>
	pair(const pair<U, V>& pr) //有参构造
		:first(pr.first)
		,second(pr.second)
	{}
};

template <class T1, class T2>
inline pair<T1, T2> make_pair(T1 x, T2 y) //创建pair对象
{
	return (pair<T1, T2>(x, y));
}

pair的第一个模板参数就是key,第二个模板参数就是value,对应的就是first就是key,second就是value。相当于将key和value捆绑起来放在名为pair的结构体中,这个结构体有两个成员first和second,first就是key,second就是value。因为key不能被改变所以加了const,value可以被允许改变所以没有加const。将它们两个捆绑起来目的是在后面它们迭代器解引用时方便打印数据。

value_type就是pair类型。

(2)构造函数和迭代器

map容器支持正向和反向迭代遍历,遍历默认按key的升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,map支持修改value数据,但是不支持修改key数据,修改关键字数据,会破坏底层搜索树的结构。

map的构造我们关注以下几个接口即可:

//empty (1) 无参默认构造 
explicit map(const key_compare& comp = key_compare(),
	         const allocator_type& alloc = allocator_type());

//range (2) 迭代器区间构造 
template <class InputIterator>
map(InputIterator first, InputIterator last,
	const key_compare& comp = key_compare(),
	const allocator_type & = allocator_type());

//copy (3) 拷贝构造 
map(const map& x);

//initializer list (4) initializer 列表构造 
map(initializer_list<value_type> il,
	const key_compare& comp = key_compare(),
	const allocator_type& alloc = allocator_type());

//迭代器是一个双向迭代器 
iterator->a bidirectional iterator to const value_type

//正向迭代器 
iterator begin();
iterator end();

//反向迭代器 
reverse_iterator rbegin();
reverse_iterator rend();

它跟set容器用法几乎一样,这里只讲一个初始化列表构造:

#include <map>
int main()
{
	pair<string, string> kv1("first", "第一个");
	pair<string, string> kv2("second", "第二个");
	pair<string, string> kv3("third", "第三个");

    //用初始化列表(3个有名对象)进行初始化
	map<string, string> dict1({ kv1,kv2,kv3 });

    //用初始化列表(3个匿名对象)进行初始化(前提是pair的构造函数支持二参构造)
	map<string, string> dict2({ pair<string,string>("one","一"),pair<string,string>("two","二"),pair<string,string>("three","三") });

    //用初始化列表(3个临时对象)进行初始化,隐式类型转换
	map<string, string> dict3({ {"action","行动"},{"plan","计划"},{"success","成功"} });

	return 0;
}

调试结果:

(3)增删查

map插入的是pair键值对数据,跟set所有不同,但是查和删的接口只用关键字key跟set是完全类似的,不过find返回iterator,不仅仅可以确认key在不在,还找到key映射的value,同时通过迭代还可以修改value。

map的增删查关注以下几个接口即可:

//key_type    -> The first template parameter (Key) 即第一个模板参数Key
//mapped_type -> The second template parameter (T)  即第二个模板参数T(value)
//value_type  -> pair<const key_type,mapped_type>   即pair<const Key,T> (pair<const key,value>)

//单个数据插入,如果已经存在key则插入失败,若存在key相等但value不相等也会插入失败 
pair<iterator, bool> insert(const value_type& val);

//列表插入,已经在容器中存在的值不会插入
void insert(initializer_list<value_type> il);

//迭代器区间插入,已经在容器中存在的值不会插入
template <class InputIterator>
void insert(InputIterator first, InputIterator last);

//查找k,返回k所在的迭代器,没有找到返回end() 
iterator find(const key_type& k);

//删除一个迭代器位置的值 
iterator erase(const_iterator position);

//删除k,k存在返回0,存在返回1 
size_type erase(const key_type& k);

//删除一段迭代器区间的值
iterator erase(const_iterator first, const_iterator last);

代码说明:

int main()
{
	map<string, string> dict1;
	pair<string, string> kv1("first","第一个");
	dict1.insert(kv1); //直接传一个有名对象
	dict1.insert(pair<string,string>("second", "第二个")); //传匿名对象
	dict1.insert(make_pair("third","第三个"));//make_paia是一个模板,它接收两个模板参数,并返回一个pair对象,它可以减少代码长度
	dict1.insert({ "sort","排序" });//C++11,隐式类型转换,{"sort","排序"}可以转换成一个pair的对象(前提是pair支持两个参数的构造函数)
	
	//插入失败
	dict1.insert({ "sort","排序xxx" }); //在map中,如果插入和key的值相等但与value的值不相等,它是不会更新value的,因为插入的时候只看key,key如果相等,就不插入

	//遍历dict
	//map<string, string>::iterator it1 = dict.begin();
	auto it1 = dict1.begin();
	while (it1 != dict1.end())
	{
		//cout << *it1 << endl; //*it1解引用就是pair类型,pair不支持流插入和流提取,所以不能直接这样写

		//可以这样写:
		//cout << (*it1).first << ":" << (*it1).second << endl;

		//it1->first += 'x'; //key不支持修改
		it1->second += 'x'; //value支持修改

		//也可以这样写:
		cout << it1->first << ":" << it1->second << endl;
		//本质上是这样的:
		//cout << it1.operator->()->first << ":" << it1.operator->()->second << endl;

		++it1;
	}
	cout << endl;

	cout << "---------------------------" << endl;

	//可以不一个个插入,直接插入初始化列表会更方便
	map<string, string> dict2;
	dict2.insert({ {"left","左边"},{"right","右边"},{"up","向上"}});//隐式类型转换

	auto it2 = dict2.begin();
	while (it2 != dict2.end())
	{
		cout << it2->first << ":" << it2->second << endl;
		++it2;
	}
	cout << endl;
	return 0;
}

因为map的删除和查找只和key有关,所以它的删除和查找几乎和set一样,大家可以直接参考set容器的删除和查找。 

(4)其它

map的swap、clear、count、 lower_bound与upper_bound几乎和set一模一样,直接参考set容器的这一部分即可。

(5)equal_range

它的功能是返回k的左闭右开区间,如果k为20,就返回[20,第一个大于b的值),这一迭代区间,这一函数在multimap中效果很好,因为它可以找到所有的k。

(6)operator[]

在map容器中,它重载了[],这点是与set容器是不同的。

它虽然重载了[],但此时的意义已经和数组中那个下标引用操作符不同了,这里是传一个key,返回对应的value的引用。

这里的[]具有三重功能:插入、查找、修改。这里的插入指的是如果传入的key,在map中找不到,那就将key插入到map容器中,它的底层会调用insert。查找是指要先查找key是否存在这个过程。修改是指我们可以对返回值value进行修改。

insert函数也就是下面这个:

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

我们在上面讲了它的用法,但没讲它的返回值,接下来我们就研究一下它的返回值:

它的返回值是pair类型,但是它与上面pair是不同的,模板参数就不一样,⼀个是map底层红⿊树节点中存的pair<const key, value>,另⼀个是insert返回值pair<iterator,bool>。

这里的pair中第一个模板参数是一个迭代器,如果插入成功,返回新插入的元素位置的迭代器;如果插入失败,返回key位置的迭代器。也就是说无论插入成功还是失败,返回pair<iterator,bool>对象的first都会指向key所在的迭代器。

这里的pair中第二个模板参数bool类型就是当key存在,那就插入失败,返回false;当key不存在,那就插入,返回true。

插入成功:pair<新插入值所在的迭代器,true>

插入失败:pair<已经存在的和key相等的值所在的迭代器,false>

insert插入失败时充当了查找的功能,正是因为这⼀点,insert可以用来实现operator[]。

operator的内部实现就像下面这样:

mapped_type& operator[] (const key_type& k)
{
	//1.如果key不在map中,insert会插入key和mapped_type(value)的默认值
	//同时[]返回结点中存储的mapped_type(value)值的引用,那么我们可以通过引用修改value值。所以[]具备了插入+修改功能
	
	//2.如果k在map中,insert会插入失败,但是insert返回pair对象的first是指向与key值相同的结点的
	//迭代器,同时[]返回结点中存储mapped_type(value)值的引用,所以[]具备了查找+修改的功能

	pair<iterator, bool> ret = insert({ k, mapped_type() });
	iterator it = ret.first;

	return it->second;
}

我们来举个例子,可能会帮助大家更好的理解:

int main()
{
	//利用find和iterator修改功能,统计水果出现的次数 
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };

	map<string, int> countMap;
	for (const auto& str : arr)
	{
		//先查找水果在不在map中 
		//1、不在,说明水果第一次出现,则插入{水果, 1} 
		//2、在,则查找到的节点中水果对应的次数++ 
		auto ret = countMap.find(str);
		if (ret == countMap.end())
			countMap.insert({ str, 1 });
		else
			ret->second++;
	}

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

	return 0;
}

这段代码是统计各个水果出现的次数的,我们在上一篇二叉搜索树中讲到过,只不过这里换成了map容器进行操作,底层还是一样的。

运行结果:

我们上面学习了解了[],现可以对这段代码进行优化:

int main()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };

	map<string, int> countMap;
	for (const auto& str : arr)
	{
		countMap[str]++; //一句代码搞定问题
		//如果map中没有str,那么就插入str,并初始化value为0,返回value的引用,接着++,相当于首次插入str,并将对应的value置为1
		//如果map中有str,那么就返回当前str位置上的value的引用,接着++,相当于,只对value进行了加1操作,value就是水果的个数,逻辑上没有任何问题
	}

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

	return 0;
}

运行结果: 

结果没有问题,大大简化了代码的复杂度。 

我们再来看一个例子:

int main()
{
	map<string, string> dict;

	dict.insert(make_pair("sort", "排序"));

	//insert不存在:插入  
	dict["insert"]; //insert({"insert", string()})

	//left不存在:插入+修改
	dict["left"] = "左边"; //将""修改为"左边"

	//left存在:修改
	dict["left"] = "左边、剩余"; //将"左边"修改为"左边、剩余"

	//left存在:查找(确认left在才能这么用,否则就是插入了)
	cout << dict["left"] << endl;

	//up不存在:插入
	cout << dict["up"] << endl; //输出:""

	return 0;
}

2、multimap容器

multimap和map的使用基本完全类似,主要区别点在于multimap支持关键值key冗余,那么multimap在插入数据时一定会成功,除非空间不够,否则就不会插入失败,insert/find/count/erase都围绕着支持关键值key冗余有所差异,这里跟set和multiset完全一样,比如find时,有多个key,返回中序第一个;erase时,它的内部先利用find找到第一个key,然后开始依次删除,直到key删除完全,其次就是multimap不支持[],因为multimap支持key冗余,调用[]会返回对应的value,如果支持key冗余,那调用后返回哪一个value呢?所以multimap不支持[]。

代码说明:

int main()
{
	multimap<string, string> dict;

	//插入一定成功
	dict.insert({ "sort", "排序1" });
	dict.insert({ "sort", "排序1" });
	dict.insert({ "sort", "排序2" });
	dict.insert({ "sort", "排序3" });
	dict.insert({ "sort", "排序3" });
	dict.insert({ "string", "字符串" });

	//将sort全部删除
	dict.erase("sort");

	return 0;
}

erase执行前调试结果:

erase执行后调试结果: 

四、结语

本篇内容到这里就结束了,主要讲了set和multiset容器以及map和multimap容器的基本使用,希望对大家有帮助,祝生活愉快,我们下一篇再见! 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构双向链表和循环链表

目录 一、循环链表二、双向链表三、循环双向链表 一、循环链表 循环链表就是首尾相接的的链表&#xff0c;就是尾节点的指针域指向头节点使整个链表形成一个循环&#xff0c;这就弥补了以前单链表无法在后面某个节点找到前面的节点&#xff0c;可以从任意一个节点找到目标节点…

Leetcode 540. 有序数组中的单一元素

1.题目基本信息 1.1.题目描述 给你一个仅由整数组成的有序数组&#xff0c;其中每个元素都会出现两次&#xff0c;唯有一个数只会出现一次。 请你找出并返回只出现一次的那个数。 你设计的解决方案必须满足 O(log n) 时间复杂度和 O(1) 空间复杂度。 1.2.题目地址 https:…

大语言模型入门(二)——提示词

一、什么是提示词 大语言模型&#xff08;LLM&#xff09;的提示词&#xff08;Prompt&#xff09;是与模型交互的关键&#xff0c;它影响着模型的输出结果。提示词&#xff08;Prompt&#xff09;和提示工程&#xff08;Prompt Engineering&#xff09;密切相关。什么又是提示…

详解代理服务器及Squid

一、 代理服务器简介 &#xff08;1&#xff09;什么是代理服务器 代理服务器英文全称为ProxyServer&#xff0c;其主要功能代理网络用户获取网络信息&#xff0c;起到内网和Internet的桥梁作用。 在TCP/IP网络中&#xff0c;传统的通信过程是这样的&#xff1a;客户端向服务…

ROS2 22.04 Carttographer安装

安装环境&#xff1a; Ubuntu22.04 ros2 humble # 下载源文件 git clone https://github.com/ros2/cartographer.git -b ros2 git clone https://github.com/ros2/cartographer_ros.git -b ros2# 使用小鱼一键配置rosdep wget http://fishros.com/install -O fishros &&am…

基于SSM的本科生操行评定管理系统

文未可获取一份本项目的java源码和数据库参考。 1课题名称 基于SSM的本科生操行评定系统 1.2课题来源和选题依据 随着时代的进步和国民生活水平的不断提高&#xff0c;教育也越来越被人们所重视&#xff0c;学校应该培养品学兼优的全方位人才&#xff0c;学生的行为习惯和日…

mac安装redis实践和客户端连接失败问题解决

文章目录 参考文档和网址redis和客户端安装下载Homebrew程序Mac系统设置redis后台运行设置连接密码安装ARDM使用ARDM连接redis错误解决 参考文档和网址 redis官网命令指导文档brew官网地址brew客户端下载地址redis客户端下载地址 redis和客户端安装 下载Homebrew程序 HomeB…

golang grpc进阶

protobuf 官方文档 基本数据类型 .proto TypeNotesGo Typedoublefloat64floatfloat32int32使用变长编码&#xff0c;对于负值的效率很低&#xff0c;如果你的域有可能有负值&#xff0c;请使用sint64替代int32uint32使用变长编码uint32uint64使用变长编码uint64sint32使用变长…

大语言模型入门(一)——大语言模型智能助手

一、大语言模型智能助手 2022年末ChatGPT一经推出&#xff0c;一时间不注册个账号用一下都跟不上潮流了。然而&#xff0c;我们要注册OpenAI的账号使用ChatGPT还是一件比较麻烦的事情&#xff08;懂的都懂&#xff09;。好在&#xff0c;国内各大团队非常给力地及时推出了自研的…

野火STM32F103VET6指南者开发板入门笔记:【1】点亮RGB

硬件介绍 提示&#xff1a;本文是基于野火STM32F103指南者开发板所写例程&#xff0c;其他开发板请自行移植到自己的工程项目当中即可。 RGB-LEDPin引脚&#xff1a;低电平-点亮&#xff0c;高电平-熄灭REDPB5GREENPB0BLUEPB1 文章目录 硬件介绍软件介绍&#xff1a;结构体方式…

三、数据链路层(上)

目录 3.1数据链路层概述 3.1.1术语 3.1.2功能 3.2封装成帧和透明传输 3.2.1封装成帧 ①字符计数法 ②字符&#xff08;节&#xff09;填充法 ③零比特填充法 ④违规编码法 3.2.2透明传输 3.2.3差错控制 差错原因 检错编码 奇偶校验 ☆循环冗余码CRC 例题 纠错…

社区医院疫苗接种预约小程序管理系统SpringBoot+vue

目录 一、项目概述 二、系统架构 1. 技术栈 2. 架构图 三、后端设计 1. 数据模型 2. API 设计 四、前端设计 五、功能实现 1. 用户登录注册 2. 接种建档 3. 疫苗展示 六、总结 一、项目概述 本项目旨在为社区医院提供一个高效便捷的疫苗接种预约管理系统。系统主要…

记一次vue路由跳转登陆之前的页面,参数丢失问题

一、背景 vue3.0&#xff0c;项目登陆之前访问某个可访问的页面&#xff0c;当跳转到需要登陆才能访问的页面时&#xff0c;跳转到登陆页面&#xff0c;登陆后再跳转到登陆之前需要登陆才能访问的页面&#xff0c;跳转时发现参数丢失了。 A页面&#xff08;无需登陆&#xff…

【零基础保姆级教程】MMDetection3安装与训练自己的数据集

最近在跑对比试验&#xff0c;由于MMDetection框架的算法较齐全&#xff0c;遂决定写一篇教程留做参考。若你对流程有问题与疑问欢迎评论区指出 本文运行环境如下供参考&#xff1a; python版本3.9MMDetection版本3.3 一、虚拟环境的搭建 参考该博客搭建基本环境&#xff1…

【开源免费】基于SpringBoot+Vue.JS水果购物网站(JAVA毕业设计)

本文项目编号 T 065 &#xff0c;文末自助获取源码 \color{red}{T065&#xff0c;文末自助获取源码} T065&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

从认识String类,到走进String类的世界

作为一个常用的数据类型&#xff0c;跟随小编一同进入String的学习吧&#xff0c;领略String的一些用法。 1. 认识 String 类 2. 了解 String 类的基本用法 3. 熟练掌握 String 类的常见操作 4. 认识字符串常量池 5. 认识 StringBuffer 和 StringBuilder 一&#xff1a;…

【吊打面试官系列-MySQL面试题】Mysql中的事务回滚机制概述?

大家好&#xff0c;我是锋哥。今天分享关于【Mysql中的事务回滚机制概述&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; Mysql中的事务回滚机制概述&#xff1f; 事务是用户定义的一个数据库操作序列&#xff0c;这些操作要么全做要么全不做&#xff0c;是一个…

职称评审一次通过需要注意什么?

谁能想到 被评委会全票通过的职称材料 居然要注意这么多细节 营业执照需要加盖公章 论文需要拆分上传 业绩需要连续提供近几年的 奖项可以加分 一些表格有模板 所以职称评审做材料还是有很多方面需要好好注意一下的&#xff0c;建议还是找机构帮你代理整理&#xff0c;因…

如何使用ssm实现基于web的网站的设计与实现+vue

TOC ssm756基于web的网站的设计与实现vue 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff0c;规范…

10. 模块

理想情况下&#xff0c;程序的结构清晰明了。它的运作方式易于解释&#xff0c;每个部分都发挥着明确的作用。 实际上&#xff0c;程序是有机生长的。当程序员发现新的需求时&#xff0c;就会添加新的功能。要使程序保持良好的结构&#xff0c;需要持续的关注和工作。这些工作只…