【STL】容器 - set和map的使用

news2024/11/28 19:05:42

目录

前言

一.键值对

1.在SGI - STL中对键值对的定义: 

2.make_pair

二.set

1.set的概念与注意事项

2.set的使用(常用接口)

<1>.构造函数

<2>.迭代器与范围for

<3>.插入和查找

<4>.删除erase

<5>.计数count

三.map

1.map的概念与注意事项

2.map的使用(常用接口)

<1>.构造函数

<2>.迭代器与范围for

<3>.查找find

<4>插入insert

<5>.删除erase

<6>.计数count

<7>.operator[]与at(重点)


前言

序列式容器与关联式容器

序列式容器:

string, vector, list, deque是序列式容器, 其底层都为线性结构, 在结构上没有其余特点, 里面存储的值是元素本身

关联式容器:

set, map, multiset, multimap是关联式容器, 其底层的结构有一定的特性与规则, 存储的是<key, value>键值对, 对于数据检索而言效率更高

注: set虽然是只存储key的容器, set也可以看为是<key, value>模型, 其中value就是key, 即为<key, key>, 在实际存储中只需要存储一个key即可

set和map, multiset和multimap

set和map的底层都是用红黑树实现的, 都自带去重

set和map的区别: set存储key, map存储key/value

multiset, multimap和set, map的区别: multiset, multimap不会去重, 其余都相同

由于其他内容过于相似, 只是去不去重的问题, 本篇博客主要介绍set和map, 对于multiset和multimap的介绍更像是基于map和set的扩展

一.键值对

一对用来表示一对对应关系, 经典的<key, value>模型, key代表键值, value表示对应的信息

在stl中, 用pair类将键值对进行了封装, 一个pair类对象就是一个键值对

1.在SGI - STL中对键值对的定义: 

template<class T1, class T2>
struct pair
{
	typedef T1 first_type;
	typedef T2 second_type;
	//默认构造
	pair()
		:first(T1())
		, second(T2())
	{}
	//有参构造
	pair(const T1& a, const T2& b)
		:first(a)
		, second(b)
	{}
	//成员变量
	T1 first; // key
	T2 second;// value
};

2.make_pair

make_pair是C++提供的一个函数模板, 用来构建pair对象

为了使用者在创建键值对对象时更加轻松, 不用再去写过长的模板参数, 而是可以通过函数模板自动类型推导(这一点在使用map时会深有体会)

//模拟实现
template<class T1, class T2>
pair<T1, T2>& make_pair(const T1& first, const T2& second)
{
	return pair<T1, T2>(first, second);
}

二.set

1.set的概念与注意事项

1.set中只可以存储值, 这个值既是key, 又是value, 不需要构建键值对

2.set支持增删查, 而并不支持修改操作, 因为key是不可以被修改的

3.set的底层使用红黑树实现

4.set的查找效率是OlogN

5.set中不可以存储相同数据, 故可以达到去重的效果

6.set可以自己控制仿函数, 可以按照自己的比较规则来实现, 默认情况下使用less仿函数, 中序遍历是一个升序序列, 相反如果使用greater仿函数, 中序遍历就是一个降序序列

2.set的使用(常用接口)

set中元素不是连续存储的, 每个元素也只是一个单一的值, 所以不支持operator[]

<1>.构造函数

//构造函数
void set_test1()
{
	//默认构造
	set<int> s1;
	//迭代器区间构造
	vector<int> v = { 7,2,4,3,5,1,9 };
	set<int> s2(v.begin(), v.end());
	//拷贝构造
	set<int> s3(s2);//or: set<int> s3 = s2;
	//C++11新增构造函数
	set<int> s = { 7,2,4,3,5,1,9 };
}

<2>.迭代器与范围for

void set_test2()
{
	//正向遍历:
	//默认使用less仿函数
	set<int> s = { 7,2,4,3,5,1,9 };
	//正向迭代器 - 底层是中序遍历
	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it++ << ' ';
	}
	cout << endl;
	//范围for
	for (auto& elem : s)
	{
		cout << elem << ' ';
	}
	cout << endl;
	//反向遍历:
	//方法一: 反向迭代器 reverse_iterator, rbegin(), rend()
	set<int>::reverse_iterator rit = s.rbegin();
	while (rit != s.rend())
	{
		cout << *rit++ << ' ';
	}
	cout << endl;
	//方法二:
	//显式使用greater仿函数, 改变值的比较规则, 需要包头文件functional
	set<int, greater<int>> s2 = { 7,2,4,3,5,1,9 };
	set<int>::iterator it2 = s2.begin();
	while (it2 != s2.end())
	{
		cout << *it2++ << ' ';
	}
	cout << endl;
	//范围for
	for (auto& elem : s2)
	{
		cout << elem << ' ';
	}
	cout << endl;
}

<3>.插入和查找

void set_test4()
{
	set<int> s = { 7,2,4 };
	s.insert(3);
	s.insert(5);
	s.insert(1);
	s.insert(9);
	for (auto& elem : s)
	{
		cout << elem << ' ';
	}
	cout << endl;
	set<int>::iterator pos = s.find(5);
	if (pos != s.end())
	{
		cout << *pos << " is find" << endl;
	}
}

在multiset中查找会找到中序遍历中的第一个符合条件的值

multiset<int> ms = { 5,6,8,12,1,5,3,12,9,4,2,5 };
for (auto& elem : ms)
{
	cout << elem << ' ';
}
cout << endl;
multiset<int>::iterator pos = ms.find(5);
while (pos != ms.end())
{
	cout << *pos << ' ';
	pos++;
}
cout << endl;

multiset的插入会插入到所有相同值的最右侧, 也就是中序遍历的最后一个 

且set的insert与multiset的insert返回值类型不同

multiset<int> ms = { 5,6,8,12,1,5,3,12,9,4,2,5 };
for (auto& elem : ms)
{
	cout << elem << ' ';
}
cout << endl;
multiset<int>::iterator pos = ms.insert(5);;
while (pos != ms.end())
{
	cout << *pos << ' ';
	pos++;
}
cout << endl;
for (auto& elem : ms)
{
	cout << elem << ' ';
}

<4>.删除erase

void set_test3()
{
	set<int> s = { 7,2,4,3,5,1,9 };
	//set容器的erase接口既可以传值删除, 又可以传iterator删除
	//有什么区别?
	s.erase(3);
	for (auto& elem : s)
	{
		cout << elem << ' ';
	}
	cout << endl;

	set<int>::iterator pos = s.find(4);
	if (pos != s.end())
	{
		s.erase(pos);
	}
	for (auto& elem : s)
	{
		cout << elem << ' ';
	}
	cout << endl;
}

set容器的erase接口既可以传值删除, 又可以传iterator删除, 有什么区别? 

如果传入的是iterator, 则必须要走一步find操作, 在底层看来erase的传值删除实际是封装了先调用find查找, 再使用find返回的iterator删除这两步

上面是删除存在的数据, 如果此时删除一个不存在的数据且不用if(pos != s.end())进行判断, 传iterator删除是有问题的, 因为如果find找不到对应数据会返回end(), 所以erase传值删除的底层也是封装了find, 如果返回end(), erase底层会有检查, 所以如果是传iterator删除, 需要手动添加if(pos != s.end())这个条件, 否则如果数据不存在就会有问题

在multiset中会存在重复元素, erase会删除所有存在的元素

void multiset_test()
{
	multiset<int> ms = { 5 ,6,8,12,1,5,3,12,9,4,2,5 };
	for (auto& elem : ms)
	{
		cout << elem << ' ';
	}
	cout << endl;
	ms.erase(5);
	for (auto& elem : ms)
	{
		cout << elem << ' ';
	}
	cout << endl;
}

<5>.计数count

set为了与multiset保持一致也支持了这个接口, 因为set中不能存放重复数据所以count只有1或这是0

但在multiset中, count可以统计这个元素存在多少个

multiset<int> ms = { 5,6,8,12,1,5,3,5,9,4,2,5 };
for (auto& elem : ms)
{
	cout << elem << ' ';
}
cout << endl;
cout << "数字5有: " << ms.count(5) << "个" << endl;

三.map

1.map的概念与注意事项

1.map中存储的是键值对 --- pair<key, value>

2.map支持增删查改, 这个改指的是value可以修改, key不可以修改

3.map的底层使用红黑树实现

4.map的查找效率是OlogN

5.map中不可以存储相同数据, 故可以达到去重的效果

6.map可以自己控制仿函数, 可以按照自己的比较规则来实现, 默认情况下使用less仿函数, 中序遍历是一个升序序列, 相反如果使用greater仿函数, 中序遍历就是一个降序序列

7.map中存储的是键值对pair<key, value>, 比较只能用key比

2.map的使用(常用接口)

<1>.构造函数

void map_test1()
{
	//默认构造
	map<string, string> m1;
	//迭代器区间构造
	pair<string, string> kv1("home", "家");
	pair<string, string> kv2("happy", "高兴");
	pair<string, string> kv3("sort", "排序");
	vector<pair<string, string>> v = { kv1,kv2,kv3 };
	map<string, string> m2(v.begin(), v.end());
	//拷贝构造
	map<string, string> m3(m2);
}

<2>.迭代器与范围for

void map_test2()
{
	pair<string, string> kv1("home", "家");
	pair<string, string> kv2("happy", "高兴");
	pair<string, string> kv3("sort", "排序");
	vector<pair<string, string>> v = { kv1,kv2,kv3 };
	map<string, string> m(v.begin(), v.end());

	//迭代器遍历
	map<string, string>::iterator it = m.begin();
	while (it != m.end())
	{
		cout << it->first << "-" << it->second << ' ';
		it++;
	}
	cout << endl;

	//范围for遍历
	for (auto& elem : m)
	{
		cout << elem.first << "-" << elem.second << ' ';
	}
	cout << endl;
}

<3>.查找find

void map_test3()
{
	pair<string, string> kv1("home", "家");
	pair<string, string> kv2("happy", "高兴");
	pair<string, string> kv3("sort", "排序");
	vector<pair<string, string>> v = { kv1,kv2,kv3 };
	map<string, string> m(v.begin(), v.end());
	//根据键值查找
	map<string, string>::iterator pos = m.find("happy");
    //如果没找到, m.find()返回m.end()
	if (pos != m.end())
	{
		cout << pos->first << '-' << pos->second << endl;
	}
}

<4>插入insert

这里insert的返回值是pair<iterator, bool>这是一个伏笔, 在后面operator[]的实现会体现他的作用 

void map_test4()
{
	map<string, string> m;
	//第一种插入方式, 先构造对象, 再插入
	pair<string, string> kv("good", "好");
	m.insert(kv);
	//第二种插入方式, 匿名对象
	m.insert(pair<string, string>("bad", "坏"));
	//第三种插入方式, 使用make_pair函数模板
	m.insert(make_pair("beautiful", "漂亮"));

	for (auto& elem : m)
	{
		cout << elem.first << "-" << elem.second << ' ';
	}
	cout << endl;
}

<5>.删除erase

void map_test5()
{
	pair<string, string> kv1("home", "家");
	pair<string, string> kv2("happy", "高兴");
	pair<string, string> kv3("sort", "排序");
	vector<pair<string, string>> v = { kv1,kv2,kv3 };
	map<string, string> m(v.begin(), v.end());
	for (auto& elem : m)
	{
		cout << elem.first << "-" << elem.second << ' ';
	}
	cout << endl;
	//传迭代器删除
	map<string, string>::iterator pos = m.find("happy");
	if (pos != m.end())
	{
		m.erase(pos);
	}
	for (auto& elem : m)
	{
		cout << elem.first << "-" << elem.second << ' ';
	}
	cout << endl;
	//传key值删除
	cout << m.erase("home") << endl;
	for (auto& elem : m)
	{
		cout << elem.first << "-" << elem.second << ' ';
	}
	cout << endl;
}

<6>.计数count

与set一样, count函数也是为multimap准备的, map中的count只是为了与multimap保持一致

<7>.operator[]与at(重点)

set是没有重载operator[]的, 在map中重载的operator[]是根据key返回对应的value的引用

map中的operator[]存在两种情况

1.传入的key值存在在map中, 则operator[]执行: 查找+修改value 

2.传入的key值不存在于map中, 则operator[]执行: 插入+修改value

如果这里insert没有返回这个pair<iterator, bool>, 那么在第2种情况, operator[]就要遍历两次, 第一次遍历, 查找且没有找到; 则需第二次遍历, 执行插入

解释insert的返回值pair<iterator, bool>:

由于map不会插入重复的键值, 插入时如果该键值已经存在, 则直接返回存在的这个键值对的迭代器, 如果插入的键值不存在, 则先插入, 后返回插入的这个键值对的迭代器, 但是需要插入的是<key,value>, 这时value就通过一个value的匿名对象去调用他的默认构造, 来构造出一个键值对

注: 对于内置类型, 例如int而言, int()这难道也要去调用int的默认构造吗? C++为了兼容自定义类型, 规定如int()这样的值就默认为0, float()就是0.0

这样operator[]的实现就可以复用一个insert就可以了

void map_test6()
{
	string str_array[] = { "老师", "学生", "校长","学生" ,"学生" ,"学生" ,"学生" ,"学生" ,"学生","老师","老师" };
	//统计老师,学生,校长各自的人数
	map<string, int> m;
	for (int i = 0; i < sizeof(str_array) / sizeof(str_array[0]); ++i)
	{
		m[str_array[i]]++;
	}
	for (auto& elem : m)
	{
		cout << elem.first << "-" << elem.second << ' ';
	}
	cout << endl;
}

对operator[]实现的解读

(this->insert(make_pair(key, value))) --- 拿到insert返回值 --- pair<iterator, bool>对象

( (this->insert(make_pair(key, value))) ).first --- 根据拿到的返回值对象, 去访问第一个成员iterator, 这个iterator是新插入或者查找到的key的键值对

(( (this->insert(make_pair(key, value))) ).first ).second --- 对拿到的iterator解引用, 在去访问iterator的第二个成员value, 以引用的形式返回

对于at而言, 只有查找+修改value的功能, 如果找不到就会抛出异常

void map_test7()
{
	string str_array[] = { "老师", "学生", "校长","学生" ,"学生" ,"学生" ,"学生" ,"学生" ,"学生","老师","老师" };
	//统计老师,学生,校长各自的人数
	map<string, int> m;
	for (int i = 0; i < sizeof(str_array) / sizeof(str_array[0]); ++i)
	{
		m[str_array[i]]++;
	}
	for (auto& elem : m)
	{
		cout << elem.first << "-" << elem.second << ' ';
	}
	cout << endl;
	m.at("校长") += 100;
	for (auto& elem : m)
	{
		cout << elem.first << "-" << elem.second << ' ';
	}
	cout << endl;
	m.at("主任");
}

 

 

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

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

相关文章

洛谷千题详解 | P1012 [NOIP1998 提高组] 拼数【C++、Java语言】

博主主页&#xff1a;Yu仙笙 专栏地址&#xff1a;洛谷千题详解 目录 题目描述 输入格式 输出格式 输入输出样例 解析&#xff1a; C源码&#xff1a; C源码2&#xff1a; C源码3&#xff1a; Java源码&#xff1a; ---------------------------------------------------------…

element-ui upload图片上传组件使用

图片上传前端收集 数据 再调用接口发送到后端 组件标签内的参数&#xff1a; 参数说明类型可选值默认值action必选参数&#xff0c;上传的地址string——headers设置上传的请求头部object——multiple是否支持多选文件boolean——data上传时附带的额外参数object——name上传…

【数据结构】链表OJ第一篇 —— 移除链表元素 反转链表 合并两个有序链表

文章目录0. 前言1. 移除链表元素2. 反转链表3. 合并两个有序链表4. 结语0. 前言 上篇博客中&#xff0c;我们学习了实现了单链表。但是仅仅实现并不算掌握&#xff0c;所以我们需要做些题目来练习巩固。而从今天开始的几期&#xff0c;anduin 都会为大家带来链表OJ题&#xff…

在Linux环境下VScode中配置ROS、PCL和OpenCV开发环境记录

一.安装必要的插件 打开VScode&#xff0c;在开展中安装CMake、CMake Tools&#xff0c;ROS和catkin-tools插件&#xff0c;截图如下&#xff0c;安装后重新打开VScode插件生效。 二.创建ROS工作空间 在选择的路径下&#xff0c;打开终端创建工作空间&#xff0c;具体命令如下…

【概率论笔记】正态分布专题

文章目录一维正态分布多维正态分布n维正态分布二维正态分布一维正态分布 设X~N(μ,σ2)X\text{\large\textasciitilde}N(\mu,\sigma^2)X~N(μ,σ2)&#xff0c;则XXX的概率密度为f(x)12πσe−(x−μ)22σ2f(x)\frac{1}{\sqrt{2\pi}\sigma}e^{-\frac{(x-\mu)^2}{2\sigma^2}}f(…

WXML模板语法

文章目录1、数据绑定1.1 数据绑定的基本原则1.2在data中定义页面的数据1.3 Mustache语法的格式1.4 Mustache语法的应用场景1.5 算数运算2、事件绑定2.1 小程序常用的事件2.2事件对象的属性列表2.3 target和currentTarget的区别2.4 <font colorred>bindtap的语法格式2.5 在…

狗厂员工来面试本想难为一下,结果被虐得连console.log也不敢写了

这次说到的面试题是关于node服务端内存溢出的问题&#xff0c;狗厂员工来面试本想难为一下&#xff0c;现在我连console.log也不敢写了 关于这道node内存溢出的问题&#xff0c;大哥从以下几个方面讲的&#xff0c;讲完我觉得自己得到了升华&#xff0c;现在搞得连代码也快不敢…

2.24 OrCAD Cadence16.6怎么更改原理图中做好的库文件?

笔者电子信息专业硕士毕业&#xff0c;获得过多次电子设计大赛、大学生智能车、数学建模国奖&#xff0c;现就职于南京某半导体芯片公司&#xff0c;从事硬件研发&#xff0c;电路设计研究。对于学电子的小伙伴&#xff0c;深知入门的不易&#xff0c;特开次博客交流分享经验&a…

FPGA代码设计规范一些探讨

代码设计规范的重要性 经过一段的工作积累已经慢慢进入了提高和进阶的阶段&#xff0c;在这篇博客里多聊一聊在现实工作中的话题&#xff0c;比如代码规范以及如何尽快接手前人代码&#xff0c;快速定位项目问题。 显然每个FPGA工程师的设计理念和代码风格很多情况下有一些差别…

python 基于PHP+MySQL的学生成绩管理系统

学生成绩管理是每一个学校都会面临的一个问题,传统的管理模式已经明显到和时代不同步。通过我对当前学校成绩管理的需求和自己的实习经验整理出了一个能够满足大多数学校的学生成绩管理系统。本系统分为管理人员,教师和学生三种用户,每种用户各负责其一部分功能然后通过他们的整…

Web前端开发基础教程二

注释和特殊字符&#xff1a; 如果需要在html文档添加一些便于阅读和理解但又不需要显示在页面中的注释文字&#xff0c;就需要使用注释标签。 html中的注释以“<!--”开头&#xff0c;以“-->”结束或者快捷键&#xff1a;Ctrl/。 举例&#xff1a; <!-- 我想放假 …

【实战】Mysql 千万级数据表结构变更 、含脚本

一、实测结果 业务无感知&#xff0c;无死锁平滑 线上800万数据以下 直接使用 alter 新增字段 300ms左右 2000万数据&#xff0c;强制使用主键索引&#xff0c;每次查询50万数据 并插入新表 &#xff0c;耗时 20s &#xff0c;cpu 占45% 二、整体步骤 创建新表 biz_table_ne…

Vue-脚手架的创建

本篇vue3的脚手架主要是使用vue-cli进行创建&#xff0c;有网的情况下才能创建成功 文章目录一、下载node.js二、全局安装vue/cli三、使用vue-cli创建项目3.1 使用vscode打开终端3.2 创建项目3.3 创建成功四、注意事项一、下载node.js 1、打开node的官网 node官网 2、点击下方图…

Oxygen XML Editor 25.0.X Crack

XML 编辑器 Oxygen XML Editor 是完整的 XML 编辑解决方案&#xff0c;适用于 XML 开发人员和内容作者。它提供了必备的 XML 编辑工具&#xff0c;涵盖了大多数 XML 标准和技术。Oxygen XML Editor 包括 Oxygen XML Developer 和 Author 的所有功能。 特点与技术 单一来源出版 …

【树莓派不吃灰】使用frp内网穿透,实现远程访问树莓派

目录1. 前言2. frp内网穿透2.1 概述2.2 实现原理3. 开源frp项目4. 公网服务器选型5. 下载frp软件5.1 公网服务器下载frp服务器5.1.1 github选择适合服务端的版本5.1.2 公网服务器进行下载解压5.2 树莓派下载frp客户端5.2.1 github选择适合客户端的版本5.2.2 树莓派进行下载解压…

流形上的预积分(上)

预积分和流形 论文&#xff1a;IMU Preintegration on Manifold for Effificient Visual-Inertial Maximum-a-Posteriori Estimation 引言 Recent results in monocular visual-inertial navigation (VIN) have shown that optimization-based approaches outperform filteri…

免费体验CSDN云IDE使用指南

云IDE产品介绍 云IDE使用教程 免费使用地址&#xff1a;点击【云IDE】&#xff0c;即可开始创建工作空间啦~ 官方活动入口 文章目录1.免费体验CSDN云IDE使用指南1.1云IDE优点2.自己的代码在云IDE上跑起来看看如何操作2.1 克隆开源仓库2.2 创建一个空工作空间2.3 使用默认模板代…

Thread类中run和start的区别

一.认识 Thread类 中的 start() 和 run() 首先来看一张jdk-api中对于start 和 run 的描述. 二.方法的区别 下面我们简单看一下代码: // 继承Thread类 重写run() class MyThread extends Thread {Overridepublic void run() {while (true) {System.out.println("thread&q…

如何通过点击商品的信息(图片或者文字)跳转到更加详细的商品信息介绍(前后端分离之Vue实现)

以下只是做简单的演示、大致实现的效果。页面效果需要进一步优化。目的是提供思路 视频效果&#xff1a; 首页商品跳转1、需求 1、首页的的商品来自接口数据2、在首页点击某一个商品跳转到更加详细的商品介绍页面&#xff08;可以购买、加入购物车、查看商品评价信息等&#x…

频频出现的DevOps到底是什么呢?浅浅了解下什么是DevOps吧

文章目录 &#x1f4d1;前言 &#x1f9e9;DevOps的概念和起源 &#x1f4f0;DevOps的概念 &#x1f4f0;DevOps的起源与发展 &#x1f4f0;总结 &#x1f9e9;DevOps的发展 &#x1f4f0;发展过程速览 &#x1f4f0;发展现状 &#x1f4f0;未来发展 &#x1f4c4;以…