C++ map 极简入门(初学者版)

提示:不喜勿喷


一、map 是什么?

你可以把 map 想象成一本字典:

  • 字典里有"词"和"解释"。
  • 给一个"词",就能查到它的"解释"。

在 C++ 里:

  • "词"叫 键(key)
  • "解释"叫 值(value)

map 就是用来存放一堆"键值对"的容器。它的特点是:键不能重复,而且会自动按 key 从小到大排序。


二、使用前准备

#include <iostream>
#include <map>
#include <string>
using namespace std;
  • map 在 <map> 头文件里。
  • 如果键是字符串,需要 <string>。

声明一个 map:

map<string, int> m;   // 键是 string,值是 int

比如:名字 → 分数。


三、最基本操作

1. 插入和修改

map<string, int> m;

m["apple"] = 3;    // 插入:键 "apple",值 3
m["banana"] = 5;   // 插入:键 "banana",值 5
m["apple"] = 10;   // 修改:把 "apple" 的值改成 10
  • 如果键不存在,m["键"] = 值; 会插入一个新元素。
  • 如果键已存在,m["键"] = 值; 会覆盖原来的值。

2. 读取值

cout << m["apple"] << endl;   // 输出 10

⚠️ 注意:如果 "apple" 不存在,m["apple"] 会自动插入一个默认值(int 默认是 0),然后返回 0。

3. 判断键是否存在

用 count:

if (m.count("apple") > 0) {
    cout << "apple 存在" << endl;
} else {
    cout << "apple 不存在" << endl;
}

count 返回 0 或 1(因为 map 的键不会重复)。返回 1 表示存在,0 表示不存在。

count 不会插入任何东西,也不会修改 map,是最安全的查询方式。

4. 删除元素

m.erase("apple");   // 删除键为 "apple" 的元素

5. 其他常用操作

m.size();    // 元素个数
m.empty();   // 是否为空,空返回 true
m.clear();   // 清空所有元素

四、专门说清楚:能不能用 m["apple"] == 0 判断"不存在"?

这是初学者最容易踩的坑,必须分开讲。结论不是"永远不行",而是要看你的值会不会是 0,以及你在不在乎 map 被悄悄插入元素。

情况一:可以用(但有副作用)

前提:你确定值永远不会是 0。

比如统计单词出现次数:

map<string, int> count;
count["apple"]++;   // 计数只会是 1、2、3……不可能是 0

这种情况下,count["apple"] == 0 确实能表示"还没出现过",因为出现过的词次数一定大于 0。

但是! 即使这样写结果对,count["apple"] == 0 这个判断本身已经把 "apple" 插入进去了,只是值恰好是 0:

map<string, int> m;
if (m["apple"] == 0) {   // 虽然判断成立,但 m 里已经多了一个 "apple"
    cout << "不存在" << endl;
}
cout << m.size();        // 输出 1,而不是 0

如果你后面还要用 m.size(),或者要遍历,就会看到一个值为 0 的幽灵元素。

所以严格来说:只有在"值不会为 0,且你不在乎 map 被插入"时,这种写法才安全。

情况二:绝对不能用

前提:值本身可能是 0。

比如用 map 记录学生成绩,0 分是完全可能的:

map<string, int> score;
score["小明"] = 0;   // 小明考了 0 分

if (score["小明"] == 0) {
    cout << "小明不存在" << endl;   // ❌ 错误!小明明明存在
}

这里你根本分不清"小明不存在"和"小明考了 0 分"。

再比如记录库存数量:

map<string, int> stock;
stock["苹果"] = 0;   // 苹果卖完了,库存为 0

if (stock["苹果"] == 0) {
    cout << "没有这种商品" << endl;   // ❌ 错误!商品存在,只是库存为 0
}

只要值可能取到 0,就绝对不能用 == 0 判断存在性。

情况三:任何时候都安全的写法

不管你属于哪种情况,只要你想纯粹地"查询",都应该用 count:

if (m.count("apple") > 0) {
    // apple 存在
} else {
    // apple 不存在,而且 m 的大小没变
}

一句话总结

场景 能不能用 m["apple"] == 0
值永远不可能是 0,且你不在乎 map 被插入 可以,但要知道有副作用
值可能是 0(分数、库存、温度……) 绝对不行
任何时候想安全地"只查询" 用 m.count("apple") > 0

建议初学者养成习惯:判断存在性一律用 count,不要用 []。


五、遍历 map(按顺序输出所有键值对)

map 不能像数组那样用下标 m[0] 访问。需要用迭代器。

迭代器你可以理解为"一个指向 map 里某个元素的游标"。

固定写法:

map<string, int>::iterator it;   // 声明一个迭代器

for (it = m.begin(); it != m.end(); it++) {
    cout << it->first << " -> " << it->second << endl;
}

解释:

  • m.begin():指向第一个元素。
  • m.end():指向最后一个元素的下一个位置(不是最后一个元素)。
  • it != m.end():只要没到末尾就继续。
  • it++:移动到下一个元素。
  • it->first:当前元素的键。
  • it->second:当前元素的值。

完整例子:

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

int main() {
    map<string, int> m;
    m["apple"] = 3;
    m["banana"] = 5;
    m["cherry"] = 2;

    map<string, int>::iterator it;
    for (it = m.begin(); it != m.end(); it++) {
        cout << it->first << " -> " << it->second << endl;
    }

    return 0;
}

输出:

apple -> 3
banana -> 5
cherry -> 2

你会发现:不管插入顺序如何,输出总是按 key 从小到大排列。 这就是 map 的自动排序。


六、完整小例子:统计单词出现次数

这是 map 最经典的练习。

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

int main() {
    map<string, int> count;
    string word;

    // 不断读入单词,直到输入结束(Windows 按 Ctrl+Z 再回车)
    while (cin >> word) {
        count[word]++;   // 如果 word 不存在,先插入 0,再 +1
    }

    map<string, int>::iterator it;
    for (it = count.begin(); it != count.end(); it++) {
        cout << it->first << " 出现了 " << it->second << " 次" << endl;
    }

    return 0;
}

输入:

apple banana apple orange banana apple

输出:

apple 出现了 3 次
banana 出现了 2 次
orange 出现了 1 次

这里 count[word]++ 非常巧妙:

  • 如果 word 第一次出现,count[word] 会自动变成 0,然后 ++ 变成 1。
  • 如果已经出现过,就直接把原来的次数加 1。

注意:这里能用 count[word]++,正是因为"计数不可能是 0"这个特点。如果换成"学生成绩"这种值可能为 0 的场景,就不能随便用 [] 了。


七、初学者必须记住的坑

  1. 判断"存在性"不要用 []

    if (m["apple"] == 0) { ... }   // 危险!可能插入元素,且值本来就可能是 0
    

    正确做法:

    if (m.count("apple") > 0) { ... }
    

    详情见第四节的三种情况。

  2. m["键"] = 值; 会覆盖旧值

    如果只想在不存在时插入,可以用 insert,但初学阶段先掌握 [] 就够了。

  3. 遍历时不要修改键

    键是只读的,不能通过迭代器修改 it->first。

  4. 删除元素后,原来的迭代器可能失效

    初学阶段先避免在遍历中删除,或者删除后重新开始遍历。


八、练习建议

  1. 输入几个整数,统计每个数出现了几次。
  2. 输入几个学生的名字和成绩,然后按名字从小到大输出。
  3. 输入一个字符串,统计每个字符出现的次数。
  4. 用 map 实现一个简单的电话簿:名字 → 电话号码,支持添加、查找、删除。
  5. 试一下:把"统计单词次数"里的 count["apple"] 改成用 count 先判断,再输出,体会两种写法的区别。

九、总结

  • map 存放键值对,键唯一,自动按 key 排序。

  • 用 m["键"] = 值; 插入或修改。

  • 用 m["键"] 读取值,但键不存在时会插入默认值。

  • 判断存在性用 m.count("键") > 0。

    • 只有"值不可能为 0"且"不在乎被插入"时,才可以用 m["键"] == 0。
  • 删除用 m.erase("键")。

  • 遍历用迭代器:

    map<string, int>::iterator it;
    for (it = m.begin(); it != m.end(); it++) {
        cout << it->first << " " << it->second << endl;
    }
    
  • 底层是红黑树,查找、插入、删除都很快(O(log n))。

把上面的单词统计例子亲手敲一遍,你就基本学会 map 了。

0 条评论

目前还没有评论...