C++set和map详细介绍

文章目录


前言

在本篇文章中,我们将会学到关联式容器set,multiset,map,multimap。

其中前两种容器对应我们上篇二叉树文章中的K模型,后两者容器对应我们的KV模型。

我们来详细看一下吧!!!

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

我们在之前学习到的vector,list,stack,queue等都属于序列式容器,底层是线性结构,只存储数据。

关联式容器也是也是用来存储数据的,不过存储的是<K,V>这样一个键值对。

二者有什么区别呢??

⭐️序列式容器中仅仅存储一个值,关联式容器中存储一个键值对

⭐️序列式容器内部不涉及排序,关联式容器内部自动排序

⭐️序列式容器的查找按照自身的值进行相关查找,关联式容器根据K的值进行查找。

并且关联式速度比序列式容器查找快很多。

键值对

键值对是由<K,V>两个值构成的,其中K叫做键值,V就是与之对应的数据。

比如在日常生活中我们用到的英汉字典,就是一个键值对,每个英文单词对应一个中文翻译。英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。

在C++中STL中已经帮助我们实现了键值对---->(pair)

T1和T2可以是两个不同的类型,访问是值是first,第二个值是second。

二、set

1.set文档介绍

看一下模板参数

🌟T:底层存储的数据类型

🌟Compare:比较方式,默认按照小于方式比较

🌟Alloc:set中元素空间的管理方式,使用STL提供的空间配置器管理

set文档介绍

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

看一下要点:

⭐️⭐️ set底层是一颗二叉树,内容可以删除,但不允许修改,查找某个元素时间复杂度为logN。

⭐️⭐️ set中不允许出现重复元素,本质是排序+去重

⭐️⭐️ set的迭代器遍历是一个有序序列

⭐️⭐️ set存的是一个值value,但是在底层也是一个键值对,不过这个键值对两个值都相同,即<value,value>,我们在使用时,只需要插入就可以,不需要构建键值对。

⭐️⭐️set比较默认按照小于比较

⭐️⭐️对于unique算法,去重相邻重复元素,去重之前需要先排序,才可以达到去重的效果。并不会改变容器的大小,随后调用erase删除这些重复元素。

nums.erase(unique(nums.begin(), nums.end()), nums.end());

2.set成员函数

1.构造函数

🌟🌟空的set

set< int >s;

🌟🌟迭代器区间构造set

set< int >s(s2.begin(),s2.end());

🌟🌟拷贝构造set

set< int >s(s2);

2.迭代器

遍历

🌟🌟迭代器遍历

cpp 复制代码
set<int>s;
int a[] = { 8, 3, 1,1,2,3,4,5,6,7,10, 6, 4, 7, 14, 13 };
for (auto& e : a)
{
	s.insert(e);
}
//迭代器遍历
set<int>::iterator it = s.begin();
while (it != s.end())
{
	cout << *it << " ";
	it++;
}
cout << endl;

我们可以看到确实完成了去重任务。

🌟🌟范围for

cpp 复制代码
	for (auto& e : s)
	{
		cout << e << " ";
	}

3.容量

🌟 🌟 empty

判断set是否为空

🌟 🌟 size

找出set中有效元素的个数。

4.修改

🌟 🌟insert

第一个:直接插入一个值

第二个:在某个位置插入一个值

第三个:插入一个迭代器区间

看起来很简单蛮,我们来看点诡异的,第一个的返回值

pair<iterator,bool> 返回一个键值对??

我们看一下文档的介绍

如果插入成功就返回插入结点的迭代器,并且返回true。

如果插入失败,也就是set中有这个元素,返回set中这个元素的迭代器,返回false。

🌟 🌟erase

第一个删除某个迭代器位置的值

第二个删除这个值,注意这个返回值,返回set中val这个元素的个数。

第三个删除一段迭代器区间
第一个删除和第二个删除有什么不同呢??

看一下这段代码,运行结果是什么??

cpp 复制代码
	set<int>s;//空的set
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	for (auto& e : a)
	{
		s.insert(e);
	}
	for (auto& e : s)
	{
		cout << e << " ";
	}
	cout << endl;
	//查找删除
	set<int>::iterator pos = s.find(3);
	s.erase(pos);
	for (auto& e : s)
	{
		cout << e << " ";
	}
	cout << endl;

成功的把3删除了,那我们如果删除一个不存在的值呢???

cpp 复制代码
	pos = s.find(30);
	s.erase(pos);
	for (auto& e : s)
	{
		cout << e << " ";
	}
	cout << endl;

系统直接崩溃了,所以我们需要加判断!!!

那如果用第二种方法直接删除一个不存在的值呢??

s.erase(20);

我们发现并没有报错。

对于直接删除:在就删除,不在不做任何处理

我们要注意这两个的区别!!!!

🌟 🌟swap

交换两个set的值

🌟 🌟clear

清空set中的元素

5.其他

🌟 🌟find

查找某个值,如果存在返回对应的迭代器区间,不存在返回end的迭代器。

🌟 🌟count

记录val这个值的个数,在set中出现了多少次。返回set中值为x的元素的个数

🌟 🌟lower_bound

记录大于等于val的迭代器

如果这个值存在,返回这个值的迭代器。

如果这个值不存在,返回大于这个值的迭代器。

在set中存在3,解引用就返回3。查找9,9不存在,就返回下一个

🌟 🌟upper_bound

upper_bound也是同样的道理,但是不同的是,这个返回大于val的迭代器。

三.multiset

multiset本质和set没有太大差别

我们先来看一下文档

  1. multiset是按照特定顺序存储元素的容器,其中元素是可以重复的。
  2. 在multiset中,元素的value也会识别它(因为multiset中本身存储的就是<value, value>组成
    的键值对,因此value本身就是key,key就是value,类型为T). multiset元素的值不能在容器
    中进行修改(因为元素总是const的),但可以从容器中插入或删除。
  3. 在内部,multiset中的元素总是按照其内部比较规则(类型比较)所指示的特定严格弱排序准则
    进行排序。
  4. multiset容器通过key访问单个元素的速度通常比unordered_multiset容器慢,但当使用迭
    代器遍历时会得到一个有序序列。
  5. multiset底层结构为二叉搜索树(红黑树)

与set相比,不同的是,multiset允许重复的值存在,本质就是排序。其他的都与set相同。

cpp 复制代码
	set<int>s;
	int a[] = { 8, 3, 1,1,2,3,4,5,6,7,10, 6, 4, 7, 14, 13 };
	for (auto& e : a)
	{
		s.insert(e);
	}
	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	for (auto& e : s)
	{
		cout << e << " ";
	}

看一下运行结果

我们再来看一下一个的返回值

这个地方就有很大的意义了。

count的意义也就发挥出来了。

四.map

map就是对应我们二叉树中的KV模型

1.map文档介绍

看一下模板参数

包括四个值:

🌟Key:键值对中的key值

🌟T:键值对中的value值

🌟Compare:比较器类型,一般是按照小于比较。如果是自定义类型,需要自己传递这个比较方式,达到要求。

🌟Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的

空间配置器

set文档介绍

  1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
  2. 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair:
    typedef pair<const key, T> value_type;
  3. 在内部,map中的元素总是按照键值key进行比较排序的。
  4. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
  5. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
  6. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))

看一下要点:

🌟🌟map中存储的是一个键值对,键值对中第一个元素,也就是key,根据key值进行排序,确定唯一元素。

第二个值value,与key的内容关联。

🌟🌟元素不可以修改key的值,但是可以修改value的值。

🌟🌟支持下标访问,把key放在[ ]中,可以找到与之对应的value元素。

2.map成员函数

这其中与很多和set的成员函数相同,在这里我就不再重复阐述了。

接下来主要讲述一些不同点,以及重点内容。

1.构造

🌟🌟1.有名构造

pair<string, string>p = { "pear", "梨" };

m.insert( p);

🌟🌟2.匿名构造

m.insert(pair<string, string>("apple", "苹果"));

🌟🌟3.make_pair

m.insert(make_pair("banana", "香蕉"));

c++98新增的,本质就是一个函数模板

🌟🌟4.c++11多参数隐式类型转换

m.insert({"strawberrier" ,"草莓"});

2.insert插入

我们看一下官方文档

我们主要看一下第一条:

插入的是一个pair类型,返回值也是一个pair类型

我们来看一下有关返回值的介绍

如果插入到元素存在,返回值中的iterator就指向这个存在的位置的迭代器,bool值为false

如果插入元素不存在,返回值中的iterator就指向这个新插入的位置的迭代器,bool值为true

这里的元素插入是看key的值,与value无关。

3.count

count返回key为x的键值在map中的个数,注意map中key是唯一的,因此该函数的返回值要么为0,要么为1,因此也可以用该函数来检测一个key是否在map中。

4.迭代器

我们注意一下map有两个值,我们再遍历的时候,不能只遍历一个,要把两个分开。

cpp 复制代码
	for (auto& kv : m)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	cout << endl;

first代表pair第一个值,second代表pair第二个值。

5.【】和at

map中支持【】访问元素。

如果我们想要统计水果出现的次数

第一步:先看这个水果在不在!!

第二步:如果不在,就把这一个键值对插入进去。如果在,就让vaule这个值加加。

最后遍历一边元素,就完成了

我们再map中不用折磨麻烦,我们看一下这个巧妙的代码。

cpp 复制代码
// 统计水果出现的次数
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
map<string, int> m;
for (auto& e : arr)
{
	m[e]++;
}

m[e]++;就完成了操作。

我们来看一下怎末实现的??

【】的返回值是value类型,参数是一个key类型。

在文档中还有这样一段话,我们来剖析一下。

这个【】需要调用insert,insert上面我们已经介绍过了,拆分来看一下

🌟insert(make_pair(k,mapped_type()))这个是insert的原型,插入一个键值对,key值必须传入,如果value不传入,采用缺省值。

这个k存在,就返回这个k这个值的迭代器,不存在,就返回新插入这个k的迭代器。

🌟this->insert(make_pair(k,mapped_type()))调用这个返回值pair

🌟(this->insert(make_pair(k,mapped_type()))).first调用这个返回值的pair的第一个元素iterator,迭代器本身就是指针。

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

这个迭代器指向的第二个元素,就是value.

通过这种方式我们就拿到了value的值

如果不存在,插入成功,返回新插入元素的迭代器

如果已存在,插入失败,返回该key所在位置的迭代器

operator[]函数最后将insert返回值键值对中的value返回
在元素访问时,有一个与operator[]类似的操作at()(该函数不常用)函数,都是通过key找到与key对应的value然后返回其引用

不同的是:当key不存在时,operator[]用默认value与key构造键值对然后插入,返回该默认value,at()函数直接抛异常。

我们看一下这段代码的结果是什么??

cpp 复制代码
	map<string, string> dict;
	dict.insert(make_pair("sort", "排序"));
	dict.insert(make_pair("string", "字符串"));
	dict.insert(make_pair("sort", "xxx"));

只与key的值有关,与value无关。

key相同,value不同,不会插入也不会更新

这个【】就有很多功能了,也可以插入,也可以查找,也可以修改

五.multimap

map和multimap没有太大区别

  1. Multimaps是关联式容器,它按照特定的顺序,存储由key和value映射成的键值对<key,value>,其中多个键值对之间的key是可以重复的。
  2. 在multimap中,通常按照key排序和惟一地标识元素,而映射的value存储与key关联的内容。key和value的类型可能不同,通过multimap内部的成员类型value_type组合在一起,value_type是组合key和value的键值对:
    typedef pair<const Key, T> value_type;
  3. 在内部,multimap中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对key进行排序的。
  4. multimap通过key访问单个元素的速度通常unordered_multimap容器慢,但是使用迭代器直接遍历multimap中的元素可以得到关于key有序的序列。
  5. multimap在底层用二叉搜索树(红黑树)来实现

multimap和map的唯一不同就是:map中的key是唯一的,而multimap中key是可以重复的。

multimap中没有重载operator[]操作

总结

以上就是今天要讲的内容,本文仅仅详细介绍了C++map和set的相关内容。希望对大家的学习有所帮助,仅供参考 如有错误请大佬指点我会尽快去改正 欢迎大家来评论~~ 😘 😘 😘

相关推荐
Beau_Will15 分钟前
数据结构-树状数组专题(1)
数据结构·c++·算法
hunandede1 小时前
av_image_get_buffer_size 和 av_image_fill_arrays
c++
Code_Artist2 小时前
使用Portainer来管理并编排Docker容器
docker·云原生·容器
Eternal-Student2 小时前
【docker 保存】将Docker镜像保存为一个离线的tar归档文件
运维·docker·容器
怀澈1222 小时前
高性能服务器模型之Reactor(单线程版本)
linux·服务器·网络·c++
码农小丘2 小时前
一篇保姆式centos/ubuntu安装docker
运维·docker·容器
chnming19873 小时前
STL关联式容器之set
开发语言·c++
威桑3 小时前
MinGW 与 MSVC 的区别与联系及相关特性分析
c++·mingw·msvc
熬夜学编程的小王3 小时前
【C++篇】深度解析 C++ List 容器:底层设计与实现揭秘
开发语言·数据结构·c++·stl·list
yigan_Eins3 小时前
【数论】莫比乌斯函数及其反演
c++·经验分享·算法