详解c++---map的介绍

news2024/11/27 3:53:54

目录标题

  • map容器的介绍
  • pair的介绍
  • map的构造函数
  • insert函数
  • make_pair函数
  • find函数
  • map的[ ]重载
  • multimap

map容器的介绍

通过之前的学习想必大家对set容器的理解应该非常的深刻了,我们知道他的底层是一个k结构的搜索二叉树,可以对数据进行去重并排序,那么这里的map的底层就是一个kv结构的搜索二叉树,他在对数据进行去重和排序的同时还可以通过这个数据存储另外一个数据的内容,那么这另外一个数据就是这里的V,在前面的学习中我们将k和v分开定义,类型k创建一个变量,类型v创建一个变量,但是库中map的底层并不是通过来个变量来实现的kv结构而是通过一个pair来实现的。

pair的介绍

pair是struct定义的类模板,比如说下面的代码:

在这里插入图片描述

pair结构体里面含有两个变量分别为first和second,其中first表示的就是key,second表示的就是value,其次pair有三种不同形式的构造函数,比如说下面的图片:
在这里插入图片描述
第一种就是创建一个空pair,第二个就是pair的拷贝构造函数,第三个就是传递两个值来构造pair,其中第一个参数表示的就是pair种的first元素,第二个参数表示的就是pair种的second元素,比如说下面的代码:

void func7()
{
	pair<int, int> tmp1;
	pair<int, int> tmp2(10,20);
	pair<int, string> tmp3(20, "hello");
	pair<int, string> tmp4(tmp3);
	cout << tmp2.first << ":" << tmp2.second << endl;
	cout << tmp3.first << ":" << tmp3.second << endl;
	cout << tmp4.first << ":" << tmp4.second << endl;

}

这段代码的运行结果如下:
在这里插入图片描述
之前的二叉搜索树我们是分开定义的k和v,而现在我们把两个值通过pair组合到了一起来定义这样做的原因就是可以更加方便的管理容器的结构,比如说map中即存在k又存在v,而迭代器又是一个像指针一样的东西,对迭代器解引用可以拿到迭代器里面的数据,那对map的迭代器进行解引用得到的究竟是k还是v呢?对吧,c++是不支持一下子返回两个值的,所以就有了pair。

map的构造函数

首先来看看map的构造函数有哪些形式:
在这里插入图片描述
第一种就是构造一个空的map比如说下面的代码:

void func()
{
	map<int, string> tmp1;
	map<int, vector<int>> tmp2;
	map<char, list<int>> tmp3;
}

那么这里就创建了三个不同kv结构的map,tmp1是根据int的值来查找对应的string数据,tmp2是根据int的值来查找对应的vector<int>的数据,tmp3则是根绝char的值来查找对应的list<int>的数据,那么这就是构造函数的第一种用法,第二种就是通过迭代器区间来进行初始化,将迭代器区间中的内容初始化到新创建的map容器里面,比如说下面的代码:

void func1()
{
	map<int, string> m;
	m.insert(pair<int, string>(10, "abcd"));
	m.insert(pair<int, string>(9, "efg"));
	m.insert(pair<int, string>(8, "hijkl"));
	map<int, string> m1(m.begin(), m.end());
}

那么这种就是使用迭代器区间来进行初始化,通过调试就可以看到m1里面的内容和m是一摸一样的:
在这里插入图片描述
那么这就是迭代器区间的构造函数,当然这里的迭代器也可以是其他容器的迭代器,比如说下面的代码:

void func1()
{
	vector<pair<int, string>> v;
	v.push_back(pair<int, string>(1, "a"));
	v.push_back(pair<int, string>(2, "b"));
	v.push_back(pair<int, string>(3, "c"));
	v.push_back(pair<int, string>(4, "d"));
	v.push_back(pair<int, string>(5, "e"));
	v.push_back(pair<int, string>(6, "f"));
	vector<pair<int, string>>::iterator begin = v.begin() + 1;
	vector<pair<int, string>>::iterator end = v.end() -1;
	map<int, string> m(begin, end);
}

这里就将v中的部分数据拷贝到了容器里面,通过调试可以看到容器中的数据如下:
在这里插入图片描述

第三种就是拷贝构造,那么这里就不多解释,大家直接看看下面的代码:

void func1()
{
	vector<pair<int, string>> v;
	v.push_back(pair<int, string>(1, "a"));
	v.push_back(pair<int, string>(2, "b"));
	v.push_back(pair<int, string>(3, "c"));
	v.push_back(pair<int, string>(4, "d"));
	v.push_back(pair<int, string>(5, "e"));
	v.push_back(pair<int, string>(6, "f"));
	vector<pair<int, string>>::iterator begin = v.begin() + 1;
	vector<pair<int, string>>::iterator end = v.end() -1;
	map<int, string> m(begin, end);
	map<int, string> m1(m);
}

通过调试可以看到这里容器的内容如下:
在这里插入图片描述
可以看到m1的数据内容和m的数据内容是一摸一样的,那么这就是第三种形式的构造函数。看到这里想必大家已经知道了map的构造函数的用法,那么接下来我们就来看看如何将数据插入的map容器里面。

insert函数

insert函数可以往map容器里面插入数据,我们来看看insert函数的介绍:
在这里插入图片描述
map的insert函数和set的insert函数几乎是一摸一样的,由于map也是基于搜索二叉树的结构,所以这里是不支持往map的尾部插入数据的所以map没有push_back函数,insert函数的第一种形式就是往容器里面插入value_type类型的数据,也就是往容器里面插入pair类型的数据,比如说下面的代码:

void func2()
{
	map<int, string> m;
	pair<int, string> p(1,"a");
	m.insert(p);
	m.insert(pair<int, string>(2, "b"));
}

第一种形式的返回值是pair类型,并且他的first是一个迭代器,second是一个bool类型,如果数据插入成功的话迭代器指向的是新开辟的空间并且sceond的值为true,如果原容器中存在相同的数据(这里的相同数据比较的是k不包括v)导致没有插入成功的话迭代器指向的是容器中已有的数据并且second的值为false,我们可以通过下面的代码来进行验证:

void func3()
{
	map<int, string> m;
	m.insert(pair<int, string>(1, "a"));
	m.insert(pair<int, string>(2, "b"));
	m.insert(pair<int, string>(3, "c"));
	m.insert(pair<int, string>(4, "d"));
	map<int, string> m1(m);
	auto tmp = m1.insert(pair<int, string>(1, "b"));
	if (tmp.second == true)
	{
		cout << "插入数据成功" << endl;
		cout << "新插入的数据为:" << (*(tmp.first)).first << " : " << tmp.first->second << endl;
	}
	else
	{
		cout << "插入数据失败" << endl;
		cout <<"已有的数据为" <<tmp.first->first<<" : " << tmp.first->second << endl;
	}
	tmp = m1.insert(pair<int, string>(5, "e"));
	if (tmp.second == true)
	{
		cout << "插入数据成功" << endl;
		cout << "新插入的数据为:" << tmp.first->first << " : " << tmp.first->second << endl;
	}
	else
	{
		cout << "插入数据失败" << endl;
		cout << "已有的数据为" << tmp.first->first << " : " << tmp.first->second << endl;
	}
}

这段代码的运行结果如下:
在这里插入图片描述
这里大家要注意的一点就是pair结构体是不支持流读取的但是他的所有数据都是公共的,所以不能直接对map的迭代器解引用并用cout打印里面的内容,而是通过first和second来读取pair里面的数据 ,那么这就是insert函数的第一种形式,第二种形式就是通过迭代器的指向,向指定位置插入数据,之前我们说过搜索二叉树每个节点都有很紧密的联系,往指定位置插入数据很可能会干扰这种联系使其不再是搜索二叉树,所以这种形式我们不建议使用,第三种形式就是迭代器区间插入,将迭代器区间里面的内容插入到容器里面,比如说下面的代码:

void func4()
{
	map<int, string> m;
	vector<pair<int, string>> v;
	v.push_back(pair<int, string>(1, "a"));
	v.push_back(pair<int, string>(2, "b"));
	v.push_back(pair<int, string>(3, "c"));
	v.push_back(pair<int, string>(4, "d"));
	m.insert(v.begin(), v.end());
	for (auto ch : m)
	{
		cout << ch.first << ":" << ch.second << endl;
	}
}

那么上面的代码运行的结果就如下:
在这里插入图片描述

那么这就是insert函数的第三种形式。

make_pair函数

通过上面的例子大家可以应该知道了如何往容器里面插入数据,可是大家有没有发现一个现象好像每次插入数据都很麻烦对吧,有匿名对象还好点我们可以在传参的时候顺便创建一个对象,那没有匿名对象的话在插入数据之前还得创建一个一摸一样的pair对象,是不是就很麻烦对吧,所以就有了make_pair函数,我们来看看这个函数的介绍:
在这里插入图片描述
这个函数的作用就是根据我们传的参数类型自动创建pair对象,并且返回值也是相应的pair类型,make_pair的第一个参数就对应的是pair中的k,第二个参数就对应的是pair中的v,所以我们使用isnert函数的时候就可以这样:

void func5()
{
	map<int, string> m;
	m.insert(make_pair(1, "a"));
	m.insert(make_pair(2, "b")); 
	m.insert(make_pair(3, "c"));
}

可以在一定程度上减少写代码的成本。

find函数

首先来看看find函数的介绍:

在这里插入图片描述
find函数找到数据之后就会返回一个指向该位置的迭代器,如果没有找到的话这个迭代器的值就为end,比如说下面的代码:

void func5()
{
	map<int, string> m;
	m.insert(make_pair(1, "a"));
	m.insert(make_pair(2, "b")); 
	m.insert(make_pair(3, "c"));
	auto it = m.find(2);
	if (it != m.end())
	{
		cout << it->first << ":"<<it->second << endl;
	}
	else
	{
		cout << "该数据不存在" << endl;
	}
}

这段代码的运行结果如下:
在这里插入图片描述
如果我们给这个find传递一个不存在的k值的话就会返回一个指向end的迭代器,比如说下面的代码:

void func5()
{
	map<int, string> m;
	m.insert(make_pair(1, "a"));
	m.insert(make_pair(2, "b")); 
	m.insert(make_pair(3, "c"));
	auto it = m.find(4);
	if (it != m.end())
	{
		cout << it->first << ":"<<it->second << endl;
	}
	else
	{
		cout << "该数据不存在" << endl;
	}

代码的运行结果如下:
在这里插入图片描述
那么这就是find函数的用法。

map的[ ]重载

首先我们用map来实现一个统计一个数组中各种水果名字出现的次数,比如说下面的代码:

void func6()
{
	string arr[] = { "苹果","苹果", "苹果", "苹果", "苹果",
				  "香蕉","香蕉", "香蕉", "香蕉", "香蕉",
					"西瓜","西瓜", "西瓜", "梨子", "梨子" };
}

我们要对数组中出现的各种水果名进行统计并得到每个水果名出现的次数,那这里我们就可以创建一个map并以string类型的数据为k,以int类型的数据为v,然后用for循环遍历arr中的每个元素,并用find函数查找当前遍历的元素是否在map里面,如果在的话就对该数据的second进行++,如果不在的话往就往map里面插入一个数据,比如说下面的代码:

	string arr[] = { "苹果","苹果", "苹果", "苹果", "苹果",
				  "香蕉","香蕉", "香蕉", "香蕉", "香蕉",
					"西瓜","西瓜", "西瓜", "梨子", "梨子" };
	map<string, int> m;
	for (auto ch : arr)
	{
		auto it = m.find(ch);
		if (it != m.end())//说明当前的元素在map里面
		{
			it->second++;
		}
		else//说明当前的元素不在map里面
		{
			m.insert(make_pair(ch, 1));
		}
	}

最后我们再使用范围for遍历一下map容器就可以达到我们的预期,比如说下面的代码:

void func6()
{
	string arr[] = { "苹果","苹果", "苹果", "苹果", "苹果",
				  "香蕉","香蕉", "香蕉", "香蕉", "香蕉",
					"西瓜","西瓜", "西瓜", "梨子", "梨子" };
	map<string, int> m;
	for (auto ch : arr)
	{
		auto it = m.find(ch);
		if (it != m.end())//说明当前的元素在map里面
		{
			it->second++;
		}
		else//说明当前的元素不在map里面
		{
			m.insert(make_pair(ch, 1));
		}
	}
	for (auto ch : m)
	{
		cout << ch.first << ":" << ch.second << endl;
	}
}

那么代码的运行结果就如下:
在这里插入图片描述
可以看到上面的代码符合我们的预期,但是这么写好像有那么点麻烦我们可以可以将其改进一下变成下面的形式也能达到我们的要求,比如说下面的代码:

void func6()
{
	string arr[] = { "苹果","苹果", "苹果", "苹果", "苹果",
				  "香蕉","香蕉", "香蕉", "香蕉", "香蕉",
					"西瓜","西瓜", "西瓜", "梨子", "梨子" };
	map<string, int> m;
	for (auto ch : arr)
	{
		m[ch]++;
	}
	for (auto ch : m)
	{
		cout << ch.first << ":" << ch.second << endl;
	}
}

这段代码的运行结果如下:
在这里插入图片描述
代码的运行结果是一样的,但是比上面的简单了许多,那这里的原理是什么呢?我们来看看map容器中对[ ]重载的介绍:
在这里插入图片描述

首先方括号重载的声明是这样的: mapped_type& operator[](const key_type&k) 接受的参数类型为key_type也就是我们平时说的Key,返回的数据类型为mapped_type也就是我们平时说的value,方括号重载执行的操作就是下面这样:return (*((this->insert(make_pair(k,mapped_type()))).first)).second;也就是说方括号本质上调用的是insert函数,给insert函数传递的参数为看,另外一个为v的默认构造函数,我们将insert函数去掉就变成了下面这个样子:return (*(返回值.first)).second;先拿到insert返回的pair然后取得第一个第一个元素并对其进行解引用,我们知道insert返回的是pair,该pair中的第一个元素为指向原有的或者新创建元素的迭代器,那么对这个迭代器解引用我们就可以拿到map容器中我们想要的数据,那么再去掉一层就变成这样:(*迭代器).second这里的迭代器是map的迭代器,所以对他解引用就可以map中的pair的数据,这里拿的是second就是v的数据,并将其返回所以我们就可以直接通过方括号来插入数据并对内部数据的value进行修改,这里可以对方括号进行一下简化变成下面这样大家就可以更好的理解:

v& operator[](const K& k)
{
	pair<iterator,bool> ret =insert(make_pair(k,v()));
	return ret.first->second;
	//这里的迭代器指向的又是一个迭代器,并且是引用返回所以这里可以根据返回值来进行一下修改
}

所以map的方括号就会有这么一个特性:如果容器中不存在要查找的数据那么他会自动创建该数据并返回该数据的value,如果容器存在了就不会自动创建直接返回该数据的value。

multimap

在这里插入图片描述

multimap的允许数据冗余,但是它没有提供方括号的功能,因为方括号具备插入和查找的功能,这里有多个数据所以方括号不知道要返回哪一个,所以这里就不提供方括号,但是提供find函数,这个也是返回中序的第一个出现的要查找的元素。
在这里插入图片描述
multimap也可以用来统计次数比如说下面的代码:

void func7()
{
	string arr[] = { "苹果","苹果", "苹果", "苹果", "苹果",
				  "香蕉","香蕉", "香蕉", "香蕉", "香蕉",
					"西瓜","西瓜", "西瓜", "梨子", "梨子" };
	multimap<string, int> m;
	for (auto ch : arr)
	{
		auto it = m.find(ch);
		if (it != m.end())//说明当前的元素在map里面
		{
			it->second++;
		}
		else//说明当前的元素不在map里面
		{
			m.insert(make_pair(ch, 1));
		}
	}
	for (auto ch : m)
	{
		cout << ch.first << ":" << ch.second << endl;
	}
	multimap<string, string> m1;
	m1.insert(make_pair("苹果", "香蕉"));
	m1.insert(make_pair("苹果", "梨子"));
	m1.insert(make_pair("苹果", "西瓜"));
	for (auto ch : m1)
	{
		cout << ch.first << ":" << ch.second << endl;
	}
}

这段代码的运行结果如下:
在这里插入图片描述
那么这就是multimap的用法,用的很少并且功能和map非常的相似,所以这里就不多说了。

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

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

相关文章

Redis实现分布式锁详解

Redis实现分布式锁详解 一 分布式锁简介二 Redis实现分布式锁核心思路三 Redis实现分布式锁实践3.1 锁的基本接口3.2 加锁解锁逻辑3.3 修改业务逻辑3.4 单元测试观察结果 四 Redis分布式锁误删情况4.1.Redis分布式锁误删情况逻辑说明&#xff1a;4.2 解决Redis分布式锁误删问题…

当心健身跑步应用悄悄泄露用户住址

据BleepingComputer 6月11日消息&#xff0c;美国北卡罗来纳州立大学罗利分校的研究人员发现 Strava 应用程序的热图功能存在隐私风险&#xff0c;可能导致攻击者识别出用户的家庭住址。 Strava 是一款流行的跑步伴侣和健身追踪应用程序&#xff0c;在全球拥有超过 1 亿用户&a…

2个月“我“从功能测试进阶到自动化测试,offer收到麻了...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 自动化测试是将人…

C++【STL】之vector模拟实现

C vector类模拟实现 上一篇讲解了vector的使用&#xff0c;这一篇接着介绍vector的模拟实现&#xff0c;这里依然是讲解常用接口的模拟实现&#xff0c;话不多说&#xff0c;正文开始&#xff01; 文章目录&#xff1a; C vector类模拟实现1. 成员变量2. 默认成员函数2.1 构造…

使用lcov生成覆盖率报告

使用lcov生成覆盖率报告 1- 需要准备的东西1.1 工具lcov1.2 需要用到中间脚本 gcno gcda1.3 源文件 2- 生成覆盖率报告2.1 step1: 编译阶段2.2 step2: 数据收集与提取阶段2.3 step3: 报告形成阶段2.4 step4: lcov生成覆盖率报告结果info文件2.5 step5: genhtml 命令生成网页版的…

给定一个字符串比如“abcdef”,要求写个函数变成“defabc”,位数是可变的。

首先可以使用字符串切片的方法来实现这个需求。 具体做法是&#xff1a;① 定义一个整数变量 n 表示要切割的位置&#xff0c;本实例中为 3 。 ② 将字符串按照 n 分割成两个字串&#xff0c;即 “abc” 和 “def”。 ③ 将两个字符串颠倒顺序&#xff0c;即 “cba” 和 “fed…

数据结构 栈和队列

栈和队列基本概念 栈&#xff08;Stack&#xff09;和队列&#xff08;Queue&#xff09;都是常见的数据结构&#xff0c;用于存储和操作一组元素。它们在结构和操作方式上有所不同。 栈的基本概念&#xff1a; 栈是一种线性数据结构&#xff0c;具有后进先出&#xff08;L…

CentOS GCC 离线升级 编译安装 8.3.0

从系统自带的 gcc-4.8.5 版本升级至 gcc-8.3.0 版本 目录 下载源代码&#xff1a; 下载依赖&#xff1a; 编译&#xff08;约一个小时&#xff09; 重开控制台确认是否生效 下载源代码&#xff1a; https://ftp.gnu.org/gnu/gcc/gcc-8.3.0/gcc-8.3.0.tar.gzhttps://ftp.gn…

Nacos和Feign

Nacos配置管理 统一配置管理实现 1.引入Nacos的配置管理客户端依赖 <!--nacos的配置管理依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency…

国产开源中文大语言模型再添重磅玩家:清华大学NLP实验室发布100亿参数规模的开源可商用大语言模型CPM-Bee

5月27日&#xff0c;OpenBMB发布了一个最高有100亿参数规模的开源大语言模型CPM-BEE&#xff0c;OpenBMB是清华大学NLP实验室联合智源研究院成立的一个开源组织。该模型针对高质量中文数据集做了训练优化&#xff0c;支持中英文。根据官方的测试结果&#xff0c;其英文测试水平…

Python零基础入门(二)——IDE介绍以及Python+PyCharm的安装

系列文章目录 个人简介&#xff1a;机电专业在读研究生&#xff0c;CSDN内容合伙人&#xff0c;博主个人首页 Python入门专栏&#xff1a;《Python入门》欢迎阅读&#xff0c;一起进步&#xff01;&#x1f31f;&#x1f31f;&#x1f31f; 码字不易&#xff0c;如果觉得文章不…

docker容器介绍及安装

Docker介绍 Docker 起源于2013年。 Docker 是一个开源的应用容器引擎&#xff0c;基于 Go语言开发&#xff0c;Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的系统。 优点&#xff1a; 可以用来快速交付应用。加…

SQL 的window开窗函数简单使用

背景&#xff1a; 开窗函数不论是spark的还是clickhouse的在日常的查询中是一个很常用的功能&#xff0c;特别是他想要解决的问题和group by的很类似&#xff0c;这两种容易引起混淆&#xff0c;本文就简单的描述下开窗函数的简单用法 使用详解 首先窗口函数和group by是完全…

caj文件在线转换成pdf方法,看这个就会了!

当需要将Caj文件转换为PDF格式时&#xff0c;有多种方法可供选择。本文将介绍三种常用的方法&#xff0c;以帮助您完成这个任务。 第一种方法&#xff1a;使用记灵在线工具 一种常用的方法是利用记灵在线工具&#xff0c;它是一款提供免费文件转换服务的在线工具。以下是使用…

消息队列RabbitMQ

1. 消息队列 RabbitMQ 消息队列是一种在应用程序之间发送和接收消息的方法&#xff0c;可以实现异步通信、解耦应用、提高系统性能等效果。RabbitMQ 是一款常用的开源消息中间件&#xff0c;它实现了 AMQP 协议规范&#xff0c;并提供了可靠性、灵活性、易用性等优秀特性。本文…

DBSyncer安装_配置postgresql和mysql_sqlserver_oracel全量增量同步---数据全量增量同步之DBSyncer001

国内做开源的大神做的,用了一下还可以,就是不能和Phoenix这种操作hbase等数据库一起用, https://gitee.com/ghi/dbsyncer#postgresql 这个是官网,下载安装非常简单,官网也有中文详细说明. 直接下载安装包: 然后解压到某个地方,主要要用unzip dbsyncer.zip -d /opt/module这样…

干翻Mybatis源码系列之第十篇:Mybatis拦截器基本开发、使用和细节分析

给自己的每日一句 不从恶人的计谋&#xff0c;不站罪人的道路&#xff0c;不坐亵慢人的座位&#xff0c;惟喜爱耶和华的律法&#xff0c;昼夜思想&#xff0c;这人便为有福&#xff01;他要像一棵树栽在溪水旁&#xff0c;按时候结果子&#xff0c;叶子也不枯干。凡他所做的尽…

微信小程序 method传参 和 页面传参

method传参 标签&#xff1a; <image src"/img/b1.jpg" classbannerImg mode"widthFix" bindtap"gotoMessage" data-flag"msg"></image> 使用data-参数Key 指定参数值 method: gotoMessage(e){ let flagName e.targe…

9. 子查询

9.1 概述 ​ 子查询指一个查询语句嵌套在另一个查询语句内部&#xff0c;这个特性从 MySQL 4.1 开始引入。 ​ 从相对位置来说&#xff0c;子查询又被称为内查询&#xff0c;主查询又被称为外查询 9.1.1 子查询的结构 子查询的结构如下所示&#xff1a; SELECT select_lis…

Apache Zeppelin系列教程第九篇——SQL Debug In Zeppelin

SQL Debug介绍 首先介绍下什么是SQL Debug&#xff1f; 但是经常有这样一个需求&#xff0c;一大段sql 跑出来之后&#xff0c;发现不是自己想要的结果&#xff1f;比如&#xff1a; demo 1: select id,name from ( select id,name from table1 union all select id,name fr…