需包含头文件<map>,声明如std::map<std::string, int> scores;,初始化支持列表初始化,但重复键会静默覆盖;查值优先用find()或at()避免operator[]隐式插入;遍历时删除须用erase返回值更新迭代器;性能敏感且无需排序时可选unordered_map。

怎么声明和初始化一个 std::map
直接用 std::map 模板,指定键和值的类型,别漏掉头文件 <map>。它默认按键升序排序,不是插入顺序——这点常被当成“乱序”误判。
常见错误:用 map<int, string> m = {{1,"a"}, {2,"b"}}; 初始化没问题,但若写成 map<string, int> m = {{"a", 1}, {"a", 2}};,第二个会静默覆盖第一个,不报错也不警告。
-
std::map<std::string, int> scores;—— 最常用写法 -
std::map<int, std::vector<double>> data;—— 值可以是任意类型,包括容器 - 避免用自定义类作键,除非你重载了
operator<或提供比较仿函数
查不到值?小心 operator[] 的副作用
operator[] 看似方便,但它会在键不存在时**默认构造一个值并插入**,比如 int 变成 0,std::string 变成空串。这不是“查询”,是“查询+可能插入”。
真正只查不改,用 find() 或 at():find() 返回迭代器,查不到就是 m.end();at() 查不到直接抛 std::out_of_range 异常。
立即学习“C++免费学习笔记(深入)”;
- 想安全读取:用
auto it = m.find("key"); if (it != m.end()) use(it->second); - 想带异常检查:用
m.at("key"),适合确定键一定存在时 - 别写
if (m["key"] != 0) ...——这行代码已经悄悄插入了一个0
遍历 std::map 时修改元素会怎样
不能在 for (auto& p : m) 循环里调用 m.erase(p.first),会触发未定义行为(迭代器失效)。std::map 的迭代器在删除当前元素后立即失效,不像 vector 那样还能继续。
正确做法是用 erase() 的返回值接住下一个有效迭代器,或者先收集待删 key 再批量删。
- 安全删除:
for (auto it = m.begin(); it != m.end(); ) { if (need_erase(it)) it = m.erase(it); else ++it; } - 批量删除:
std::vector<std::string> to_del; for (const auto& p : m) if (cond(p)) to_del.push_back(p.first); for (const auto& k : to_del) m.erase(k); - 千万别用
range-based for+erase混搭,编译不报错,运行大概率崩
性能敏感场景下,std::map 和 std::unordered_map 怎么选
std::map 是红黑树,O(log n) 插入/查找,键有序;std::unordered_map 是哈希表,平均 O(1),但最坏退化到 O(n),且键无序、不支持范围查询(比如找所有大于 5 的 key)。
如果你只需要“给 key 拿 value”,不在乎顺序,又常做大量随机访问,优先考虑 unordered_map。但注意:自定义类型作 key 时,必须提供 hash 和 ==,比 map 的 operator< 更麻烦。
- 需要按键排序、或做
lower_bound/upper_bound:只能用std::map - 频繁增删查,key 是
int/string,且不关心顺序:std::unordered_map更快 - 内存紧张时,
unordered_map通常更占空间(哈希桶开销),map更紧凑
键的比较逻辑、迭代器失效规则、以及 operator[] 的隐式插入,这三个点一旦记混,bug 就藏得深又难复现。







