0

0

C++STL multimap与map使用区别

P粉602998670

P粉602998670

发布时间:2025-09-16 08:18:01

|

1050人浏览过

|

来源于php中文网

原创

std::map要求键唯一,每个键仅映射一个值,支持operator[];std::multimap允许键重复,可存储多个相同键的键值对,不支持operator[],需用equal_range访问所有值。

c++stl multimap与map使用区别

C++ STL中的

std::multimap
std::map
,它们最核心的区别在于对键(key)的唯一性处理:
std::map
坚持键的唯一性,每个键只能映射到一个值;而
std::multimap
则允许同一个键关联多个不同的值。这个看似简单的差异,却深刻地决定了它们在不同场景下的适用性与内部行为。选择哪一个,往往取决于你的数据模型是否天然允许或需要重复的键。

解决方案

在我看来,理解

std::map
std::multimap
的使用区别,首先要从它们各自的设计哲学说起。
std::map
就像一本严谨的字典,每个词条(键)都只对应一个唯一的解释(值)。如果你尝试给一个已经存在的词条添加新的解释,它通常会忽略你的新尝试(
insert
方法),或者直接用新的解释覆盖旧的(
operator[]
)。这种行为非常适合那些一对一映射的数据结构,比如用户ID到用户资料、商品SKU到商品详情等等,确保了数据的一致性和唯一性。

std::multimap
则更像一本多义词词典,同一个词条可以有多个不同的解释,并且这些解释都会被完整地保留下来。当你用一个已存在的键插入新的值时,它不会覆盖旧值,而是简单地添加一个新的键值对。这对于那些一对多关系的数据模型简直是天作之合,例如一个事件时间戳可能对应多个日志条目,或者一个关键词可能指向多篇文章。

它们两者在底层实现上,通常都基于自平衡二叉搜索树(比如红黑树),这保证了插入、删除、查找等操作的对数时间复杂度

O(logN)
。但是,由于对键唯一性的不同处理,它们在接口和实际使用中存在一些关键差异:

立即学习C++免费学习笔记(深入)”;

  1. operator[]
    的可用性:

    • std::map
      提供了
      operator[]
      ,你可以通过
      myMap[key] = value;
      直接访问或修改与键关联的值。如果键不存在,
      operator[]
      会自动插入一个默认构造的新元素,这非常方便。
    • std::multimap
      不提供
      operator[]
      。原因很简单:如果一个键有多个值,
      operator[]
      应该返回哪一个呢?这种歧义使得它无法提供这样的接口。所以,你不能直接通过
      myMultimap[key]
      来获取或设置值。
  2. 插入行为:

    • std::map
      insert({key, value})
      方法在键已存在时不会执行插入操作。如果你想强制更新,需要使用
      myMap[key] = newValue;
      或者
      myMap.at(key) = newValue;
    • std::multimap
      insert({key, value})
      方法总是会插入新的键值对,即使键已经存在。它不会覆盖任何旧值。
  3. 查找多个值:

    • 对于
      std::map
      find(key)
      返回的迭代器直接指向唯一的匹配项(如果存在)。
    • 对于
      std::multimap
      ,当你需要查找所有与某个键关联的值时,你通常会使用
      equal_range(key)
      。这个方法会返回一对迭代器,分别指向第一个匹配项和最后一个匹配项的下一个位置,你可以通过遍历这个范围来获取所有关联的值。当然,
      lower_bound(key)
      upper_bound(key)
      也可以实现类似的功能。

这是一个简单的代码示例,展示了它们的核心行为差异:

#include 
#include 
#include 
#include 

int main() {
    // std::map 示例
    std::map myMap;
    myMap.insert({1, "Apple"});
    myMap.insert({2, "Banana"});
    myMap.insert({1, "Orange"}); // 键1已存在,此插入操作会被忽略

    std::cout << "std::map 键1的值 (首次插入): " << myMap[1] << std::endl; // 输出: Apple

    myMap[1] = "Grape"; // 使用 operator[] 覆盖键1的值
    std::cout << "std::map 键1的值 (operator[] 覆盖后): " << myMap[1] << std::endl; // 输出: Grape

    std::cout << "---------------------------------" << std::endl;

    // std::multimap 示例
    std::multimap myMultimap;
    myMultimap.insert({1, "Red"});
    myMultimap.insert({2, "Green"});
    myMultimap.insert({1, "Blue"});  // 键1已存在,但会插入新元素
    myMultimap.insert({1, "Yellow"}); // 键1已存在,再次插入新元素

    std::cout << "std::multimap 键1的所有值:" << std::endl;
    auto range = myMultimap.equal_range(1);
    for (auto it = range.first; it != range.second; ++it) {
        std::cout << "  " << it->second << std::endl;
    }
    // 预期输出:
    //   Red
    //   Blue
    //   Yellow (顺序可能因插入顺序和底层实现而异,但都会被保留)

    // 尝试在 multimap 上使用 operator[] 会导致编译错误
    // myMultimap[1] = "Purple"; // 错误: no operator "[]" matches these operands

    return 0;
}

这个例子清楚地展示了

map
如何通过
operator[]
覆盖值,以及
multimap
如何保留所有重复键的值,并需要通过
equal_range
来访问它们。这是我个人在实际开发中经常需要提醒自己和同事的地方,尤其是在从
map
切换到
multimap
时,
operator[]
的缺失是一个常见的“坑”。

什么时候我们应该优先选择
std::map
而不是
std::multimap

选择

std::map
的场景通常非常明确:当你的数据模型中,每个键都必须是唯一的,并且你只关心与这个键关联的“唯一”值时。

知识吐司
知识吐司

专注K12教育的AI知识漫画生成工具

下载
  1. 数据天然具有唯一性约束: 比如,你正在管理一个用户数据库,每个用户都有一个唯一的ID。那么
    std::map
    就是理想的选择。你不会希望同一个用户ID对应多份不同的用户资料,那会引发数据混乱。
  2. 需要快速、直接的键值访问:
    std::map
    提供的
    operator[]
    语法糖非常便捷。当你确信键存在或希望在键不存在时自动创建并初始化一个默认值时,它能让你的代码简洁高效。例如,统计词频时
    wordCounts[word]++;
    这种写法就非常直观。
  3. 防止意外的重复键: 如果你的业务逻辑要求键是唯一的,使用
    std::map
    可以天然地强制这一约束。尝试插入重复键的操作会被忽略(
    insert
    ),或者覆盖旧值(
    operator[]
    ),这有助于避免数据错误。
  4. 内存效率: 在每个键只对应一个值的情况下,
    std::map
    通常会比
    std::multimap
    稍微节省一些内存。因为
    multimap
    的节点设计可能需要考虑处理多个值的情况,或者简单来说,如果你只存一个值,
    multimap
    也会用一个完整的节点来存,而
    map
    也是一个节点,但
    multimap
    会为每个重复的键值对都创建一个节点,而
    map
    只为唯一的键创建一个节点。当然,这在现代系统中通常不是决定性因素,但值得一提。

简单来说,如果你的数据是“一对一”的关系,并且你重视键的唯一性和

operator[]
的便捷性,那么
std::map
几乎总是你的首选。

std::multimap
在实际项目中常见的应用场景有哪些?

std::multimap
的价值在于它能够优雅地处理“一对多”的关系,即同一个键可以关联多个值。在我的项目经验中,有几个场景是
std::multimap
的亮点:

  1. 事件日志或时间序列数据: 想象一个系统需要记录在特定时间点发生的所有事件。同一个时间戳(键)可能对应多个不同的日志条目(值)。
    std::multimap
    可以很好地组织这些数据,并且由于其内部排序,你可以按时间顺序轻松遍历所有事件。
  2. 多对多关系建模: 在没有数据库的轻量级应用中,或者在需要内存缓存时,
    multimap
    可以用来表示多对多关系。例如,一个学生可以选修多门课程,一门课程也可以被多个学生选修。你可以用
    std::multimap
    来存储学生选课信息,或者
    std::multimap
    来存储课程被哪些学生选修。
  3. 索引系统: 构建一个简单的内存搜索引擎时,一个关键词(键)可能出现在多篇文章或文档(值)中。
    std::multimap
    就能有效地建立倒排索引,快速查找包含某个关键词的所有文档。
  4. 图数据结构: 在表示图的邻接列表时,一个节点(键)可能连接到多个邻居节点(值)。
    std::multimap
    可以用来存储图的边信息,并且由于键的有序性,可以方便地按节点ID访问其所有邻居。
  5. 自定义排序与分组: 当你需要根据某个属性(键)对一组对象进行分组,并且这些对象可能共享相同的属性值时,
    multimap
    非常有用。它会自动根据键对所有元素进行排序,让你能轻松地迭代属于同一组的所有元素。
  6. 配置管理: 有些配置文件允许同一个配置项(键)被定义多次,每次定义都带有不同的上下文或值,并且这些定义都需要被保留和处理。
    std::multimap
    可以完美地处理这种需求。

这些场景的核心共同点是:数据的逻辑模型允许或要求键的重复性,并且你需要能够方便地访问与某个键关联的所有值。

从性能角度看,
multimap
map
在操作复杂度上有什么异同?

从理论的算法复杂度角度来看,

std::multimap
std::map
在核心操作上表现出高度的一致性,因为它们都基于相同的底层数据结构——平衡二叉搜索树(通常是红黑树)。

  1. 插入 (Insert) 操作:

    • std::map::insert()
      :
      O(logN)
      。如果键已存在,插入失败(不修改现有元素)。
    • std::map::operator[]
      :
      O(logN)
      。如果键不存在则插入,如果键存在则修改。
    • std::multimap::insert()
      :
      O(logN)
      。总是插入新元素,即使键已存在。 从单次插入的角度看,两者的效率是相同的。
  2. 查找 (Find) 操作:

    • std::map::find(key)
      :
      O(logN)
      。返回一个迭代器指向唯一的匹配项。
    • std::multimap::find(key)
      :
      O(logN)
      。返回一个迭代器指向第一个匹配项。
    • std::multimap::equal_range(key)
      : 也是
      O(logN)
      来找到范围的边界。但如果你需要遍历所有
      k
      个与键关联的值,那么总时间复杂度是
      O(logN + k)
      。这里的
      k
      是与该键关联的元素数量。
  3. 删除 (Erase) 操作:

    • std::map::erase(key)
      :
      O(logN)
      。删除与键匹配的唯一元素。
    • std::multimap::erase(key)
      : 删除所有与键匹配的元素。这实际上是先找到第一个匹配项,然后遍历并删除所有匹配项。因此,其复杂度是
      O(logN + k)
      ,其中
      k
      是被删除元素的数量。
    • erase(iterator)
      : 对于两者,删除指定迭代器指向的元素,平均是
      O(1)
      (摊销),最坏情况
      O(logN)
      (取决于树的重新平衡)。
  4. 遍历 (Iteration) 操作:

    • 对于两者,从头到尾遍历整个容器都是
      O(N)
      ,其中
      N
      是容器中的元素总数。因为它们都维护了元素的有序性。

异同点总结:

  • 核心复杂度相同: 大多数基本操作的理论时间复杂度都是
    O(logN)
    ,这得益于底层平衡二叉搜索树的效率。
  • 处理重复键的开销:
    multimap
    在处理重复键时,例如查找或删除一个键的所有关联值,其复杂度会额外包含一个
    +k
    的项,其中
    k
    是与该键关联的元素数量。这意味着如果一个键有非常多的重复值,那么处理这个键的成本会更高。
  • 内存开销:
    multimap
    由于为每个键值对都创建一个独立的节点,如果存在大量重复键,它的内存开销可能会比
    map>
    这种结构更大。后者为每个键只存储一个
    vector
    对象,即使
    vector
    中包含很多值,其内部管理方式可能更紧凑。不过,
    multimap
    的优势在于它自动维护了所有元素的排序,且单个元素的插入/删除通常更高效。
  • operator[]
    的缺失:
    multimap
    缺乏
    operator[]
    导致不能进行常量时间或摊销常量时间的“存在即访问,不存在即插入”的便捷操作。这在某些场景下可能会让代码显得稍微冗长,需要使用
    find
    insert
    结合迭代器来处理。

总的来说

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1496

2023.10.24

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

536

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

17

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

24

2026.01.06

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1072

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

148

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1074

2025.12.29

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

13

2026.01.19

c++ 根号
c++ 根号

本专题整合了c++根号相关教程,阅读专题下面的文章了解更多详细内容。

70

2026.01.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
HTML5/CSS3/JavaScript/ES6入门课程
HTML5/CSS3/JavaScript/ES6入门课程

共102课时 | 6.8万人学习

前端基础到实战(HTML5+CSS3+ES6+NPM)
前端基础到实战(HTML5+CSS3+ES6+NPM)

共162课时 | 19万人学习

第二十二期_前端开发
第二十二期_前端开发

共119课时 | 12.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号