C++中的STL中map用法详解(零基础入门)

前言


std::map是 C++ 标准库的关联容器,保存「键—值」对,按键有序排列,且每个键唯一。它的典型实现是平衡二叉搜索树(多为红黑树),因此查找、插入、删除都是对数复杂度。对零基础读者来说,最重要的一点是:map不是哈希表,它的迭代顺序按键的排序规则确定,而不是插入顺序,也不是哈希顺序。


初学者最容易踩的坑是operator[]。m["missing"]在键不存在时会插入一个默认构造的值,并返回它的引用。很多「只想读一下」的代码因此悄悄改变了容器大小,甚至让后续的遍历多出一个空项。另一个误解是「map比unordered_map慢所以不该用」——有序性本身是功能,需要按序遍历、范围查询、稳定输出时,map的排序正是你要的。


本文从类型定义讲起,覆盖插入、访问、遍历、删除、查找与自定义比较器,最后给出与unordered_map的选择建议。示例以 C++17 为基准,C++20 的新成员会单独标注。


一、类型与基本性质


使用map需要包含<map>。它的模板参数有四个,通常只写前两个:


// 适用于 C++17
#include <map>
#include <string>

std::map<std::string, int> score;

完整形式是std::map<Key, T, Compare, Allocator>,其中Compare默认是std::less<Key>,Allocator一般不用管。map的元素类型是std::pair<const Key, T>:键是const,值可以修改。这决定了迭代器解引用得到的first不能赋值——it->first = "x"会编译失败,因为改键会破坏树的排序结构。


几个基本性质需要记住:




性质说明



键唯一重复插入同一个键不会产生第二个元素

有序按Compare决定的顺序排列,默认升序

节点稳定插入、删除其它元素不会使指向已有元素的迭代器失效(被删的除外)

键不可改value_type是pair<const Key, T>

对数复杂度查找、插入、删除均为 O(log n)



map的迭代器是双向迭代器,不支持随机访问,不能写m.begin() + 3。遍历顺序由比较器决定;对std::string键,默认是字典序,大写字母排在小写之前(按字符编码比较),这一点在做大小写不敏感统计时要注意。


二、插入与访问


插入有几种写法,语义各不相同。insert接受value_type或迭代器区间,返回std::pair<iterator, bool>:second表示是否真的插入,first指向该键对应的元素(无论新插入还是已存在)。用结构化绑定可以一次取出:


// 适用于 C++17
#include <iostream>
#include <map>
#include <string>

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

auto r1 = stock.insert({"apple", 10});
std::cout << std::boolalpha << r1.second << '\n'; // true

auto r2 = stock.insert({"apple", 99});
std::cout << r2.second << '\n'; // false,键已存在
std::cout << r2.first->second << '\n'; // 10,保留原值
return 0;
}

emplace把参数转发给value_type的构造函数,语义上也是「不存在才插入」。C++17 新增的try_emplace(key, args...)更精确:只有当键不存在时,才用args构造值;键已存在时不会构造临时对象。insert_or_assign(key, value)则是「存在就赋值,不存在就插入」,适合「更新或新增」的语义。


operator[]的行为要单独强调:它要求T可默认构造;键不存在时先插入一个值初始化的T,再返回引用。因此它既能读也能写,但不能用于纯查询。C++11 起还有at(key):键不存在时抛std::out_of_range,不会插入;at有const版本,operator[]没有,所以const std::map只能用at或find访问。


// 适用于 C++17
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>

int main() {
std::map<std::string, int> m;
m["apple"] = 1; // 插入并赋值
m["apple"] = 2; // 已存在,直接赋值
std::cout << m.size() << '\n'; // 1

int x = m["banana"]; // 插入 banana -> 0,再返回
std::cout << m.size() << '\n'; // 2

try {
std::cout << m.at("cherry") << '\n'; // 抛 out_of_range
} catch (const std::out_of_range& e) {
std::cout << "no such key: " << e.what() << '\n';
}

auto it = m.find("apple");
if (it != m.end()) {
std::cout << it->second << '\n';
}
return 0;
}

如果只是想判断键是否存在,用find(C++20 起也可以用contains),不要用operator[]。count(key)返回 0 或 1,可读性也不错,但对map来说find更直接。




操作键不存在时键存在时返回



operator[]插入默认值返回引用T&

at抛std::out_of_range返回引用T&/const T&

find返回end()返回迭代器iterator

insert插入不修改pair<iterator, bool>

try_emplace构造并插入不构造、不修改pair<iterator, bool>

insert_or_assign插入赋值pair<iterator, bool>



三、遍历、删除与查找


遍历map得到的是pair<const Key, T>。C++17 的结构化绑定让代码更清晰:


// 适用于 C++17
#include <iostream>
#include <map>
#include <string>

int main() {
std::map<std::string, int> counts{{"apple", 3}, {"banana", 5}};

for (const auto& [word, count] : counts) {
std::cout << word << " = " << count << '\n';
}

for (auto& kv : counts) {
kv.second += 1; // 值可以改,键不能改
}
return 0;
}

按 C++17 之前的写法是for (const auto& kv : counts),再用kv.first、kv.second。注意结构化绑定默认拷贝还是引用要看写法:const auto& [k, v]是只读引用,auto [k, v]会拷贝一份pair,修改v不会影响容器。


删除有三种形式。erase(key)删除指定键,返回删除的元素个数(对map是 0 或 1);erase(iterator)删除迭代器指向的元素,C++11 起返回下一个元素的迭代器;erase(first, last)删除区间。遍历中删除要用返回值更新迭代器:


// 适用于 C++17
#include <map>
#include <string>

int main() {
std::map<std::string, int> m{{"a", 1}, {"b", 2}, {"c", 3}};

for (auto it = m.begin(); it != m.end(); ) {
if (it->second < 2) {
it = m.erase(it); // 用返回值继续
} else {
++it;
}
}
return 0;
}

不要写成m.erase(it); ++it;,因为it已经失效。map的删除只会使指向被删元素的迭代器失效,其它迭代器仍然有效,这是它与vector的重要区别。


查找相关成员包括find、count、lower_bound、upper_bound、equal_range。lower_bound(key)返回第一个不小于key的元素,upper_bound(key)返回第一个大于key的元素;equal_range返回这两者组成的区间。对map而言,因为键唯一,equal_range的区间最多包含一个元素。这些接口在「找最接近的键」「按范围遍历」时很有用,而且是对数复杂度,不需要手动扫描。


// 适用于 C++17
#include <iostream>
#include <map>
#include <string>

int main() {
std::map<int, std::string> m{{10, "ten"}, {20, "twenty"}, {30, "thirty"}};

auto it = m.lower_bound(15); // 指向 20
if (it != m.end()) {
std::cout << it->first << ' ' << it->second << '\n';
}

auto range = m.equal_range(20);
for (auto p = range.first; p != range.second; ++p) {
std::cout << p->first << ' ' << p->second << '\n';
}
return 0;
}

四、自定义比较器与容器选择


map的排序规则由第三个模板参数决定。自定义比较器必须满足严格弱序(strict weak ordering):对任意元素,comp(a, a)必须为假,且传递性成立。写成return a <= b;是典型错误——它让相等元素也「小于」,破坏严格弱序,标准规定行为未定义。正确写法是用<,相等时返回 false。


// 适用于 C++17
#include <iostream>
#include <map>
#include <string>

struct ByLength {
bool operator()(const std::string& a, const std::string& b) const {
if (a.size() != b.size()) {
return a.size() < b.size();
}
return a < b; // 长度相同再按字典序,保证严格弱序
}
};

int main() {
std::map<std::string, int, ByLength> m;
m["bb"] = 2;
m["a"] = 1;
m["ccc"] = 3;

for (const auto& kv : m) {
std::cout << kv.first << ' ';
}
std::cout << '\n';
return 0;
}

比较器也可以写成 lambda,但需要在构造时传入类型(C++20 之前 lambda 的类型不能直接作为模板参数写出,通常用decltype或函数对象)。C++20 起 lambda 可以在未求值语境中使用,写法更方便,但可移植性要看编译器支持。


map与unordered_map的选择取决于需求:




维度std::mapstd::unordered_map



底层结构平衡二叉搜索树哈希表

元素顺序按键排序不保证

查找/插入/删除O(log n)平均 O(1),最坏 O(n)

范围查询支持lower_bound等不支持

键的要求可比较(严格弱序)可哈希且可相等比较

迭代器稳定性插入删除不影响其它元素扩容(rehash)使迭代器失效

适用场景需要有序、范围查询、稳定遍历只做精确查找、追求平均性能



如果键类型没有现成的std::hash特化,unordered_map还需要自己提供哈希函数;map只需要<。对小型容器,map的常数开销可能比哈希表更小,但具体差异要在实际数据上测量,不能凭印象下结论。


常见坑点


❌ 用m[key]判断键是否存在。 ✅ 用find或 C++20 的contains;operator[]会插入默认值。


❌ 在const std::map上用operator[]读取。 ✅ 用at()或find();operator[]不是const成员。


❌ 通过迭代器修改it->first。 ✅ 键是const,不能改;需要改键就删除旧元素再插入新键。


❌ 自定义比较器写成a <= b。 ✅ 用严格弱序,相等时返回 false;通常写a < b并在需要时补充次级比较。


❌m.erase(it); ++it;。 ✅it = m.erase(it);,删除分支不再自增。


❌ 认为insert会覆盖已存在的值。 ✅insert不覆盖;要覆盖用insert_or_assign或m[key] = value。


❌ 用map保存对象地址作为键,期待按对象内容排序。 ✅ 默认比较的是指针地址;需要按内容排序就自定义比较器,或改用值类型作键。


❌ 依赖unordered_map的遍历顺序做输出。 ✅ 需要稳定顺序时排序后再输出,或直接用map。


总结




主题关键结论



本质有序、键唯一的关联容器,典型为平衡树

元素类型std::pair<const Key, T>,键不可修改

访问operator[]会插入;纯查询用find/at/contains

插入insert不覆盖;try_emplace延迟构造;insert_or_assign覆盖

删除erase返回迭代器,遍历删除要用返回值更新

比较器必须严格弱序,不能写<=

选择需要顺序与范围查询用map,只需精确查找可考虑unordered_map



map的难点不在接口数量,而在几个语义细节:operator[]会插入、键是常量、比较器必须严格弱序、删除要使迭代器更新。把这几条记牢,再根据「是否需要有序」在map与unordered_map之间做选择,日常使用就不会出大问题。




相关新闻

计算机视觉新手入门:27个实操友好型开源项目地图

计算机视觉新手入门:27个实操友好型开源项目地图

1. 这份整理不是“资料包”&#xff0c;而是一张计算机视觉新手的生存地图你点开这个标题&#xff0c;大概率正站在一个熟悉的路口&#xff1a;刚学完Python基础&#xff0c;啃完《深度学习入门》&#xff0c;甚至能手写个简单的CNN分类器——但一打开GitHub&#xff0c;面对成…

2026/10/20 12:36:24 阅读更多 →
碰碰好评卡业务功能实现全流程实战指南 附核心逻辑与避坑技巧

碰碰好评卡业务功能实现全流程实战指南 附核心逻辑与避坑技巧

碰碰好评卡业务功能实现全流程实战指南 附核心逻辑与避坑技巧 在电商、本地生活等业态中&#xff0c;好评率是影响商品转化、商家流量权重的核心指标&#xff0c;碰碰好评卡作为连接商家、消费者、平台的轻量化评价激励工具&#xff0c;通过「扫码提交好评-核验发放权益」的极简…

2026/10/20 12:36:24 阅读更多 →
ECC引擎拆解:Hooks与持久记忆如何终结复制粘贴开发

ECC引擎拆解:Hooks与持久记忆如何终结复制粘贴开发

复制粘贴式开发&#xff0c;大概是这几年程序员被吐槽最多的习惯。需求一来&#xff0c;先翻旧项目&#xff0c;找到差不多的代码&#xff0c;复制过来改改名&#xff0c;再修一两个报错&#xff0c;一天就这么过去了。我最近一直在玩一个叫ECC的AI编程引擎&#xff0c;它最打动…

2026/10/20 12:36:24 阅读更多 →

最新新闻

计算机网络传输层复习:TCP/UDP、三次握手与拥塞控制核心考点全解析

计算机网络传输层复习:TCP/UDP、三次握手与拥塞控制核心考点全解析

期末复习最怕什么&#xff1f;最怕翻开课本的时候发现第五章已经只剩一个标题&#xff0c;脑子里全是上课时似懂非懂的名词。计算机网络这门课有个特点&#xff1a;前几章物理层、数据链路层讲的是“链路级”的东西&#xff0c;到第五章一上来就是端到端的传输层&#xff0c;视…

2026/10/21 1:45:30 阅读更多 →
GD32H759 RT-Thread USB CDC ACM虚拟串口配置与避坑指南

GD32H759 RT-Thread USB CDC ACM虚拟串口配置与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 1:45:30 阅读更多 →
AI硬件周报:面向工程落地的选型决策地图

AI硬件周报:面向工程落地的选型决策地图

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 1:45:30 阅读更多 →
虚拟机个人免费版避坑指南:安装、网络、共享与性能调优

虚拟机个人免费版避坑指南:安装、网络、共享与性能调优

1. 为什么虚拟机软件的选择会成为一个问题很多人第一次接触虚拟机&#xff0c;都是因为一个很朴素的需求&#xff1a;想在主力电脑上跑一个完全隔离的系统环境&#xff0c;用来做测试、跑一些不太放心的软件&#xff0c;或者单纯想体验一下别的操作系统。市面上能选的方案其实不…

2026/10/21 1:45:30 阅读更多 →
Docker根分区告警?LVM在线扩容实战与避坑指南

Docker根分区告警?LVM在线扩容实战与避坑指南

1. 磁盘告警背后的真相&#xff1a;为什么容器环境总先撑爆根分区凌晨两点&#xff0c;监控面板上那台跑了十几个容器的宿主机突然飘红&#xff0c;/dev/mapper/centos-root使用率冲到 98%&#xff0c;Docker 日志写入直接卡死&#xff0c;几个业务容器开始反复重启。这种场景我…

2026/10/21 1:45:30 阅读更多 →
不登录、不注册,如何免费完整破解所有国际版 Unity?UniHacker 快速上手指南

不登录、不注册,如何免费完整破解所有国际版 Unity?UniHacker 快速上手指南

不登录、不注册&#xff0c;如何免费完整破解所有国际版 Unity&#xff1f;UniHacker 快速上手指南 【免费下载链接】UniHacker 为Windows、MacOS、Linux和Docker修补所有版本的Unity3D和UnityHub 项目地址: https://gitcode.com/GitHub_Trending/un/UniHacker UniHacke…

2026/10/21 1:44:29 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →