玩转C++11之map

本文是玩转C++11的map的单独文章,更多内容可以点击链接跳转

map——映射

map使用的是红黑树用于查找,封装在头文件map中。
先用代码说说map是怎么使用的吧:

#include<map>
std::map<key,value> temp = {key1,value1};

map的元素为pair,它跟map有点像:

std::pair<key,value> temp = {key1,value1};

并且,map也提供了迭代器,由于其内部的实现使用的是双向链表,因此其迭代器也是双向迭代器。
在先前我们已经说了map的底层是基于红黑树实现的,因此存进map的值需要能够明确地进行大小比较。所以在使用自定义类型的时候,如果要让我们的自定义数据类型作为map的键,至少需要重载<运算符

(1) map的基本操作
方法作用时间复杂度
insert({key , value})向map中插入一个键值对<key,value>O(logn)
erase(key)删除map中的指定的键值对O(logn)
find(key)查找map中指定键对应的键值对的迭代器O(logn)
count(key)查找键的数量,在普通map中,由于键唯一,故只返回0、1O(logn)
operator[key]查找map中指定键对应的值O(logn)
size()返回map中键值对的数量O(1)
clear()清空map中所有的键值对O(n)
empty()判断map是否为空O(1)
begin()返回map中第一个键值对的迭代器O(1)
cbegin()前者的const版本
rbegin()指向“起始”的逆向迭代器
end()返回map中最后一个键值对的下一个位置的迭代器O(1)
cend()前者的const版本
rend()指向“尾节点的下一个节点“的逆向迭代器
emplace()原地构造函数
(2) pair——map的元素

经过上面的表述,已经知道了pair是map的元素,它储存的是键值对,根据所在位置的不同我们可以使用first和second来分别表示key和value:

std::pair<const std::string,int> student("西施",18);
std::cout  << "student.first = " << student.first << '\n';
std::cout << "student.second = " << student.second;

所以我们想要操作map其实是对pair的一系列操作。
map有三种额外类型:key_type,mapped_type和value_type:

额外类型它是什么?
key_type此容器的关键字类型,也就是key
mapped_type关键字的关联类型,也就是value
value_typemap的类型,其实就是pair<const key_type, mapped_type>

可能会注意到:为什么map的value_type的键使用了const修饰?这是因为map的键是不允许修改的,将其修饰为const也就避免了误操作。

(5) map的迭代器

map也是支持迭代器的,跟大多数STL中的模板类一样,虽然map的元素是pair,但是它的迭代器并不是pair类型,而是:

std::map<key_type, mapped_type>::iterator

这个也很好理解,我也就不再赘述了。
在这里插入图片描述

(4) insert()

insert()有很多需要注意的地方,我们先看看insert的声明吧:

//实际上有很多,不止这一个
pair<iterator, bool> insert(const value_type& value);

根据先前的内容,可以知道insert()传入的是一个pair,同时它返回一个pair,但是返回的这个pair并不是之前传入的pair了!那么这个pair究竟是什么呢?
它的first是个迭代器,second是一个布尔值,那么问题来了:first的迭代器指向谁呢?这里用一段代码来测试下:

std::map<std::string, int> girl = {};
std::pair<std::string, int> temp = { "西施", 18 };

auto res = girl.insert(temp);

std::cout << "res.first->first = " << res.first->first << "\n";
std::cout << "res.first->second = " << res.first->second;

输出的结果为:

西施 18

并且可以得到res的类型为:std::pair<std::map<std::string, int>::iterator, bool>。
因此,可以得出结论:如果insert()插入成功,这个迭代器将会指向这个成功插入的元素,同时bool设为true;若是插入失败,说明在map中已有这个关键字,则会指向map中,键与想要插入的元素的键相同的对象,bool设为false
在上面的代码中,我们是创建了一个std::pair的变量后,再将其用于insert()函数传参,但是更多情况下,我们是使用std::pair的非成员函数std::make_pair(),以此来避免创建多余的变量占用栈的空间,对上面的代码进行修改:

std::map<std::string, int> girl = {};

//make_pair不是pair的成员函数
//所以需要使用std来指定命名空间
auto res = girl.insert(std::make_pair("西施", 18));

std::cout << "res.first->first = " << res.first->first << "\n";
std::cout << "res.first->second = " << res.first->second;

这才是我们更常用的形式。

(5) find()与count()

find和count都是map中的查找元素的函数,但是它们的作用不太一样,我们分别看看二者的声明:

iterator find(const Key& key);

size_type count(const Key& key) const;

可以发现,虽然二者都起到了查找的作用,但是二者的返回值截然不同:前者是一个迭代器而后者是一个size_type(可以说就是size_t,一种无符号整型);find()返回的迭代器就是我们根据键值查到的那一个,如果它不在map中,则返回的迭代器等同于end(),由于在map中,每个键只对应一个值,所以count()的返回值只有0、1两种可能

(6) erase()

这是删除函数,能够删除指定的元素或指定范围内的元素。照例先看声明:

size_type erase(const Key& key);
iterator erase(iterator pos);
iterator erase(iterator first, iterator last);

这里我们写一段代码,试试第二种删除(因为第一种很好理解,第三种跟第二种本质上是一样的):

#include<iostream>
#include<map>
#include<iterator>

int main() {
	std::map<std::string, int> gl = {
		{"西施",18},
		{"貂蝉",19},
		{"如花",20}
	};

	//用auto会省事很多,我这么写是为了能更好地体现它的数据类型
	//target指向要删除的元素
	std::map<std::string, int>::iterator target = gl.begin();
	std::cout << target->first << " " << target->second << "\n";
	
	//temp是指向target的下一个元素的迭代器
	auto temp = target;
	temp++;
	std::cout << temp->first << " " << temp->second << "\n";

	//erase_res接收删除target之后返回的迭代器
	std::map<std::string, int>::iterator erase_res = gl.erase(target);
	std::cout << erase_res->first << " " << erase_res->second << "\n";
}

我们看看运行的结果是什么:

如花 20
西施 18
西施 18

可以发现,temp和erase_res指向同一个元素。
总结出来就是它有三个类型,也就是上述,我们一个个进行分析:

  1. 传入一个键,删除这个键所对应的键值对,返回一个size_type,表示删除元素的个数,在普通map中只可能是0、1。
  2. 传入一个迭代器,删除指定的迭代器,返回被删除元素的下一个元素的迭代器。
  3. 传入两个迭代器,删除这两个迭代器之间的元素(包括第一个迭代器和最后一个迭代器指向的元素),并返回删除后的下一个元素的迭代器。
(7) 随机访问[]

把它单独提出来肯定是它有特别的地方。在数组中我们就支持随机访问(即下标访问),map它也支持,并且访问方式很简单:跟数组差不多,只是将里面的元素变为map的键,这里给个实例:

#include<iostream>
#include<map>

int main(){
	std::map<std::string, int> gl = {
		{"西施",18},
		{"貂蝉",19},
		{"如花",20}
	};

	std::cout << "西施的年龄是:" << gl["西施"] << "\n";
}

运行这段代码,输出为:

西施的年龄是:18

那么有个问题:如果我要用下标访问不在map中的元素呢?是不是会失败?我们也试试,就接着上面的代码写:

std::cout << "杨玉环的年龄是:" << gl["杨玉环"] << "\n";

发现程序没有报错!居然能正常运行!:

杨玉环的年龄是:0

这就是map的特殊之处了:在map中使用随机访问,传入想要查找的键,如果这个键存在,则返回它相对应的值;若是不存在,则创建一个新的键值对,其对应的值进行值初始化
根据这个特点,在执行了gl[“杨玉环”]之后,我们理应是能在map中找到它的:

std::map<std::string, int>::iterator temp = gl.find("杨玉环");
std::cout << temp->first << " " << temp->second << "\n";

运行之后发现确实如此。

(8) emplace()

先看看它的源码:

template< class... Args >
std::pair<iterator, bool> emplace( Args&&... args );

它的作用是:原位构造元素并插入map中。它的作用其实和之前说的insert()一样,但是它比insert()更高效:emplace()是直接构造元素,而insert需要构造std::pair类型的临时变量后再进行插入操作
使用insert()添加元素:

#include<iostream>
#include<map>

int main() {
	std::map<std::string, int> opt;

	//方法一:显示构造
	opt.insert(std::make_pair<std::string, int>("西施", 18));

		//方法二:隐式转换构造
	opt.insert(std::make_pair("貂蝉", 19));

	for (std::pair<std::string, int> temp : opt) {
		std::cout << "name = " << temp.first << ",age = " << temp.second << '\n';
	}
}

使用emplace()添加元素:

#include<iostream>
#include<map>

int main() {
	std::map<std::string, int> opt;
	opt.emplace("西施", 18);
	opt.emplace("貂蝉", 19);

	for (std::pair<std::string, int> temp : opt) {
		std::cout << "name = " << temp.first << ",age = " << temp.second << '\n';
	}
}

这两段代码的效果是完全一样的,只是使用emplace()更加的便捷、高效

结语

至此,普通map就讲完了,但是map不止有这一种,它的键值不能重复,因此还有键值能重复的map——multimap。
如果觉得我写的不错,可以点赞收藏给予我鼓励,若是有出入或者不足,也请在评论区指出,我会立即修改!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

默示MoS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值