今天你学C++了吗?——map

今天你学C++了吗?——map


♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥

♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥

♥♥♥我们一起努力成为更好的自己~♥♥♥

♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥

♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥✨✨✨✨✨✨ 个人主页✨✨✨✨✨✨

前面我们已经学习了set容器的使用,接下来我们来看看map容器有什么奇妙之处?准备好了吗~我们发车去探索C++的奥秘啦~🚗🚗🚗🚗🚗🚗

目录

什么是map?

pair

什么是pair?

pair的组成

pair的构造与初始化

pair的成员函数

pair的比较

​编辑

pair的用途

map的构造

map的插入

​编辑

operator[ ]

at

multimap

equal_range

equal_range、lower_bound和upper_bound简单对比

1. equal_range

2. lower_bound

3. upper_bound

对比与联系

C++中map和set容器的简单对比


什么是map?

   map的声明定义了两种类型:KeyT。其中,Keymap底层使用的关键字(键)的类型,而T是与之对应的值(value)的类型。对于set(集合),默认情况下要求Key类型支持小于比较操作。如果Key类型不支持小于比较,或者需要自定义比较逻辑,可以通过传递一个仿函数(即自定义的比较函数对象)作为map的第二个模板参数来实现。

   map底层存储数据的内存是通过空间配置器分配的。在大多数情况下,我们不需要指定map的后两个模板参数(即比较函数和内存分配器),因为它们有默认的实现。map的底层实现是基于红黑树,红黑树是一种自平衡的二叉搜索树,它能够在O(logN)的时间复杂度内完成增、删、查、改操作。我们使用迭代器遍历map时,会按照中序遍历的顺序进行,这意味着遍历将按照key的有序顺序进行。

map(映射)是一个关联容器,它存储的是键值对(key-value pairs)。每个键(key)在map中是唯一的,并且每个键都映射到一个值(value)。

  • 特点
    • 键是唯一的。
    • 自动按键排序(默认情况下,使用<运算符排序,但可以自定义排序规则)。
    • 查找、插入和删除操作的时间复杂度通常为O(log n),因为map内部通常实现为红黑树。
  • 用途
    • 当你需要快速查找、插入和删除键值对时。
    • 当你需要存储唯一键的数据集时。

pair

什么是pair?

        在真正地探讨map之前,我们首先需要解决我们以前留下来的问题,什么是pair?

        C++标准库中,pair是一个模板类,用于存储一对值。这对值可以是任何类型,包括自定义类型。pair通常用于需要同时返回或存储两个相关数据的场景~

pair的组成

pair由两个成员组成:

  • firstpair的第一个元素,可以是任何类型。
  • secondpair的第二个元素,也可以是任何类型,并且不必与first的类型相同。

pair的构造与初始化

pair可以通过多种方式构造和初始化:

  • 默认构造:创建一个pair对象,其firstsecond成员都被初始化为它们的默认值(通常是零或空指针,取决于类型)。
  • 直接初始化:在构造pair对象时直接提供firstsecond的值。
  • 列表初始化(C++11及以后):使用花括号{}提供firstsecond的值。
  • make_pair函数:使用std::make_pair函数可以方便地构造一个pair对象,而无需显式指定类型。
#include<utility>//pair头文件 void test1() { pair<int, string> p1;//默认构造 pair<int, string> p2(2, "Hello!");//直接初始化 pair<int, string> p3 = { 3,"Haha!" };//列表初始化 pair<int, string> p4 = make_pair(4, "Hehe!");//make_pair函数 cout << p1.first << " " << p1.second << endl; cout << p2.first << " " << p2.second << endl; cout << p3.first << " " << p3.second << endl; cout << p4.first << " " << p4.second << endl; }

pair的成员函数

pair提供了一些成员函数来访问和操作其成员:

  • firstsecond:访问pairfirstsecond成员。
  • operator=:赋值操作符,用于将一个pair对象的值赋给另一个pair对象。
  • swap:交换两个pair对象的值。
void test2() { pair<int, string> p1;//默认构造 pair<int, string> p2(2, "Hello!");//直接初始化 pair<int, string> p3 = { 3,"Haha!" };//列表初始化 pair<int, string> p4 = make_pair(4, "Hehe!");//make_pair函数 p3 = p2;//支持赋值 p1.swap(p4);//支持交换 cout << p1.first << " " << p1.second << endl; cout << p2.first << " " << p2.second << endl; cout << p3.first << " " << p3.second << endl; cout << p4.first << " " << p4.second << endl; }

pair的比较

pair对象可以使用关系运算符(<, <=, >, >=, ==, !=)进行比较。比较是基于first成员的字典序进行的,如果first成员相等,则比较second成员。

void test3() { pair<int, string> p1;//默认构造 pair<int, string> p2(2, "Hello!");//直接初始化 pair<int, string> p3 = { 3,"Haha!" };//列表初始化 pair<int, string> p4 = make_pair(4, "Hehe!");//make_pair函数 pair<int, string> p5 = make_pair(4, "AAAA!");//make_pair函数 if (p2 > p3) cout << "p2 > p3" << endl; else cout << "p2 < p3" << endl; if (p4 < p5) cout << "p4 < p5" << endl; else cout << "p4 > p5" << endl; //first相等,比较second }

pair的用途

pair在C++编程中有多种用途,包括但不限于:

  • 返回多个值:函数可以返回一个pair对象,从而同时返回两个值。
  • 存储相关数据:在需要同时存储两个相关联的数据项时,可以使用pair
  • 作为其他容器的元素pair可以作为其他容器(如vector, list, set等)的元素类型,用于存储键值对或其他成对的数据。

接下来我们的map就会大量使用pair~

        在map中,每个红黑树节点都存储一个pair<Key, T>对象。这允许map将键和值紧密地关联在一起,并有效地管理它们。当我们向map中插入一个元素时,实际上是在红黑树中插入一个新的节点,该节点包含一个pair<Key, T>对象,其中Key是我们要插入的键,T是与该键相关联的值。

map的构造

        map的构造涉及选择键和值的类型,并使用合适的构造函数。最常用的构造函数包括默认构造函数(创建空map)、拷贝构造函数(基于现有map创建新map),以及迭代器区间构造函数(基于迭代器指定的元素范围初始化map)。map的构造灵活,支持多种初始化方式,满足不同的编程需求。构造后的map提供高效的插入、删除和查找操作,时间复杂度为O(log n)。

map的插入

        map提供了insert成员函数,来进行插入,同样有多个版本,我们可以根据需要进行选择~



        我们可以看到第一个insert插入函数,返回值是一个pair<iterator,bool>,返回一个 pair,其中 first 是一个迭代器,指向插入的键值对,second 是一个布尔值,来表示插入是否成功~
void test4() { map<int, string> mymap; mymap.insert(pair<int, string>(1, "Hello!")); pair<int, string> p(2, "Haha!"); mymap.insert(p); mymap.insert(make_pair(3, "Hehe!")); mymap.insert({ 4,"Heihei!" }); mymap[5] = "Five";//这里还可以使用数组的方式,更加巧妙 //遍历打印键值对 for (const auto& e : mymap) { cout << "Key:" << e.first << " " << "Value:" << e.second << endl; } }
        我们还需要注意的是map是不支持key冗余的,即使它们的value不一样,所以相同的key插入就会失败~
void test5() { map<int, string> mymap; mymap.insert(pair<int, string>(1, "Hello!")); pair<int, string> p(2, "Haha!"); mymap.insert(p); mymap.insert(make_pair(3, "Hehe!")); mymap.insert({ 4,"Heihei!" }); mymap.insert({ 4,"He!" }); //使用迭代器遍历访问 auto it = mymap.begin(); while (it != mymap.end()) { //cout << (*it).first << " " << (*it).second << endl; cout << it->first << " " << it->second << endl; //本质上是下面这种调用方式,编译器进行了优化 //cout << it.operator->()->first << " " << it.operator->()->second << endl; it++; } }

operator[ ]

前面我们使用初始化直接使用数组来对map进行初始化,实现了插入元素并且修改value,这就不得不提map对[]运算符的重载了

下面这一句是重点:

让我们逐步解析:this->insert(make_pair(k, mapped_type())):this 指针指向当前对象。insert 是当前对象的一个成员函数。make_pair(k, mapped_type()) 创建了一个键值对,其中 k 是键,mapped_type() 是默认构造的值。insert 函数将这个键值对插入到当前对象中,并返回一个 pair,其中 first 是一个迭代器,指向插入的键值对,second 是一个布尔值,表示插入是否成功。(this->insert(make_pair(k, mapped_type()))).first:从 insert 函数返回的 pair 中取出 first,即指向插入的键值对的迭代器。*((this->insert(make_pair(k, mapped_type()))).first):对迭代器进行解引用,得到插入的键值对。(*((this->insert(make_pair(k, mapped_type()))).first)).second:从解引用的键值对中取出 second,即插入的值。

根据这个,我们就可以来简单实现一下operator的底层:

//operaror[]底层 Value& opertor[](const K& key) { //首先调用insert,得到返回的pair类型 pair<iterator, bool> ret = inset({ key,Value() }); //返回插入键值对迭代器里面的第二个,也就是插入位置的value return ret.first->second; }

这里的Value()也就是调用的默认构造~返回值也就是Value值的引用~

事实上,operator[ ]的使用不仅仅限于此,它还有其他的使用方式~我们一起来看看:

void test6() { map<int, string> mymap; mymap.insert(pair<int, string>(1, "Hello!")); pair<int, string> p(2, "Haha!"); mymap.insert(p); mymap.insert(make_pair(3, "Hehe!")); //1.插入 mymap[4]; //2、插入+修改 mymap[5] = "HHHH";//没有存在的,就插入然后返回的value值进行了修改 //3、修改 mymap[1] = "Heee";//已经存在的修改value值 //4.查找对应key的value值 cout << "mymap[2]:" << mymap[2] << endl; auto it = mymap.begin(); while (it != mymap.end()) { cout << it->first << " " << it->second << endl; it++; } }

可以发现map对[]的重载给我们带来了极大的方便~

接下来我们来看一段小程序,进一步体会operator[]的魅力~

void test7() { vector<string> s = { "key","value","learn","learn","key","hello","key" }; //单词计数 map<string, int> count_map; for (auto e : s) { //利用[]重载 //没有就进行插入并且修改value值 //有就利用找到的迭代器对value值进行修改 count_map[e]++; } auto it = count_map.begin(); while (it != count_map.end()) { cout << (*it).first << " " << (*it).second << endl; it++; } }

这样使用[ ] 就十分方便~

        值得注意的是,map实现了[]运算符重载,但是set、multimap、multiset都没有实现[]运算符重载,所以使用的时候我们需要注意可以使用的地方~

at

我们还可以看到C++11还添加了at函数用于访问map中指定键对应的值,有两种重载形式:非常量版本和常量版本。如果键存在,返回对应值的引用;如果键不存在,抛出std::out_of_range异常,这提供了一种安全的元素访问方式。

简单测试:

void test8() { map<int, string> mymap; mymap.insert(pair<int, string>(1, "Hello!")); pair<int, string> p(2, "Haha!"); mymap.insert(p); mymap.insert(make_pair(3, "Hehe!")); cout << mymap.at(1) << endl;//键存在,返回对应值的引用 //cout << mymap.at(4) << endl;//err,键不存在,抛出std::out_of_range异常 }

我们已经讲解了map容器里面的大多数接口,剩下的接口大家可以查阅文档C++中的map,大多数与我们前面讲解的set接口是一样的,大家也可以参考前面set的文章~

接下来,我们来看看multimap:

multimap

   map中每个键是唯一的,插入相同键会失败或更新值;而multimap允许多个相同键的元素,两者通常基于红黑树实现,查找、插入和删除操作的时间复杂度为O(log n)~

我们来看看它们的使用:

void test9() { //是否插入看的是key,不是value //map不支持key冗余,无论value是否一样 map<int, string> mymap; mymap.insert({ 1, "Hello" }); mymap.insert({ 1, "Haha" }); mymap.insert({ 2,"Hehe" }); mymap.insert({ 3,"H" }); mymap.insert({ 3,"H" }); //multimap支持key冗余,无论value是否一样 multimap<int, string> mul_map; mul_map.insert({ 1, "Hello" }); mul_map.insert({ 1, "Haha" }); mul_map.insert({ 2,"Hehe" }); mul_map.insert({ 3,"H" }); mul_map.insert({ 3,"H" }); cout << "map:" << endl; auto it = mymap.begin(); while (it != mymap.end()) { cout << it->first << " " << it->second << endl; it++; } cout << "multimap:" << endl; for (const auto& e : mul_map) { cout << e.first << " " << e.second << endl; } }

其他接口依然是类似的,这里就不过多的描述了~

equal_range

在set里面我们提到了lower_bound和upper_bound,这里我们有一个新的内容equal_range,它可以用来获取与指定键匹配的所有元素范围~返回值是pair类型,里面是两个迭代器,也就是开始位置的迭代器和结束位置后面的迭代器(左闭右开的区间)

void test10() { multimap<int, string> mul_map; mul_map.insert({ 1, "Hello" }); mul_map.insert({ 1, "Haha" }); mul_map.insert({ 2,"Hehe" }); mul_map.insert({ 3,"H" }); mul_map.insert({ 3,"H" }); //获取与指定键1匹配的所有元素范围 pair<multimap<int, string>::iterator,multimap<int, string>::iterator> ret = mul_map.equal_range(1); auto it = ret.first; //左闭右开的区间 while (it != ret.second)//ret是pair类型的 { cout << it->first << " " << it->second << endl; it++; } }

equal_rangelower_boundupper_bound简单对比

equal_rangelower_boundupper_bound都是C++标准库中关联容器(std::mapstd::multimapstd::setstd::multiset)的成员函数,用于查找元素或元素范围。它们之间的区别和联系如下:

1. equal_range

  • 功能:返回一个std::pair,其中first是指向第一个不小于指定键的元素的迭代器,second是指向第一个大于指定键的元素的迭代器。
  • 用途:用于获取与指定键匹配的所有元素范围,特别适用于允许多个相同键的容器(如std::multimapstd::multiset)。
  • 返回值:一个迭代器对,表示匹配元素的范围。

2. lower_bound

  • 功能:返回一个迭代器,指向第一个不小于(即大于或等于)指定键的元素。
  • 用途:用于找到指定键或下一个更大键的起始位置。
  • 返回值:单个迭代器,指向第一个不小于指定键的元素。

3. upper_bound

  • 功能:返回一个迭代器,指向第一个大于指定键的元素。
  • 用途:用于找到大于指定键的起始位置。
  • 返回值:单个迭代器,指向第一个大于指定键的元素。

对比与联系

  • 关系equal_range实际上可以通过lower_boundupper_bound的组合来实现,具体来说,equal_range(key)返回的范围等同于{lower_bound(key), upper_bound(key)}
  • 使用场景
    • 如果你只需要找到第一个不小于指定键的元素,使用lower_bound
    • 如果你只需要找到第一个大于指定键的元素,使用upper_bound
    • 如果你需要找到与指定键匹配的所有元素范围,使用equal_range

C++中mapset容器的简单对比

存储内容

  • map:存储键值对,每个键对应一个值。
  • set:只存储键,不存储值。

唯一性

  • mapset都保证存储的元素(键)是唯一的。

排序

  • mapset中的元素都会自动排序。

时间复杂度

  • 查找、插入、删除mapset这些操作的时间复杂度都是O(log n)。

使用场景

  • map:当你需要存储键值对,并且希望快速查找、插入和删除时。
  • set:当你只需要存储唯一元素,并且希望它们自动排序,同时支持快速查找、插入和删除时。



♥♥♥本篇博客内容结束,期待与各位优秀程序员交流,有什么问题请私信♥♥♥

♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥

✨✨✨✨✨✨个人主页✨✨✨✨✨✨

Read more

最新电子电气架构(EEA)调研-3

而新一代的强实时性、高确定性,以及满足CAP定理的同步分布式协同技术(SDCT),可以实现替代TSN、DDS的应用,且此技术已经在无人车辆得到验证,同时其低成本学习曲线、无复杂二次开发工作,将开发人员的劳动强度、学习曲线极大降低,使开发人员更多的去完成算法、执行器功能完善。 五、各大车厂的EEA 我们调研策略是从公开信息中获得各大车厂的EEA信息,并在如下中进行展示。 我们集中了华为、特斯拉、大众、蔚来、小鹏、理想、东风(岚图)等有代表领先性的车辆电子电气架构厂商。        1、华为 图12 华为的CCA电子电气架构              (1)华为“计算+通信”CC架构的三个平台                         1)MDC智能驾驶平台;                         2)CDC智能座舱平台                         3)VDC整车控制平台。        联接指的是华为智能网联解决方案,解决车内、车外网络高速连接问题,云服务则是基于云计算提供的服务,如在线车主服务、娱乐和OTA等。 华

By Ne0inhk
Apache IoTDB 架构特性与 Prometheus+Grafana 监控体系部署实践

Apache IoTDB 架构特性与 Prometheus+Grafana 监控体系部署实践

Apache IoTDB 架构特性与 Prometheus+Grafana 监控体系部署实践 文章目录 * Apache IoTDB 架构特性与 Prometheus+Grafana 监控体系部署实践 * Apache IoTDB 核心特性与价值 * Apache IoTDB 监控面板完整部署方案 * 安装步骤 * 步骤一:IoTDB开启监控指标采集 * 步骤二:安装、配置Prometheus * 步骤三:安装grafana并配置数据源 * 步骤四:导入IoTDB Grafana看板 * TimechoDB(基于 Apache IoTDB)增强特性 * 总结与应用场景建议 Apache IoTDB 核心特性与价值 Apache IoTDB 专为物联网场景打造的高性能轻量级时序数据库,以 “设备 - 测点” 原生数据模型贴合物理设备与传感器关系,通过高压缩算法、百万级并发写入能力和毫秒级查询响应优化海量时序数据存储成本与处理效率,同时支持边缘轻量部署、

By Ne0inhk
SQL Server 2019安装教程(超详细图文)

SQL Server 2019安装教程(超详细图文)

SQL Server 介绍) SQL Server 是由 微软(Microsoft) 开发的一款 关系型数据库管理系统(RDBMS),支持结构化查询语言(SQL)进行数据存储、管理和分析。自1989年首次发布以来,SQL Server 已成为企业级数据管理的核心解决方案,广泛应用于金融、电商、ERP、CRM 等业务系统。它提供高可用性、安全性、事务处理(ACID)和商业智能(BI)支持,并支持 Windows 和 Linux 跨平台部署。 一、获取 SQL Server 2019 安装包 1. 官方下载方式 前往微软官网注册账号后,即可下载 SQL Server Developer 版本(

By Ne0inhk