【c++丨STL】map/multimap的使用

简介: 本文详细介绍了STL关联式容器中的`map`和`multimap`的使用方法。`map`基于红黑树实现,内部元素按键自动升序排列,存储键值对,支持通过键访问或修改值;而`multimap`允许存在重复键。文章从构造函数、迭代器、容量接口、元素访问接口、增删操作到其他操作接口全面解析了`map`的功能,并通过实例演示了如何用`map`统计字符串数组中各元素的出现次数。最后对比了`map`与`set`的区别,强调了`map`在处理键值关系时的优势。

前言

    之前我们学习了STL关联式容器——set/multiset的使用,本篇文章我们将介绍另一组关联式容器**map(映射表)/multimap(多重映射表)**。
AI 代码解读

一、map/multimap的介绍

image.png

image.png

    与set相同,map的底层也是基于**红黑树**实现的,**其内部元素根据键自动升序排列**。
AI 代码解读

但两者有如下区别

存储内容:set存储的是,而map存储的是键值对(**数据元素是一个pair**)

元素访问:set只能访问,而map可以通过键来访问对应的值,并且值可以支持修改

因此,set适用于唯一元素的集合操作,如去重;而map更适用于处理键值关系

    相比map,multimap支持多个相同键存在。

    map和multimap相关接口查阅:
AI 代码解读

- C++ Reference

    map和multimap的**使用方法基本相同**,但相比multimap,**map更加常用**,所以**接下来的内容将主要聚焦于map常用接口的使用方法**,而在与**multimap**出现显著区别时,我们会适当提及它。
AI 代码解读

注意:在使用map/multimap时,要引头文件,且该容器定义在命名空间std当中。

二、map的默认成员函数

image.png

构造函数constructor

image.png

map有五种构造函数,其中较为常用的有如下四个:

函数原型 功能说明
map(); 无参构造
map(InputIterator first, InputIterator last); 迭代器区间构造
map(const map& x); 拷贝构造,用一个map对象构造另一个map对象
map(initializer_list<value_type> il); 初始化器构造

代码示例:

#include <map>
#include <string>
#include <iostream>
using namespace std;

int main()
{
   
    map<string, int> m1;
    map<string, int> m2({
    {
   "hello",1},{
   "hehe",3},{
   "haha",2} });
    map<string, int> m3(m2);
    map<string, int> m4(++m3.begin(), --m3.end());
    return 0;
}
AI 代码解读

析构函数destructor

image.png

析构函数在对象生命周期结束时自动调用,会销毁容器中的所有元素,并使用其分配器释放map容器分配的所有存储空间。

赋值重载

image.png

将新内容赋值给容器,替换当前内容。

代码示例:

#include <map>
#include <string>
#include <iostream>
using namespace std;

int main()
{
   
    map<int, int> m1 = {
    {
   1,1},{
   2,2},{
   3,3} };
    map<int, int> m2;
    map<int, int> m3;
    m2 = m1;
    m3 = {
    {
   1,1},{
   2,2},{
   3,3} };
    return 0;
}
AI 代码解读

三、map的迭代器接口

image.png

map的迭代器接口使用方法与之前学过的容器都相同,这里就不多赘述。

    需要注意以下几点:
AI 代码解读

1. map的迭代器是**双向迭代器**。

2. map迭代器进行**顺序遍历的结果是有序的,这归因于它采用了红黑树的中序遍历**算法。

3. 无论是使用普通迭代器还是const迭代器,都**无法修改map元素的键key,但是普通迭代器可以修改value**。

四、map的容量相关接口

image.png

empty

image.png

判断map容器是否为空,若为空,则返回true;否则返回false。

这个函数不会以任何方式修改容器。

代码示例:

#include <map>
#include <string>
#include <iostream>
using namespace std;

int main()
{
   
    map<int, int> m = {
    {
   1,1},{
   2,2},{
   3,3} };
    cout << m.empty() << endl;
    return 0;
}
AI 代码解读

image.png

size

image.png

size返回map容器内的元素个数

代码示例:

#include <map>
#include <string>
#include <iostream>
using namespace std;

int main()
{
   
    map<int, int> m = {
    {
   1,1},{
   2,2},{
   3,3} };
    cout << m.size() << endl;
    return 0;
}
AI 代码解读

image.png

五、map的元素访问接口

image.png

由于map元素是以键值对的方式存储,所以其相比set多了元素访问接口,我们可以使用该接口通过键来访问值。两个接口的详细讲解如下:

operator[ ]

image.png

operator[]在map当中的功能十分强大,它不仅支持根据键访问值,还支持元素的插入

首先,对于该重载函数,我们应将键key作为下标来传参

如果容器内已经有相同的key,那么该函数返回value的引用,达到根据键访问或修改值的效果。

如果容器内不存在key,那么该函数就会在容器中插入一个带有该key的新元素,并返回value的引用。此时我们将其返回值进行修改,就达到了插入一个键值对的效果。注意:若我们没有修改其返回值,容器中的元素个数也会+1,此时value的值为默认构造值

调用这个函数等价于以下语句:

(*((this->insert(make_pair(k,mapped_type()))).first)).second
AI 代码解读

使用示例:

#include <map>
#include <string>
#include <iostream>
using namespace std;

int main()
{
   
    map<int, int> m = {
    {
   1,1},{
   2,2},{
   3,3} };
    m[3] = 0;
    m[4] = 4;
    return 0;
}
AI 代码解读

at

image.png

与operator[ ]不同,at仅仅支持根据键访问或修改值,但不支持元素插入。我们将key作为参数传入,返回值是valus的引用。

当容器内不存在相同的key时,该函数会抛出异常

使用示例:

#include <map>
#include <string>
#include <iostream>
using namespace std;

int main()
{
   
    map<int, int> m = {
    {
   1,1},{
   2,2},{
   3,3} };
    m.at(3) = 0;
    m.at(4) = 4;
    return 0;
}
AI 代码解读

注意:对于multimap,不存在operator[ ]和at这两个函数。

六、map的增删相关接口

image.png

insert

image.png

insert的作用是插入元素,增加容器的大小。支持单个键值对插入、迭代器区间插入和初始化器插入。

因为map中元素的键是唯一的,所以插入操作会检查每个被插入元素的键是否与容器中已经存在的元素的键相等,如果相等,则不插入该元素,并返回包含重复键的元素的迭代器(如果该重载函数有返回值)

这里介绍一下第一个重载函数(单个元素插入)的返回值:是一个pair,当插入成功时,pair的第一个元素是指向新元素的迭代器,第二个元素是true;当因有重复键而插入失败时,pair的第一个元素是指向包含重复键的元素的迭代器,第二个元素是false。

使用示例:

#include <map>
#include <string>
#include <iostream>
using namespace std;

int main()
{
   
    map<int, int> m;
    map<int, int> m2({
    {
   3,3},{
   4,4} });
    m.insert({
    1,1 });
    m.insert(make_pair(2, 2));
    m.insert(m2.begin(), m2.end());
    m.insert({
    {
   5,5},{
   6,6} });
    return 0;
}
AI 代码解读

在map当中,使用operator[ ]插入元素的方法会比insert更加常用。

对于multimap的insert函数,其插入方法与map相同。并且即使有重复键,也会插入成功。

erase

image.png

erase的作用是删除map中的元素包括迭代器指定删除、按键删除和迭代器区间删除

对于按键删除的重载函数,它的返回值是被成功删除的元素数量,它的作用在支持重复键的multimap中较大。

使用示例:

#include <map>
#include <string>
#include <iostream>
using namespace std;

int main()
{
   
    map<int, int> m = {
    {
   1,1},{
   2,2},{
   3,3} };
    m.erase(1);
    m.erase(m.begin());
    m.erase(m.begin(), m.end());
    return 0;
}
AI 代码解读

swap

image.png

swap用于交换两个map容器的内容。使用方法与其他容器相同,不多赘述。

clear

image.png

清空map容器内的所有元素

七、map的其他操作接口

image.png

find

image.png

find用于按键查找元素如果找到了就返回指向该元素的迭代器,否则返回尾迭代器

count

image.png

count的作用是获取容器中键key所在元素的出现次数对于不允许键重复的map,它只返回0(表示不存在)或1(表示存在),可以用于判断某个元素是否在容器当中。而对于multimap,可以统计数量

lower_bound** upper_bound equal_range**

这三个函数的使用方法请参照这篇文章:

https://developer.aliyun.com/article/1656951

它们的使用方法、允许重复元素出现时的逻辑与set/multiset基本相同,并且并不是很常用,注意对于map/multimap而言是以键key为查找标准即可。

八、map的具体使用

    接下来我们使用map来解决一个具体问题:**有一个字符串数组,其中包含各种水果的名称,统计每种水果的出现次数**。
AI 代码解读

代码如下:

#include <map>
#include <string>
#include <iostream>
using namespace std;

int main()
{
   
    string arr[] = {
    "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
    map<string, int> m;
    for (auto& str : arr)
    {
   
        m[str]++;
    }
    for (auto& kv : m)
    {
   
        cout << kv.first << ':' << kv.second << endl;
    }
    return 0;
}
AI 代码解读

运行结果:

image.png

我们巧妙地利用了operator[ ]函数,当遇到新水果时,插入{水果,0},然后将其自增,出现次数变为1;当遇到已有水果时,直接将出现次数进行累加。这样就达到了统计每种水果的出现次数的效果,最后遍历输出即可。

总结

    本篇文章,我们讲解了STL中的另一对关联式容器map/multimap的使用方法与具体场景。之后博主会和大家一起,在实现红黑树的基础上尝试模拟实现set和map两种容器。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤
AI 代码解读
目录
打赏
0
73
73
0
133
分享
相关文章
【c++丨STL】set/multiset的使用
本文深入解析了STL中的`set`和`multiset`容器,二者均为关联式容器,底层基于红黑树实现。`set`支持唯一性元素存储并自动排序,适用于高效查找场景;`multiset`允许重复元素。两者均具备O(logN)的插入、删除与查找复杂度。文章详细介绍了构造函数、迭代器、容量接口、增删操作(如`insert`、`erase`)、查找统计(如`find`、`count`)及`multiset`特有的区间操作(如`lower_bound`、`upper_bound`、`equal_range`)。最后预告了`map`容器的学习,其作为键值对存储的关联式容器,同样基于红黑树,具有高效操作特性。
13 3
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
19天前
|
【c++丨STL】priority_queue(优先级队列)的使用与模拟实现
本文介绍了STL中的容器适配器`priority_queue`(优先级队列)。`priority_queue`根据严格的弱排序标准设计,确保其第一个元素始终是最大元素。它底层使用堆结构实现,支持大堆和小堆,默认为大堆。常用操作包括构造函数、`empty`、`size`、`top`、`push`、`pop`和`swap`等。我们还模拟实现了`priority_queue`,通过仿函数控制堆的类型,并调用封装容器的接口实现功能。最后,感谢大家的支持与关注。
55 1
|
2月前
|
【c++丨STL】stack和queue的使用及模拟实现
本文介绍了STL中的两个重要容器适配器:栈(stack)和队列(queue)。容器适配器是在已有容器基础上添加新特性或功能的结构,如栈基于顺序表或链表限制操作实现。文章详细讲解了stack和queue的主要成员函数(empty、size、top/front/back、push/pop、swap),并提供了使用示例和模拟实现代码。通过这些内容,读者可以更好地理解这两种数据结构的工作原理及其实现方法。最后,作者鼓励读者点赞支持。 总结:本文深入浅出地讲解了STL中stack和queue的使用方法及其模拟实现,帮助读者掌握这两种容器适配器的特性和应用场景。
68 21
深入浅出 C++ STL:解锁高效编程的秘密武器
C++ 标准模板库(STL)是现代 C++ 的核心部分之一,为开发者提供了丰富的预定义数据结构和算法,极大地提升了编程效率和代码的可读性。理解和掌握 STL 对于 C++ 开发者来说至关重要。以下是对 STL 的详细介绍,涵盖其基础知识、发展历史、核心组件、重要性和学习方法。
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
67 1
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
35 16
|
9天前
|
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
51 6
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等