C++ STL map和set的使用

序列式容器和关联式容器 

想必大家已经接触过一些容器如:list,vector,deque,array,forward_list,string等,这些容器统称为系列容器。因为逻辑结构为线性的,两个位置的存储的值一般是没有固定关系的,比如交换一下并不会破坏它的结构特点。序列式容器一般是通过来顺序保存和访问的

关联式容器也是用来存储数据的,与序列式容器不同,关联式容器逻辑结构通常是非线性的,两个位置之间通常有紧密的联系,如果进行位置交换,那么就会破坏容器的结构。关联式容器中的元素一般是按关键字来保存和访问的。关联式容器有map/set系列和unordered_map/unordered_set系列。

本博客主要讲map和set系列容器的使用

map和set底层都是由红黑树来实现的,本质是一颗平衡二叉树,因此效率是极高的。

其中set是Key结构,map是Key-Value结构

pair类的介绍

在正式了解map、set前,我们要知道pair这个类型。

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)
    {
    return ( pair<T1,T2>(x,y) );
}

我们可以看出,它就是一个结构体,存的两个数据。因为我们写代码的过程有很多数据有对应关系,因此pair的使用非常频繁。它可以免去我们自己构造一个相同类的麻烦。

make_pair(1, 2);
pair<int, int>(1, 2);

make_pair和下面的使用是一样的,但是make_pair可以推到出类型。下面的需要手动输入类型。

set的使用 

首先set是一个存储K结构,存储的数据不能重复的容器。

下面是相关的函数

begin返回正向迭代器的开头
end返回正向迭代器的结尾
rbegin返回反向迭代器的开头
rend返回反向迭代器的结尾
cbegin返回正向常迭代器的开头
cend返回正向常迭代器的结尾
crbegin返回反向常迭代器的开头
crend返回反向常迭代器的结尾
empty判断容器是否为空
size返回容器的数据个数
max_size返回容器存储的最大值,一般系统会检测计算机内存做出合理的返回值
insert插入一个值
erase删除一个值
swap交换两个set的数据
clear清除一个set容器的数据
emplace插入一个数据,和insert的效果等效
key_comp返回一个比较对象
value_comp返回一个比较对象
find对数据进行查找
count查找某个值的数量
lower_bund返回一个大于等于某个值的迭代器
upper_bound返回一个大于某个值的迭代器
equal_range返回大于等于某个值和大于某个值两个的迭代器

    首先在了解上面的函数前,我们要了解set的构造函数、

构造函数

这里我只说一些常用的构造方式:

默认构造

	set<int>con;

这种方式构造出来的set容器为空。可以进行后续的插入

拷贝构造

set<int>con;
set<int>con_(con);
set<int>con__ = con;

用其他的set值来构造一个新的set

初始化生定向列表构造

set<int>con{ 1,2,3,4,5,6,34,2 };
set<int>con_({ 1,3,4,5,6 });
set<int>con__ = { 1234,325,2 };

这种构造输入十分方便

迭代器构造

vector<int>vec{ 1,2,34,5,2 };
set<int>(vec.begin(), vec.end());
int arr[] = { 1,2,3,4 };
set<int>(arr, arr + 3);

迭代器构造能够夸容器,进行数据的流通。

同时我们在构造的时候可以传函数指针或者仿函数来改变set的存储形式

bool compare(int x, int y) {
	return x > y;
}

struct compare_ {
	bool operator()(const int&x,const int& y)const {
		return x > y;
	}
};
int main(){
	bool(*pare)(int, int) = compare;
	compare_ pare_;
	set<int, bool(*)(int, int)>con({ 2432423,21,23,4,5,345,34,5 },pare);
	set<int,compare_>con_({ 2432423,21,23,4,5,345,34,5 });
	set<int, greater<int>>con__({ 2432423,21,23,4,5,345,34,5 });
	for (auto& num : con)cout << num<<" ";
	cout << endl;
	for (auto& num : con_)cout << num<<" ";
	cout << endl;
	for (auto& num : con__)cout << num << " ";
	return 0;
}
//打印结果
//2432423 345 34 23 21 5 4
//2432423 345 34 23 21 5 4
//2432423 345 34 23 21 5 4

通过比较的改变将升序变为降序。

这几个构造基本上可以满足所有的构造需求了。

下面的构造是优化效率

右值引用构造

这个构造是将右值构造直接夺取其值来进行构造,减少拷贝,加快了构造的速度。

一般大家传常量值机会出发它的构造

	set<int>contain(set<int>{1, 3});

成员函数

下面开始看成员函数的使用

迭代器相关的函数

如果还不知道迭代器是什么或者迭代器还不会使用的,可以看看这篇博客:迭代器的使用

看完之后对于迭代器的几个函数应该是懂的,这里就不做过多的讲解了。

size empty

这里size和empty函数也不做讲解,就是返回容器的数据量和判空。

max_size:

这个函数就是在我们插入比较多的数据时,我们要判断一下这些数据能否一下存的完。

int arr[10000] = { 123,21,321,3,12,3,21,4,3243,543,6,34,43,4124/*...........*/ };
int len = sizeof(arr) / sizeof(int);
set<int>con;
if (len < con.max_size()) {
	for (int x = 0; x < len; ++x)con.insert(arr[x]);
}
else {
	cout << "存不下" << endl;
	assert(false);
}

insert

insert虽然大家知道是干什么的,但是它的重载形式是有很多的,还有返回值是怎么返回的都需要知道

//1
pair<iterator,bool> insert (const value_type& val);
//2
pair<iterator,bool> insert (value_type&& val);
//3
iterator insert (const_iterator position, const value_type& val);
//4
iterator insert (const_iterator position, value_type&& val);
//5
template <class InputIterator>
void insert (InputIterator first, InputIterator last);	
//6
void insert (initializer_list<value_type> il);

第一个和第二个的输入我就不多说了,这里说一下它的返回值,上面我讲解了pair的使用,那么这里就返回了两个值,first是成功插入后的这个值的迭代器或者失败后已经有的值的迭代器,second是判断这个值是否插入成功。

例如:

set<int>con{ 1,2};
pair<set<int>::iterator, bool>p = con.insert(1);
if (p.second) {
	cout << "插入成功" << endl;
}
else cout << "插入失败";
//输出插入失败

第三个和第四个是在对应迭代器的后面插入一个值,返回这个插入的迭代器,如果set里面已经有了这个值,那么就会返回这个值的迭代器。

int arr[10000] = { 123,21,321,3,12,3,21,4,3243,543,6,34,43,4124/*...........*/ };
int len = sizeof(arr) / sizeof(int);
set<int>con{ 1,2};
set<int>::iterator it = con.insert(con.begin(), 2);
for (auto num:con)cout << num;
// 1 2

第四个就是插入一个迭代器区间的值,相当于进行了n次单个迭代器的插入。

第五个就是插入一段初始化设定项列表里面的数据。

第四个和第五个因为是多插入,不好返回各个插入情况的布尔值。所以是void。

 erase

//(1)	
iterator  erase (const_iterator position);
//(2)	
size_type erase (const value_type& val);
//(3)	
iterator  erase (const_iterator first, const_iterator last);

对于用值来查找

就返回成功删除值的个数,这里因为值不能重复,因此只会返回0或1

对于用迭代器或则迭代器区间来删除,会返回这个值或者迭代器最后一次删除的值的下一个迭代器。区间迭代器删除时迭代器是左开右闭的,考虑删除begin(),end()区间就理解什么了。

那么就有这种写法:可以删除里面的偶数

set<int>con{ 1,2,3,4,5,6 };
for (auto it = con.begin(); it != con.end();) {
	if ((*it) % 2 == 0)it = con.erase(it);
	else ++it;
}
for (auto& num : con)cout << num << " ";
//1 3 5

我们不能一直it++,因为erase后it会直接跳到下一个值上。

swap

交换两个map的值

是否是以下面的代码交换的呢?

template<class K>
void swap(set<K>& x, set<K>& y) {
	set<K>temp = x;
	x = y;
	y = temp;
}

觉得是就错了,这种会有大量的拷贝,效率急转直下。

只要把里面的_root的指针交换一下就行了,数据就交换了。

clear

这个不多说,就是将数据清完

emplace

这个用到了右值引用移动构造,在插入右值的时候构造速度会快一些。效果和insert一样。

key_comp

会返回一个key_compare类型,它的效果类似于operator<重载,用的不多,看下面代码演示就行了

// set::key_comp
#include <iostream>
#include <set>

int main ()
{
  std::set<int> myset;
  int highest;

  std::set<int>::key_compare mycomp = myset.key_comp();

  for (int i=0; i<=5; i++) myset.insert(i);

  std::cout << "myset contains:";

  highest=*myset.rbegin();
  std::set<int>::iterator it=myset.begin();
  do {
    std::cout << ' ' << *it;
  } while ( mycomp(*(++it),highest) );

  std::cout << '\n';

  return 0;
}
//myset contains: 0 1 2 3 4

value_comp

从k的比较换成了value的比较,这里的value和key是相同的,因此这两个函数相同

find

查找一个值并返回它的迭代器

如果找不到就会返回end()迭代器。

count

查找某个值的个数,因为容器不允许重复数据,因此返回值只有0或者1。

lower_bound

返回第一个大于等于某个值的迭代器

因为set的去重性,最后要么返回等于这个值的迭代器,要么返回第一个大于这个值的迭代器。

upper_bound

返回第一个大于某个值的迭代器

这个比lower_bound稍微窄一点,当没有要查找的那个值时lower_bound的效果等于upper_bound效果。

例如要删除[10,60]的数据:

set<int>con{ 0,10,20,30,40,50,60,70};
con.erase(con.lower_bound(10), con.upper_bound(60));
for (auto num : con)cout << num << " ";
//0 70

equal_range

作用是找到等于某个值的区间,返回装有两个迭代器的pair类,范围是做开右闭。

那么就和上面的lower_bound和upper_bound差不多了,等价返回make_pair(lower_bound,upper_bound)。但是估计这个函数的效率更高,因为两次_bound都是从开始往后找,但是equal_range可以在lower_bound的基础上找upper_bound的迭代器。

multiset的使用

成员函数

multiset和set的差异就multiset支持冗余值。其它是一模一样的。那么我就只说函数里面不同的地方。

erase

删除的时候,会把所有等于它的值删除,并不是只删一个。

find

他是寻找中序的第一个目标值

count

因为有冗余值,因此范围可以是[0,max_size]

其它就是一样的,这里就不重复说了

map的使用

map就是K-V的结构, 通过K来寻找,同时可以找到对应的映射值V。其中K是不能修改的,不然破坏了结构,V可以修改。

增删查改和set的不同

这里我不具体一个一个讲,只讲和set不同的点。不同点只有一个,插入删除都是直接作用pair整体,插入用pair,删除就删除K-V整体。

value_comp和key_comp

在set里面两个函数是一样的,这里稍微不一样,一个是比较value,一个是比较key。就这里不同

operator[]

这个是非常重要的修改接口,不仅仅支持修改,还支持插入数据和查找数据,所以他是一个多功能复合接口

我们来看它底层是怎么实现的,首先我们复习一下insert的插入方法

insert插入一个pair<key, T>对象
1、如果key已经在map中,插入失败,则返回一个pair<iterator,bool>对象,返回pair对象
first是key所在结点的迭代器,second是false
2、如果key不在在map中,插入成功,则返回一个pair<iterator,bool>对象,返回pair对象
first是新插入key所在结点的迭代器,second是true
也就是说无论插入成功还是失败,返回pair<iterator,bool>对象的first都会指向key所在的迭
代器
那么也就意味着insert插入失败时充当了查找的功能,正是因为这一点,insert可以用来实现
operator[]

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

也可以用一行代码搞定

mapped_type& operator[] (const key_type& k){
    return (*con.insert(k).first).second;
}

它有一点要注意,就是访问过的一定会插入到列表,做查找的话要考虑是否要让它插入进来,否则就用count查找,或者find查找。

multimap和map的差异

multimap和map的使用基本完全类似,主要区别点在于multimap支持关键值key冗余,那么
insert/find/count/erase都围绕着支持关键值key冗余有所差异,这里跟set和multiset完全一样,比如find时,有多个key,返回中序第一个。其次就是multimap不支持[],因为支持key冗余,[]就只能支持插入了,不能支持修改。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

人工智能及深度学习的一些题目(三)

1、【填空题】 使用RNNCTC模型进行语音识别&#xff0c;在产生预测输出时&#xff0c;对于输入的音频特征序列通过网络预测产生对应的字母序列&#xff0c;可以使用&#xff08; beamsearch &#xff09;算法进行最优路径搜索。 2、【填空题】 逻辑回归模型属于有监督学习中的&…

《C++11》右值引用深度解析:性能优化的秘密武器

C11引入了一个新的概念——右值引用&#xff0c;这是一个相当深奥且重要的概念。为了理解右值引用&#xff0c;我们需要先理解左值和右值的概念&#xff0c;然后再理解左值引用和右值引用。本文将详细解析这些概念&#xff0c;并通过实例进行说明&#xff0c;以揭示右值引用如何…

cp命令详解

&#x1f3dd;️专栏&#xff1a;计算机操作系统 &#x1f305;主页&#xff1a;猫咪-9527主页 “欲穷千里目&#xff0c;更上一层楼。会当凌绝顶&#xff0c;一览众山小。” 目录 1. 基本功能 2. 命令语法 3. 常用选项 4. 常见用法示例 4.1 复制单个文件 4.2 递归复制目录…

Git的学习和常见问题

文章目录 1.初始化配置2.新建仓库3.添加和提交文件4.git reset 回退版本5.git diff 查看差异6.git rm 删除文件7.文件 .gitigonre8.克隆远程仓库9.将已有的本地仓库关联到远程仓库10.分支的基本操作11.解决合并冲突配置问题 最近基于GeekHour的视频学习Git&#xff0c;记录了一…

《Mcal》--MCU模块

一、MCU模块的主要功能 控制系统时钟的产生。控制系统通用模块&#xff0c;该模块会涉及到Adc、Ftm等外设的配置。控制外设时钟。控制MCU运行的模式。初始化定义RAM Section。 比较重要的是时钟的配置。 二、系统时钟的配置 1、芯片时钟树 要想弄明白时钟配置&#xff0c;需…

【每日学点鸿蒙知识】查看触摸热区范围、直接赋值到剪贴板、组件截图、横竖屏切换、防截图等

1、如何查看触摸热区范围&#xff1f; 前只能通过自定义的方式获取responseRegion。参考文档&#xff1a;触摸热区设置 Entry Component struct TouchTargetExample {State text: string State x:number 0State y:number 0State reg_width:string 50%State reg_height:st…

ThinkPHP 8高效构建Web应用-获取请求对象

【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《2025新书 ThinkPHP 8高效构建Web应用 编程与应用开发丛书 夏磊 清华大学出版社教材书籍 9787302678236 ThinkPHP 8高效构建Web应用》【摘要 书评 试读】- 京东图书 使用VS Code开发ThinkPHP项目-CSDN博客 编程与应用开…

记一次k8s下容器启动失败,容器无日志问题排查

问题 背景 本地开发时&#xff0c;某应用增加logback-spring.xml配置文件&#xff0c;加入必要的依赖&#xff1a; <dependency><groupId>net.logstash.logback</groupId><artifactId>logstash-logback-encoder</artifactId><version>8…

STM32烧写失败之Contents mismatch at: 0800005CH (Flash=FFH Required=29H) !

一&#xff09;问题&#xff1a;用ULINK2给STM32F103C8T6下载程序&#xff0c;下载方式设置如下&#xff1a; 出现下面两个问题&#xff1a; 1&#xff09;下载问题界面如下&#xff1a; 这个错误的信息大概可以理解为&#xff0c;在0x08000063地址上读取到flash存储为FF&am…

vscode通过ssh连接服务器实现免密登录

一、通过ssh连接服务器 1、打开vscode&#xff0c;进入拓展&#xff08;CtrlShiftX&#xff09;&#xff0c;下载拓展Remote - SSH。 2、点击远程资源管理器选项卡&#xff0c;选择远程&#xff08;隧道/SSH&#xff09;类别。 3、点击SSH配置。 4、在中间上部分弹出的配置文件…

在Nvidia Jetson ADX Orin中使用TensorRT-LLM运行llama3-8b

目录 背景&#xff1a;步骤 1.获取模型权重第 2 步&#xff1a;准备第 3 步&#xff1a;构建 TensorRT-LLM 引擎 背景&#xff1a; 大型语言模型 &#xff08;LLM&#xff09; 推理的关键瓶颈在于 GPU 内存资源短缺。因此&#xff0c;各种加速框架主要强调减少峰值 GPU 内存使…

Unity Shader学习日记 part4 Shader 基础结构

其实在这一篇之前&#xff0c;应该还有一个关于坐标空间转换的内容&#xff0c;但是内容囤积的有些多&#xff0c;就先把Shader的基础结构先记录一下。 笔记主要记录在代码中&#xff0c;所以知识点主要是图和代码的展示。 Unity Shader分类 在Unity中&#xff0c;Shader的种…

特征点检测与匹配——MATLAB R2022b

特征点检测与匹配在计算机视觉中的作用至关重要,它为图像处理、物体识别、增强现实等领域提供了坚实的基础。 目录 Harris角点检测 SIFT(尺度不变特征变换) SURF(加速稳健特征) ORB(Oriented FAST and Rotated BRIEF) 总结 特征点检测与匹配是计算机视觉中的一项基…

Airflow:HttpSensor实现API驱动数据流程

数据管道工作流通常依赖于api来访问、获取和处理来自外部系统的数据。为了处理这些场景&#xff0c;Apache Airflow提供了HttpSensor&#xff0c;这是一个内置的Sensor&#xff0c;用于监视HTTP请求的状态&#xff0c;并在满足指定条件时触发后续任务。在这篇博文中&#xff0c…

图数据库 | 17、高可用分布式设计(上)

我们在前面的文章中&#xff0c;探索了多种可能的系统扩展方式&#xff0c;以及每种扩展方式的优劣。 本篇文章将通过具体的架构设计方案来对每一种方案的设计、投入产出比、各项指标与功能&#xff0c;以及孰优孰劣等进行评价。 在设计高性能、高可用图数据库的时候&#xf…

JAVA学习记录1

文章为个人学习记录&#xff0c;仅供参考&#xff0c;如有错误请指出。 什么是JAVA&#xff1f; JAVA是一种高级的编程语言&#xff0c;可以用于开发大部分场景的软件&#xff0c;但主要用于服务器的开发。 什么是JDK&#xff1f; 类似于python使用PyCharm来编写代码&#…

css中的部分文字特性

文章目录 一、writing-mode二、word-break三、word-spacing;四、white-space五、省略 总结归纳常见文字特性&#xff0c;后续补充 一、writing-mode 默认horizontal-tbwriting-mode: vertical-lr; 从第一排开始竖着排&#xff0c;到底部再换第二排&#xff0c;文字与文字之间从…

Android wifi常见问题及分析

参考 Android Network/WiFi 那些事儿 前言 本文将讨论几个有意思的网络问题&#xff0c;同时介绍 Android 上常见WiFi 问题的分析思路。 网络基础Q & A 一. 网络分层缘由 分层想必大家很熟悉&#xff0c;是否想过为何需要这样分层&#xff1f; 网上大多都是介绍每一层…

【C语言】_指针与数组

目录 1. 数组名的含义 1.1 数组名与数组首元素的地址的联系 1.3 数组名与首元素地址相异的情况 2. 使用指针访问数组 3. 一维数组传参的本质 3.1 代码示例1&#xff1a;函数体内计算sz&#xff08;sz不作实参传递&#xff09; 3.2 代码示例2&#xff1a;sz作为实参传递 3…

IDEA 字符串拼接符号“+”位于下一行的前面,而不是当前行的末尾

效果图 IDEA 默认效果是“历史效果”&#xff0c;经过修改后为“预期效果” 设置方式 在设置中找到Editor > Code Style > Java > Wrapping and Braces > Binary expressions > 勾选 Operation sign on next line 即可实现。具体设置如图。