【c++】STL容器-map和set、multimap和multiset的使用和介绍(2.3w字详情解析)

小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
c++系列专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!
在这里插入图片描述



前言

【c++】二叉搜索树的模拟实现——由浅入深2.7w字详细讲解(key结构的非递归,key结构的递归,key_value结构的递归)——书接上文 详情请点击<——
本文由小编为大家介绍——STL容器-map和set、multimap和multiset的使用和介绍


一、关联式容器

在前面的学习中,我们已经接触了c++的STL的部分容器中的string,vector,list,deque,list_forward(c++11)等,这些容器统称为序列式容器,因为其底层为线性序列的存储结构,存储的是元素本身

本文要讲解的map和set、multimap和multiset是关联式容器,它也是用来存储数据的,与序列式容器不同的是,它存储的是<key,value>结构的键值对,关联式容器在数据检索的时候比序列式容器的效率高

根据应用场景的不同,STL总共实现了两种不同的结构的关联式容器:树形结构和哈希结构,树型结构的访问单个数据比哈希结构访问单个数据的效率要低。树型结构的关联式容器一共有四种,即map和set、multimap和multiset,这四种容器的共同特点是:都是使用平衡二叉搜索树(红黑树)作为其底层结构,二叉搜索树的独有特性即中序遍历是有序序列,即使用迭代器进行遍历(中序遍历)是有序的,它们的迭代器都是双向迭代器,不是随机迭代器

所以为了带来更好的理解,在阅读本文前读者友友一定要先阅读二叉搜索树的key结构以及key_value结构的模拟实现 详情请点击<——,其中key结构就是set,multiset的底层结构,key_value就是map,multiple的底层结构,这里小编写的二叉搜索树的key结构以及key_value结构的模拟实现并不是完全版本的底层结构,STL库里真正的底层结构是基于二叉搜索树的这种结构实现的红黑树(平衡二叉搜索树)

二、键值对(key-value pair)— pair

pair的介绍

在这里插入图片描述

pair是类模板,pair用来表示具有一 一对应关系的一种结构,该结构中一般只包括key和value两个成员变量,key是键值,value是与key对应的值,这里的key对应定义的成员变量first,value对应定义的成员变量second

//SGI-STL关于pair键值对的定义
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的使用

在这里插入图片描述
pair是包含在utility中的,因此在使用pair的时候要包含头文件#include <utility> ,其实不包这个头文件也能进行使用pair,从规范性上来讲,还是建议包头文件

构造函数

在这里插入图片描述

  1. 这里小编演示第三条使用first和second进行初始化构造pair对象,因为pair对象是map存储的结构,因此pair对象要有对应的key和value才能往map中进行存储
make_pair(c++98)

在这里插入图片描述

  1. pair是类模板,需要显示传入两个类型才能实例化出对象,进行构造出可以传入map的对象还需要传入两个数据进行pair对象的初始化,那么传参给map的时候比较繁琐
  2. 那么为了简便,utility提供了一个函数模板make_pair可以传入两个数据直接构造出pair对象,因为函数模板可以隐式实例化推导出传入数据的类型,那么在make_pair内部去调用pair的构造函数去实例化,这里的make_pair同时还被定义成了内敛函数,会在调用的地方直接展开,make_pair构造pair对象和直接去使用pair构造函数构造pair对象消耗几乎一样
  3. 那么make_pair的意义就是方便了我们,进行构造pair对象传入map比较方便,那么make_pair是一个pair<T1,T2>通常是用于map的中的数据存储
//make_pair的大体实现
template <class T1,class T2>
pair<T1,T2> make_pair (T1 x, T2 y)
{
  return ( pair<T1,T2>(x,y) );
}
#include <iostream>
#include <utility>
#include <string>

using namespace std;

int main()
{
	pair<string, string> p1("insert", "插入");

	cout << p1.first << ':' << p1.second << endl;

	cout << make_pair("delete", "删除").first << ':' 
		<< make_pair("delete", "删除").second << endl;

	return 0;
}

运行结果如下
在这里插入图片描述

  1. pair<string, string> p1(“insert”, “插入”);这里pair接收参数的过程中发生了隐式类型转换
  2. 对于要插入的两个字符串"insert", “插入”,类型是const char*,pair构造函数的原型是pair(const T1& a, const T2& b)(这里使用引用是为了减少消耗,因为T1或T2的类型有可能是自定义类型,自定义类型通常传参消耗大,所以这里使用引用)即这里的实例化是pair<string, string>类型,即T1,T2都是string类型
  3. 那么形参的类型是const char*字符串,实参的类型是char string&,那么这里的类型不匹配,所以不能直接进行转换,那么就进行了隐式类型转换,即const char*的字符串去隐式类型转换为一个string类型的对象,这个对象是类型对象,具有常性,不能被普通进行引用,所以接收这个参数只能使用const引用才可以

三、set

set的介绍

在这里插入图片描述

  1. set是按照一定次序存储元素的容器,其底层是平衡二叉搜索树(红黑树)
    在这里插入图片描述
  2. 在set中,value就是key,类型都是T,在set中key的值是唯一的并且不可以修改,一旦修改,那么二叉搜索树的结构就被破坏了,那么就不能构成set了,所以key不能修改,虽然key不可以修改,但是key可以删除或插入,但是当插入的时候如果这个key已经有了,由于key是唯一的,所以默认将不会进行插入
  3. 与map/multimap不同,map/multimap中存储的是真正是键值对<key,value>,set中只放key(value),这里的key和value是一样的,类型都为T,T是set中存放的元素类型,但是在底层存放的实际上是<value,value>构成的键值对,即每个数据即是key键也是value值
  4. 由于set中的key是唯一的,那么我们可以利用set对数据进行去重
  5. 使用set的迭代器进行遍历(中序遍历)元素,那么得到的是有序序列
  6. 默认使用仿函数less小于<的逻辑进行元素比较,即遍历后得到的是升序,这个仿函数很灵活,我们可以显示传入仿函数,实现我们自己的比较逻辑得到我们想要的预期结果

set的使用

在使用set容器的时候不要忘记包头文件#include <set>

构造函数、赋值运算符重载

在这里插入图片描述

  1. 如上图第一条为无参的构造,第二条为使用一段迭代器区间构造,第三条为拷贝构造
    在这里插入图片描述
  2. 为容器分配新内容,替换当前内容
#include <iostream>
#include <set>

using namespace std;

int main()
{
	set<int> s1;
	int arr[5] = { 4,3,5,1,6 };
	set<int> s2(arr, arr + sizeof(arr) / sizeof(arr[0]));
	set<int> s3(s2);
	s1 = s2;

	return 0;
}

运行结果如下

  1. 调试,s1赋值前

在这里插入图片描述

  1. s1赋值后

在这里插入图片描述

迭代器的使用begin、end 范围for

在这里插入图片描述在这里插入图片描述

  1. 这里set的迭代器的使用和之前容器(例如string,vector等)的迭代器的使用无异,这里小编只演示普通迭代器的使用,对于const迭代器的使用和反向迭代器以及const的反向迭代器的使用这里小编不在演示,如果不了解的友友可以参考string容器的相关const以及反向迭代器的使用详情请点击<——
  2. 同时既然set支持迭代器,那么也就必然支持范围for,因为范围for的本质其实就是编译器进行无脑替换为迭代器进行遍历
int main()
{
	int arr[5] = { 4,3,5,1,6 };
	set<int> s(arr, arr + sizeof(arr) / sizeof(arr[0]));

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

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

	return 0;
}

运行结果如下
在这里插入图片描述

empty、size

在这里插入图片描述在这里插入图片描述

  1. empty判断set中的数据是否为空
  2. size判断set中的数据节点个数
int main()
{
	int arr[5] = { 4,3,5,1,6 };
	set<int> s(arr, arr + sizeof(arr) / sizeof(arr[0]));

	cout << s.empty() << endl;
	cout << s.size() << endl;

	return 0;
}

运行结果如下
在这里插入图片描述

insert

在这里插入图片描述
在这里插入图片描述

  1. 第一点是插入一个key(val)(这里的value_type(value)和key_type(key)一样都是T类型,T:set中存储的数据类型),观察其返回的是一个键值对pair类型,其中第一个数据first是插入位置的迭代器,第二个位置是是否插入成功,由于set中的key值不能重复且只能有一个,如果key值已经有了,那么返回的是原key位置的迭代器以及false,如果key值没有,那么返回的是新插入key位置的迭代器以及true,由于return不能返回两个值,所以如果想返回多个值,必须放在一个结构中进行返回,这里是放在了键值对pair中进行返回
  2. 第二点是在一个迭代器位置插入一个key(val)但是注意,这里的迭代器位置并不是强制插入到该迭代器位置,我们传入的这个迭代器位置对于编译器来说只是一个建议位置,实际插入数据还是根据其原有的二叉搜索树的逻辑进行插入,第三点是插入一段迭代器区间
int main()
{
	int arr[5] = { 4,3,5,1,6 };
	set<int> s;

	pair<set<int>::iterator,bool> p = s.insert(5);
	//auto p = s.insert(5);这里也可以使用auto来推导类型

	cout << *(p.first) << ' ' << p.second << endl;

	set<int>::iterator it = s.insert(s.begin(), 20);
	cout << *it << endl;

	s.insert(arr, arr + sizeof(arr) / sizeof(arr[0]));

	return 0;
}

运行结果如下
在这里插入图片描述

  1. 进行调试,初始

在这里插入图片描述

  1. 插入一个值后

在这里插入图片描述

  1. 在迭代器位置插入一个值,注意这里的迭代器位置仅仅是一个建议位置,例如小编给的是根节点的位置即0位置,那么按道理key值20应该插入到0位置上,实际却没有,所以编译器实际插入还是走的原来的二叉搜索树的插入逻辑进行插入key

在这里插入图片描述

  1. 插入一段迭代器区间

在这里插入图片描述

erase

在这里插入图片描述

  1. 第一点是删除一个迭代器位置
  2. 第二点是删除某个key值,但是观察这个返回值,是删除的这个key值的个数。是不是很奇怪?明明在set中key值只有一个或没有,这里要是想要返回的话直接返回bool值即可,何必返回删除这个key值的个数呢?其实这是为了后面的multiset做准备,因为multiset可以允许有多个相同的key值,而set和mulitset的关联性很大,所以为了接口的统一性将这里的删除值为key的erase的操作的返回值设置为了返回删除key值的个数,在set中当有这个key值的时候删除对应的节点成功返回1,当没有这个key值的时候删除对应节点失败返回0
  3. 第三点是删除一段迭代器区间
int main()
{
	int arr[5] = { 4,3,5,1,6 };
	set<int> s(arr, arr + sizeof(arr) / sizeof(arr[0]));

	s.erase(s.begin());

	size_t t = s.erase(5);
	cout << t << endl;

	s.erase(s.begin(), s.end());

	return 0;
}

运行结果如下
在这里插入图片描述

进行调试

  1. s的初始状态

在这里插入图片描述

  1. 删除一个迭代器位置

在这里插入图片描述
3. 删除一个存在的key值,由于是在set中,所以key值只有1个,所以删除了一个key返回1
在这里插入图片描述
4. 删除一个不存在的key值,当要删除的key值不存在,所以编译器不会进行任何删除操作,所以未进行删除返回0,代表删除了0个key值,即没有删除key值
在这里插入图片描述
5. 删除一段迭代器区间,这里小编传入的是这个s的开头和结尾的迭代器,所以会进行全部删除
在这里插入图片描述

swap、clear

在这里插入图片描述在这里插入图片描述

  1. swap是交换两个set对象的数据
  2. clear是将set对象的数据清空
int main()
{
	int arr[5] = { 3,1,5,2,99 };

	set<int> s1(arr, arr + sizeof(arr) / sizeof(arr[0]));
	set<int> s2;

	s1.swap(s2);
	s2.clear();

	return 0;
}

运行结果如下

  1. 进行调试,初始状态的s1和s2

在这里插入图片描述

  1. 交换后的s1和s2

在这里插入图片描述

  1. 清空后的s1

在这里插入图片描述

find

在这里插入图片描述

  1. find是获取一个元素的迭代器,如果没有查找到那么返回end()迭代器,所以我们在进行使用find查找的迭代器的时候,应该使用if语句判断一下是否不等于end()之后在进行后序对迭代器的操作,那么find就可以与erase或insert进行搭配使用
  2. 同时erase自己也可以直接使用元素进行删除,那么这里为什么还要提供find呢?其实是为了满足某些场景需求,例如先使用find查找一个元素,如果没有这个元素那么插入一个,如果有了那么进行另外的删除操作
  3. 同时这里需要注意,库里也有find,为什么这里要单独提供find呢?因为库里的是给出区间遍历查找,时间复杂度为O(N),set由于是在二叉搜索数的基础上进行了平衡,所以成员函数find的查找效率高为O(logN)
int main()
{
	int arr[5] = { 3,1,5,2,99 };

	set<int> s(arr, arr + sizeof(arr) / sizeof(arr[0]));

	set<int>::iterator it = s.find(99);
	if (it != s.end())
	{
		cout << *it << endl;
		s.erase(it);
	}
	else
	{
		s.insert(99);
	}

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

	return 0;
}

运行结果如下
在这里插入图片描述

count

在这里插入图片描述

  1. count是返回key值对应的个数,由于个数不能为负数,所以返回值是size_t类型的,奇怪,在set中key要么是1个要么是0个,这里难不成还会有多个,其实这里同样是为了和multiset保持接口的一致性,multiset允许多个key值存在,所以当myltiset调用count的时候可能key值对应的个数会有多个,为了set和multiset接口的统一性,我们给set也提供一个count接口,count接口在set中可以用于判空,count其实也不常用,因为set其实这部分有一个find就足够了
int main()
{
	int arr[5] = { 3,1,5,2,99 };

	set<int> s(arr, arr + sizeof(arr) / sizeof(arr[0]));

	size_t t = s.count(99);//统计一个已经存在的key值
	cout << t << endl;
	
	t = s.count(6);//统计一个不存在的key值
	cout << t << endl;

	return 0;
}

运行结果如下
在这里插入图片描述

lower_bound 下界

在这里插入图片描述

upper_bound 上界

在这里插入图片描述

  1. lower_bound是下界(>=)的意思,即寻找比给定key值等于大于的值的位置的迭代器,有等于优先是等于,其次是大于,如果找不到那么返回end()
  2. upper_bound(>)是上界的意思,即寻找比给定key值大的值的位置的迭代器,如果找不到那么返回end()
  3. lower_bound和upper_bound通常是搭配使用,用于寻找指定区间的迭代器(前闭区间后开区间),通常要使用erase或insert插入一段区间,对区间进行操作,那么就会用到这两个函数
int main()
{
	int arr[] = { 3,1,5,2,77,22,99 };

	set<int> s(arr, arr + sizeof(arr) / sizeof(arr[0]));

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

	set<int>::iterator it = s.lower_bound(0);
	//由于0不存在,lower_bound是寻找等于大于位置的迭代器,优先找等于,
	//等于找不到,所以会去寻找比0大的值的位置的迭代器即1的迭代器
	cout << *it << endl;

	set<int>::iterator it1 = s.lower_bound(2);
	set<int>::iterator it2 = s.upper_bound(77);

	cout << *it1 << endl;
	cout << *it2 << endl;//这里的序列中99比77大,所以返回的是99位置的迭代器

	//使用这两个函数去找[2,77]返回的是[2,99),因为接口接收区间只能
	//是前闭区间后开区间的形式,所以调用接口去对[2,99)进行操作可以
	//保证[2,77]这段序列中的值全部被删除
	s.erase(it1, it2);

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

	return 0;
}

运行结果如下
在这里插入图片描述

equal_range

在这里插入图片描述

  1. equal_range的意思是相等的范围,即去寻找和key值相等序列的开头和结尾位置的迭代器,同样疑惑在set中key值只有一个或0个,怎么还会有和key值相等的序列呢?其实这里同样是为了和multiset保持接口的一致性,即multiset中可以有多个相同的key值,即使用equal_range去找key值会找到的是一段序列,那么equal_range对于multiset的作用不可谓不大
  2. 开头位置的迭代器是找到等于大于key值位置的迭代器(和lower_bound类似),结尾位置的迭代器找的是大于key值位置的迭代器(和upper_bound类似),例如set的数据序列为1,5,7,那么传入5给equal_range,那么就会将5位置的迭代器传给开头位置的迭代器,7位置的迭代器传给结尾位置的迭代器,即返回[5,7)这个序列,那么进行erase进行删除,就会将key值为5的序列全部删除,由于是两个迭代器需要返回,return无法返回两个迭代器,所以将这两个迭代器其放在pair中进行返回,pair的first放开头的迭代器,pair的second放结尾的迭代器
  3. 如果给equal_range的key值没有,那么会返回比key值大的位置的迭代器作为开头位置的迭代器和结尾位置的迭代器进行返回,举例set数据序列为1,5,7,那么传入查找3作为key值进行查找,3没有,那么就会找比key值3大的值的迭代器即5位置的迭代器作为开头和结尾位置的迭代器,那么就会返回[5,5),那么由于成员函数对序列区间进行操作是前闭区间后开区间,那么[5,5)这个序列就相当于不存在的区间,如果比key值(key值不存在set的数据序列中)大的位置的值都没有那么会返回end()作为开头和结尾的位置的迭代器即[end(),end()),即如果给equal_range的key值不存在,那么equal_range会返回一个不存在的区间
int main()
{
	int arr[] = { 3,1,5,2,77,22,99 };

	set<int> s(arr, arr + sizeof(arr) / sizeof(arr[0]));

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

	pair<set<int>::iterator, set<int>::iterator> p1 = s.equal_range(7);
	//auto p = s.equal_range(7); //这里的7不存在,那么就会将比7大的值的位置的
	cout << *(p1.first) << endl; //迭代器作为开头和结尾位置的迭代器进行返回,
	cout << *(p1.second) << endl << endl;//那么这里对应就为[22,22)

	pair<set<int>::iterator,set<int>::iterator> p = s.equal_range(77);
	//auto p = s.equal_range(77);
	cout << *(p.first) << endl;
	cout << *(p.second) << endl;

	s.erase(p.first, p.second);

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

	return 0;
}

运行结果如下
在这里插入图片描述

四、multiset

multiset的介绍

在这里插入图片描述

  1. multiset是类模板,其基本使用以及接口与set都相同,唯一不同是multiset中可以有多个相等的key值
  2. multiset的其它性质和set类似,这里小编就不过多赘述

multiset的使用

这里小编仅挑一些multiset接口与set接口的作用有差异的multiset接口进行讲解,并且也不会讲解的和set一样细致,因为multiset在使用上和set几乎一致
在使用multiset的时候不要忘记包头文件#include <set>

insert
  1. 与set不同的是,multiset可以插入多个相同的key值
int main()
{
	multiset<int> ms;
	ms.insert(7);
	ms.insert(1);
	ms.insert(19);
	ms.insert(5);
	ms.insert(5);
	ms.insert(5);
	ms.insert(5);
	ms.insert(5);

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

	return 0;
}

运行结果如下
在这里插入图片描述

erase
  1. 与set不同的是,multiset删除key值,如果key值有多个,那么会将多个相同的key值同时删除
int main()
{
	multiset<int> ms;
	ms.insert(7);
	ms.insert(1);
	ms.insert(19);
	ms.insert(5);
	ms.insert(5);
	ms.insert(5);
	ms.insert(5);
	ms.insert(5);

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

	ms.erase(5);

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

	return 0;
}

运行结果如下
在这里插入图片描述

find
  1. 与set不同的是,multiset的find的key值可能会有对应的多个相等的key值,那么find会返回迭代器遍历(中序遍历)中的第一个key值位置的迭代器
count
  1. 将key值传入count,count会返回key值的个数,在multiset中允许存在多个相同的key值,所以在multiset中相同key值如果有多个,那么返回的key值个数也为多个
int main()
{
	multiset<int> ms;
	ms.insert(7);
	ms.insert(1);
	ms.insert(19);
	ms.insert(5);
	ms.insert(5);
	ms.insert(5);
	ms.insert(5);
	ms.insert(5);

	size_t t = ms.count(5);
	cout << t << endl;

	return 0;
}

运行结果如下
在这里插入图片描述

equal_range
  1. 与set不同的是,multiset的equal_range查找的key值可以对应有多个相等key值,即如果查找的key值对应有多个相等的key值,那么进行删除操作会将这些相等的key值全部删除
int main()
{
	multiset<int> ms;
	ms.insert(7);
	ms.insert(1);
	ms.insert(19);
	ms.insert(5);
	ms.insert(5);
	ms.insert(5);
	ms.insert(5);
	ms.insert(5);

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

	pair<multiset<int>::iterator, multiset<int>::iterator> p = ms.equal_range(5);
	//auto p = ms.equal_range(5);
	cout << *(p.first) << endl;
	cout << *(p.second) << endl;

	ms.erase(p.first, p.second);

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

	return 0;
}

运行结果如下
在这里插入图片描述

五、map

在这里插入图片描述

map的介绍

  1. map是关联式容器,它是按照特定次序(按照key值比较)进行存储由键值key和值value组成的元素
    在这里插入图片描述
  2. 在map中键值key通常用于排序和唯一的标识元素,而值value中存储和键值关联的内容,键值key和value的类型可能相同也可能不同,并且在map内部,key与value通过成员类型value_type(pair)绑定在一起,typedef pair<const Key, T> value_type。并且通常是将键值key和值value放在pair结构中存储到map中
  3. map通过键值访问单个元素的速度通常比unorderd_map容器慢,但是使用迭代器对map中的key元素进行遍历得到的是一个有序序列
  4. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value,可以对value进行访问,修改
  5. map通常被实现为平衡二叉搜索树(红黑树),查找单个元素的效率为O(logN)
  6. 在map中key是唯一的不可以修改,与key对应的value可以修改

map的使用

在使用map容器的时候需要包头文件#include <map>

在这里插入图片描述

构造函数

在这里插入图片描述

  1. 第一个是无参数的构造,第二个是使用一段迭代器区间构造,第三个是拷贝构造
#include <iostream>
#include <utility>
#include <map>

using namespace std;

int main()
{
	pair<string, string> arr[] = { make_pair("delete", "删除"),
	make_pair("insert", "插入") ,
	make_pair("add", "增加") };

	map<string, string> m1;
	map<string, string> m2(arr, arr + sizeof(arr) / sizeof(arr[0]));

	map<string, string> m3(m2);

	return 0;
}

运行结果如下
在这里插入图片描述

operator=

在这里插入图片描述

  1. 为容器分配新的内容,替换当前内容
int main()
{
	pair<string, string> arr[] = { make_pair("delete", "删除"),
	make_pair("insert", "插入") ,
	make_pair("add", "增加") };

	map<string, string> m1;
	map<string, string> m2(arr, arr + sizeof(arr) / sizeof(arr[0]));

	m1 = m2;

	return 0;
}

运行结果如下

  1. 进行调试,赋值前

在这里插入图片描述

  1. 进行调试,赋值后

在这里插入图片描述

迭代器 begin、end 范围for
  1. map的迭代器需要特别介绍,map的迭代器的使用与之前的容器有所不同,当然框架还是相同的,只不过map中存储的是一个结构对象pair,那么迭代器it进行解引用拿到的是pair这个结构对象,那么我们本质想要访问的是pair这个结构对象中存储的first(key),second(value),这里进行解引用拿不到我们想要的值,那么由于拿到的是pair这个结构对象,那么我们可以使用 . 的方式访问结构的成员变量,这样就可以访问到pair这个结构对象中存储的first(key),second(value)了,但是这种方式略微繁琐
  2. 那么接下来小编就介绍一种略微简单的方法,即使用箭头 ->,当迭代器中存储的是一个结构的使用,这里map的迭代器类似于链表list中的迭代器(【c++】STL容器-list的模拟实现(迭代器由浅入深逐步完善2w字讲解)详情请点击<——),即map迭代器封装了一个指向map的一个节点的指针,我们可以类似结构的指针的方式去访问它的成员函数,因为迭代器中重载了箭头 -> 运算符,这种方式在使用上略微简单,小编推荐这种方式访问pair这个结构对象中存储的first(key),second(value)
  3. 那么既然支持迭代器,那么就肯定支持范围for,这里的范围for在使用上推荐规范使用即不改变值加const修饰,由于map中存储的是一个结构,拷贝传参消耗大,所以这里使用引用,同样的范围for的变量e拿到的是map节点中存储的pair这个结构对象的引用,那么既然是拿到的是一个对象,访问这个对象的成员变量应该使用 . 的方式进行访问pair这个结构对象中存储的first(key),second(value)
int main()
{
	pair<string, string> arr[] = { make_pair("delete", "删除"),
	make_pair("insert", "插入") ,
	make_pair("add", "增加") };

	map<string, string> m(arr, arr + sizeof(arr) / sizeof(arr[0]));

	map<string, string>::iterator it = m.begin();
	//auto it = m.begin();
	while (it != m.end())
	{
		cout << (*it).first << ':' << (*it).second << endl;
		cout << it->first << ':' << it->second << endl;//建议这种方式访问key,value
		++it;
	}
	cout << endl;

	for (const auto& e : m)
	{
		cout << e.first << ':' << e.second << endl;
	}
	cout << endl;

	return 0;
}

运行结果如下
在这里插入图片描述

empty、size

在这里插入图片描述在这里插入图片描述

  1. empty判断容器内的数据是否为空
  2. size返回容器内的数据节点的大小
int main()
{
	pair<string, string> arr[] = { make_pair("delete", "删除"),
	make_pair("insert", "插入") ,
	make_pair("add", "增加") };

	map<string, string> m(arr, arr + sizeof(arr) / sizeof(arr[0]));

	cout << m.empty() << endl;
	cout << m.size() << endl;

	return 0;
}

运行结果如下
在这里插入图片描述

insert

在这里插入图片描述

  1. 第一点,insert插入一个pair类型的键值对<key,value>,如果插入成功,那么返回一个pair类型的键值对<iterator,bool>,其中第一个数据first是插入位置的迭代器,第二个位置是是否插入成功,由于map中的key值不能重复且只能有一个,如果key值已经有了,那么返回的是原key对应位置的迭代器以及false插入是被,如果key值没有,那么返回的是新插入key对应位置的迭代器以及true插入成功,由于return不能返回两个值,所以如果想返回多个值,必须放在一个结构中进行返回,这里是放在了键值对pair中进行返回
  2. 第二点是在迭代器位置(这个迭代器位置对于编译器来说只是一个建议位置,具体插入在哪里还是要看原map的插入规则)插入一个pair类型的键值对<key,value>,第三点是插入一段迭代器区间,同样这两点和set类似,这里小编就不过多演示
int main()
{
	map<string, string> m;

	pair<string, string> p("delete", "删除");
	m.insert(p);//插入一个pair对象

	m.insert(pair<string, string>("insert", "插入"));//插入一个pair的匿名对象

	m.insert(make_pair("add", "增加"));//c++98支持,调用make_pair函数
	                                  //返回一个pair对象进行插入(推荐使用)

	m.insert({ "free", "释放" });//c++11支持,支持了多参数的构造函数进行隐式类型转换为
	                             //pair对象进行插入

	return 0;
}

运行结果如下
在这里插入图片描述

find

在这里插入图片描述

  1. find传入key,如果key存在那么返回key对应位置的迭代器,如果key值不存在,那么返回end()
int main()
{
	pair<string, string> arr[] = { make_pair("delete", "删除"),
	make_pair("insert", "插入") ,
	make_pair("add", "增加") };
	
	map<string, string> m(arr, arr + sizeof(arr) / sizeof(arr[0]));

	map<string, string>::iterator it = m.find("delete");
	if (it != m.end())
	{
		cout << it->second << endl;
	}
	else
	{
		cout << "没有对应的key值,无法查找" << endl;
	}

	return 0;
}

运行结果如下
在这里插入图片描述

operator[]

在这里插入图片描述

  1. operator[]的原理是使用<key,T()>构造出一个pair的键值对,这个T()是调用的value对应类型的默认构造函数去生成对应类型的匿名对象,然后调用insert将键值对插入到map中
  2. 如果map中已经有了key,那么insert会返回key位置所在的迭代器,那么上面构造的键值对不会起作用,即不会影响原map中的key和对应的value
  3. 如果map中没有key,那么insert会向map插入该键值对,并且返回新插入位置的迭代器
  4. 接下来operator会根据insert返回的迭代器拿到key对应的value,那么operator[]会将value以引用的形式返回,因为value被放在pair结构对象中,pair结构对象存储在map节点中,这个节点在operator[]函数外已存在
  5. 那么使用[]的行为就是,在[]内输入key,返回得到key对应的value的引用,我们可以对这个value进行访问或修改
统计次数,方法一
  1. 使用find查找加insert的方法进行统计名字出现的次数
  2. 当想要退出循环的时候依次按下ctrl+z,空格即可退出
int main()
{
	string arr[] = { "听安","听安","边路之怪","大勇",
		"安天帝","YGking","听安","大勇","YGking" };
	map<string, int> countmap;
	
	for (const auto& str : arr)
	{
		if (countmap.find(str) == countmap.end())
		{
			countmap.insert(make_pair(str, 1));
		}
		else
		{
			countmap.find(str)->second++;
		}
	}

	string tmp;
	while (cin >> tmp)
	{
		map<string, int>::iterator it = countmap.find(tmp);
		if (it != countmap.end())
		{
			cout << it->second << endl;
		}
		else
		{
			cout << "输入非法,请重新输入" << endl;
		}
	}

	return 0;
}

运行结果如下
在这里插入图片描述

统计次数,方法二
  1. 使用operator[]天然的性质就可以进行统计次数,当要查找的key值不存在的时候,会调用value对应类型的构造函数去生成匿名对象生成键值对<key,T()>插入到map中
  2. 这里我们给value的类型是int,在c++中对内置类型做了升级,为了更好的支持模板,所以给内置类型都加上了默认构造,即调用默认构造生成匿名对象,对应int生成的匿名对象的值恰好为0,那么我们对其进行加加,那么就是对应第一次出现,再次出现的时候再加加,那么就是第二次出现,依次类推
int main()
{
	string arr[] = { "听安","听安","边路之怪","大勇",
		"安天帝","YGking","听安","大勇","YGking" };
	map<string, int> countmap;
	
	for (const auto& str : arr)
	{
		countmap[str]++;
	}

	string tmp;
	while (cin >> tmp)
	{
		map<string, int>::iterator it = countmap.find(tmp);
		if (it != countmap.end())
		{
			cout << it->second << endl;
		}
		else
		{
			cout << "输入非法,请重新输入" << endl;
		}
	}

	return 0;
}

运行结果如下
在这里插入图片描述

erase

在这里插入图片描述

  1. 第一点是删除一个迭代器位置
  2. 第二点是删除某个key值,但是观察这个返回值,是删除的这个key值的个数。是不是很奇怪?明明在map中key值只有一个或没有,这里要是想要返回的话直接返回bool值即可,何必返回删除这个key值的个数呢?其实这是为了后面的multimap做准备,因为multimap可以允许有多个相同的key值,而map和mulitmap的关联性很大,所以为了接口的统一性将这里的删除值为key的erase的操作的返回值设置为了返回删除key值的个数,在map中当有这个key值的时候删除对应的节点成功返回1,当没有这个key值的时候删除对应节点失败返回0
  3. 第三点是删除一段迭代器区间
int main()
{
	pair<string, string> arr[] = { make_pair("delete", "删除"),
	make_pair("insert", "插入") ,
	make_pair("add", "增加") };
	
	map<string, string> m(arr, arr + sizeof(arr) / sizeof(arr[0]));

	m.erase(m.begin());

	size_t t = m.erase("insert");
	cout << t << endl;

	t = m.erase("hello");
	cout << t << endl;

	m.erase(m.begin(), m.end());

	return 0;
}

运行结果如下
在这里插入图片描述

进行调试

  1. 初始状态的m

在这里插入图片描述

  1. 删除一个迭代器位置

在这里插入图片描述

  1. 删除一个key值对应的节点,删除后erase返回1,代表删除了一个节点

在这里插入图片描述

  1. 删除一个不存在的key值对应的节点,这时候erase不会进行删除,返回0,代表删除了0个节点

在这里插入图片描述

  1. 删除一个迭代器区间,这个迭代器区间对应m的开头和结尾区间,即代表删除全部节点

在这里插入图片描述

swap、clear

在这里插入图片描述在这里插入图片描述

  1. swap交换两个map对象的数据
  2. clear清除map对象的数据
int main()
{
	pair<string, string> arr[] = { make_pair("delete", "删除"),
	make_pair("insert", "插入"),
	make_pair("add", "增加") };

	map<string, string> m1;
	map<string, string> m2(arr, arr + sizeof(arr) / sizeof(arr[0]));
	
	m2.swap(m1);
	m1.clear();

	return 0;
}

调试结果如下

  1. m1和m2的初始状态

在这里插入图片描述

  1. 交换m1和m2的数据

在这里插入图片描述

  1. 清除m1的数据

在这里插入图片描述

count

在这里插入图片描述

  1. count是返回key值对应的节点个数,由于个数不能为负数,所以返回值是size_t类型的,奇怪,在map中key要么是1个要么是0个,这里难不成还会有多个,其实这里同样是为了和multimap保持接口的一致性,multimap允许多个key值存在,所以当myltimap调用count的时候可能key值对应的节点个数会有多个,为了map和multimap接口的统一性,我们给map也提供一个count接口,count接口在map中可以用于判空,count其实也不常用,因为map其实这部分有一个find就足够了
int main()
{
	pair<string, string> arr[] = { make_pair("delete", "删除"),
	make_pair("insert", "插入") ,
	make_pair("add", "增加") };

	map<string, string> m(arr, arr + sizeof(arr) / sizeof(arr[0]));

	size_t t = m.count("insert");
	cout << t << endl;

	return 0;
}

运行结果如下
在这里插入图片描述

lower_bound

在这里插入图片描述

upper_bound

在这里插入图片描述

  1. lower_bound,下界:寻找等于大于key值的节点,并返回对应的迭代器,当有等于的时候优先找等于的节点,当没有比key大的值的时候,返回end()
  2. upper_bound,上界:寻找大于key值的节点,并返回对应的迭代器,当没有比key大的值的时候,返回end()
  3. 通常来讲lower_bound和upper_bound是搭配使用的,其它具体的解释请看上面中的set中介绍的lower_bound和upper_bound
  4. 那么当需要对一段区间进行操作的时候,这个lower_bound和upper_bound就可以与insert或erase中的迭代器区间的操作进行搭配使用
int main()
{
	pair<int, char> arr[] = { make_pair(1,'a'),make_pair(3,'c'),
	make_pair(2, 'b') };

	map<int, char> m(arr, arr + sizeof(arr) / sizeof(arr[0]));

	for (const auto& e : m)
	{
		cout << e.first << ':' << e.second << endl;
	}
	cout << endl;

	map<int, char>::iterator it1 = m.lower_bound(1);
	map<int, char>::iterator it2 = m.upper_bound(2);

	cout << it1->first << ':' << it1->second << endl;
	cout << it2->first << ':' << it2->second << endl;
	cout << endl;
	
	//迭代器区间的传入必须要求前闭区间后开区间[1,3)---->对应将[1,2]这段区间全部删除
	m.erase(it1, it2);

	for (const auto& e : m)
	{
		cout << e.first << ':' << e.second << endl;
	}
	cout << endl;

	return 0;
}

运行结果如下
在这里插入图片描述

equal_range

在这里插入图片描述

  1. equal_range的意思是相等的范围,即去寻找和key值相等序列的开头和结尾位置的迭代器,同样疑惑在map中key值只有一个或0个,怎么还会有和key值相等的序列呢?其实这里同样是为了和multimap保持接口的一致性,即multimap中可以有多个相同的key值,即使用equal_range去找key值会找到的是一段序列,那么equal_range对于multiset的作用不可谓不大
  2. 开头位置的迭代器是找到等于大于key值位置的迭代器(和lower_bound类似),结尾位置的迭代器找的是大于key值位置的迭代器(和upper_bound类似),例如map的key的数据序列为1,5,7,那么传入5给equal_range,那么就会将5位置的迭代器传给开头位置的迭代器,7位置的迭代器传给结尾位置的迭代器,即返回[5,7)这个序列,那么进行erase进行删除,就会将key值为5的序列全部删除,由于是两个迭代器需要返回,return无法返回两个迭代器,所以将这两个迭代器其放在pair中进行返回,pair的first放开头的迭代器,pair的second放结尾的迭代器
  3. 如果给equal_range的key值没有,那么会返回比key值大的位置的迭代器作为开头位置的迭代器和结尾位置的迭代器进行返回,举例map的key的数据序列为1,5,7,那么传入查找3作为key值进行查找,3没有,那么就会找比key值3大的值的迭代器即5位置的迭代器作为开头和结尾位置的迭代器,那么就会返回[5,5),那么由于成员函数对序列区间进行操作是前闭区间后开区间,那么[5,5)这个序列就相当于不存在的区间,如果比key值(key值不存在set的数据序列中)大的位置的值都没有那么会返回end()作为开头和结尾的位置的迭代器即[end(),end()),即如果给equal_range的key值不存在,那么equal_range会返回一个不存在的区间
int main()
{
	pair<int, char> arr[] = { make_pair(1,'a'),make_pair(3,'c'),
	make_pair(2, 'b') };

	map<int, char> m(arr, arr + sizeof(arr) / sizeof(arr[0]));

	for (const auto& e : m)
	{
		cout << e.first << ':' << e.second << endl;
	}
	cout << endl;

	pair<map<int, char>::iterator, map<int, char>::iterator> p = m.equal_range(1);
	//auto p = m.equal_range(1);
	
	cout << (p.first)->first << ':' << (p.first)->second << endl;
	cout << (p.second)->first << ':' << (p.second)->second << endl;
	cout << endl;

	//迭代器区间的传入必须要求前闭区间后开区间[1,2)---->对应将[1,1]这段区间全部删除
	m.erase(p.first, p.second);

	for (const auto& e : m)
	{
		cout << e.first << ':' << e.second << endl;
	}
	cout << endl;

	return 0;
}

运行结果如下
在这里插入图片描述

六、multimap

在这里插入图片描述

multimap的介绍

  1. multimap是关联式容器,它是按照特定顺序,存储由key和value映射的键值对<key,value>,其中多个键值对的key是可以重复
  2. multimap通过key访问单个元素的速度通常比unorderd_multimap容器慢,但是使用迭代器遍历
    multimap的元素可以得到关于key的有序序列
  3. multimap的底层是通过平衡二叉搜索树(红黑树)实现的
  4. multimap与map唯一不同的是:map中的key是唯一的,multimap中的key是可以重复的
  5. multimap不提供operator[],因为有多个key值相同的键值对<key,value>,使用[]传入key需要返回key对应的value,那么对于这个value,value是有多个的,无法确定要返回哪一个value,所以干脆multimap不提供这个operator[]这个接口了

multimap的使用

这里小编仅挑一些multimap接口与map接口的作用有差异的multimap接口进行讲解,并且也不会讲解的和map一样细致,因为multimap和map在使用上几乎一致
在使用multimap的时候不要忘记包头文件#include <map>

insert
  1. 与map不同的是,multimap可以插入多个相同的key值相同的键值对<key,value>,甚至在满足key相同的前提下,value也可以相同(value也可以不同)
int main()
{
	pair<int, char> arr[] = { make_pair(1,'a'),make_pair(3,'c'),
		make_pair(1,'a'),make_pair(3,'d'),
		make_pair(2, 'b') };

	multimap<int, char> mp;

	for (auto& e : arr)
	{
		mp.insert(e);
	}

	for (const auto& e : mp)
	{
		cout << e.first << ':' << e.second << endl;
	}
	cout << endl;

	return 0;
}

运行结果如下
在这里插入图片描述

erase
  1. 与map不同的是,multimap删除key值,如果key值相同的有多个,那么会将多个相同的key值对应存储键值对<key,value>的多个multimap节点全部删除
  2. 并且当是传入key值使用erase进行删除后还会返回删除了几个multimap节点,因为删除的个数不能为负数,所以类型是size_t
int main()
{
	pair<int, char> arr[] = { make_pair(1,'a'),make_pair(3,'c'),
		make_pair(1,'a'),make_pair(3,'d'),make_pair(1,'a'),
		make_pair(2, 'b') };

	multimap<int, char> mp;

	for (auto& e : arr)
	{
		mp.insert(e);
	}

	for (const auto& e : mp)
	{
		cout << e.first << ':' << e.second << endl;
	}
	cout << endl;

	size_t t = mp.erase(1);
	cout << t << endl << endl;

	for (const auto& e : mp)
	{
		cout << e.first << ':' << e.second << endl;
	}
	cout << endl;

	return 0;
}

运行结果如下
在这里插入图片描述

find
  1. 由于使用key传入find进行查找,对应的key的位置可能有多个,那么默认返回迭代器遍历(中序遍历)的第一个key对应的键值对的节点的迭代器
count
  1. 传入key值给count,那么count会返回相同key值的键值对对应的节点的个数,由于在multimap中允许存在多个相同key值的键值对,所以当传入的key值恰好有多个键值对的key值相同的时候,返回的个数相同key值的键值对对应的节点的个数为多个
int main()
{
	pair<int, char> arr[] = { make_pair(1,'a'),make_pair(3,'c'),
		make_pair(1,'a'),make_pair(3,'d'),make_pair(1,'a'),
		make_pair(2, 'b') };

	multimap<int, char> mp;

	for (auto& e : arr)
	{
		mp.insert(e);
	}

	for (const auto& e : mp)
	{
		cout << e.first << ':' << e.second << endl;
	}
	cout << endl;

	size_t t = mp.count(1);
	cout << t << endl << endl;

	return 0;
}

运行结果如下
在这里插入图片描述

equal_range
  1. 与map不同的是,multimap的equal_range查找的key值可以对应有多个相等key值的键值对的multimap节点,即如果查找的key值对应有多个相等的key值的键值对的multimap节点,那么进行删除操作会将这些相等的key值的键值对的multimap节点全部删除
int main()
{
	pair<int, char> arr[] = { make_pair(1,'a'),make_pair(3,'c'),
		make_pair(1,'a'),make_pair(3,'d'),make_pair(1,'a'),
		make_pair(2, 'b') };

	multimap<int, char> mp;

	for (auto& e : arr)
	{
		mp.insert(e);
	}

	for (const auto& e : mp)
	{
		cout << e.first << ':' << e.second << endl;
	}
	cout << endl;

	pair<multimap<int, char>::iterator, 
		multimap<int, char>::iterator> p = mp.equal_range(1);

	cout << (p.first)->first << ':' << (p.first)->second << endl;
	cout << (p.second)->first << ':' << (p.second)->second << endl << endl;

	mp.erase(p.first, p.second);

	for (const auto& e : mp)
	{
		cout << e.first << ':' << e.second << endl;
	}
	cout << endl;

	return 0;
}

运行结果如下
在这里插入图片描述

七、map和set如何保证key不被修改

map和set、multimap和multiset的迭代器都是双向迭代器,即不能使用库里的algorithm中的sort算法对map和set、multimap和multiset进行排序,因为库里的algorithm中的sort算法要求排序的容器支持随机访问,即库里的algorithm中的sort算法需要排序的容器的迭代器是随机迭代器

库里的algorithm中的还有一个stable_sort算法保证了排序的稳定性

set如何保证key不被修改

在这里插入图片描述

  1. set中遍历访问key需要使用到迭代器,set的const迭代器不能修改key,而set的普通迭代器是由const迭代器封装转换而来的,所以普通迭代器也不能修改key
    在这里插入图片描述
  2. 上图是小编截取的库里实现的set的迭代器,普通迭代器是由const迭代器重命名来的,这里由于普通迭代器是类模板,那么在使用typedef对ret_type::const_iterator进行重命名的时候要使用typename声明ret_type::const_iterator是类型而不是变量
  3. multiset对key不进行修改的限制方式同set

map如何保证key不被修改

在这里插入图片描述

  1. map中是通过给键值对pair的成员变量key的类型加const进行修改不能对key进行修改,同时保证了value的类型不加const修改,即value可以修改
  2. 在map中不可以采取上述类似于set中让普通迭代器由const迭代器封装转换的形式保证key不被修改,因为虽然这样真的保证了key不被修改,但是同时value也被限制不能修改了,但是map中的性质要求value可以修改,所以不能采取类似于set的方式进行限制key
  3. multimap不对key进行修改的限制方式同map

总结

以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!

评论 43
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值