map 与 set:键值对与去重
这是什么:map 是"键值对"容器(按名字找东西),set 是自动去重的集合——C++ STL 里最常用的两件查值利器。
为什么用:vector 按位置存东西,可生活中更多是"按名字找":按学号查成绩、按名字查电话。这就轮到 map(C++ 版的 Python 字典),而重复的名字则交给 set 自动去重。
如何用:先用一个 std::map<string, int> 演示"键不存在就新增、键已存在就覆盖",再用 set 看自动去重。
map:按键找值
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> score; // 名字 → 分数
score["小绿"] = 88; // 键不存在 → 新增
score["小蓝"] = 95;
score["小绿"] = 92; // 键已存在 → 覆盖
std::cout << "小绿:" << score["小绿"] << std::endl;
if (score.count("小红") == 0) { // 查:键在不在
std::cout << "查无此人" << std::endl;
}
for (auto& [name, s] : score) { // 遍历:按键的顺序
std::cout << name << " → " << s << std::endl;
}
return 0;
}
你会看到:
小绿:92
查无此人
小绿 → 92
小蓝 → 95
和 Python 字典几乎一样:[] 负责增和改、count 查有无、range-for 遍历(auto& [name, s] 相当于 Python 的 key, value 解包)。两处不同要记牢:map 遍历永远按键排序(字符串键按编码比,不是拼音,所以"小绿"排在"小蓝"前);以及一个大坑——用 score["小红"] 去查不存在的键,会顺手创建一个值为 0 的键!拿不准在不在,先用 count 或 find。
map 常用操作
| 操作 | 干什么 | 返回 | 备注 |
|---|---|---|---|
m[k] = v |
插入或覆盖键值对 | — | 最常用的增改 |
m[k] |
读取键 k 的值 | 值 | ⚠️ 键不存在会顺手创建默认值(int 是 0) |
m.count(k) |
键 k 在不在 | 0 或 1 | 推荐用来查存在性 |
m.find(k) |
找键 k | 迭代器 | 找到返回位置,找不到返回 end() |
m.erase(k) |
删掉键 k | — | |
m.size() |
键值对个数 | 整数 |
find 的典型用法:if (m.find(k) != m.end()) 就说明找到了,取值用 it->second。
set:自动去重 + 排序
set 只关心"有没有":塞重复的它自己不要,还顺手排好序。
#include <iostream>
#include <set>
int main() {
std::set<int> nums = {3, 1, 4, 1, 5, 9, 2, 6, 5}; // 有重复
std::cout << "去重后剩 " << nums.size() << " 个" << std::endl;
for (int n : nums) {
std::cout << n << " ";
}
std::cout << std::endl;
if (nums.count(4)) {
std::cout << "有 4" << std::endl;
}
return 0;
}
你会看到:
去重后剩 7 个
1 2 3 4 5 6 9
有 4
两个 1、两个 5 只留一个,出来还自动从小到大排好。
set 常用操作
| 操作 | 干什么 | 返回 | 备注 |
|---|---|---|---|
s.insert(x) |
插入元素 | — | 重复的会被忽略 |
s.count(x) |
元素 x 在不在 | 0 或 1 | 查存在性 |
s.find(x) |
找元素 x | 迭代器 | 找不到返回 end() |
s.erase(x) |
删除元素 x | — | |
s.size() |
元素个数 | 整数 |
count 与 find 对照
| 操作 | map | set | 返回 |
|---|---|---|---|
count(k) |
键 k 在不在 | 元素 k 在不在 | 0 或 1 |
find(k) |
找键 k | 找元素 k | 迭代器,找不到返回 end() |
记忆口诀
map 键值成对,set 去重排队;查在不在用 count,别拿中括号瞎猜。
❓ 常见问题
Q1:map 的键可以是 int 吗?
当然可以。std::map<int, std::string> 把学号(int)映射到姓名(string)完全没问题。map 的键可以是任何支持 < 比较的类型。
Q2:set 和 vector 去重有什么区别?
set 插入时自动去重且保持有序;vector 要先塞再 std::sort + std::unique 才能去重。如果边插边要去重,用 set 更方便。
Q3:map 遍历为什么不是插入顺序?
std::map 默认按键排序(红黑树实现)。如果需要保持插入顺序,C++ 标准库没有现成的"有序字典",可以用 vector<std::pair<...>> 或者第三方库。
⚠️ 新手翻车现场
翻车一:用 [] 查不存在的键,顺手建了一个
std::map<std::string, int> score;
std::cout << score["小黑"] << std::endl; // 打印 0,并且 score 里多了一个 "小黑" → 0
std::cout << score.size() << std::endl; // size 变成了 1!
问题:operator[] 如果键不存在,会默认构造一个值(int 是 0)并插入。这会污染你的数据。
修正:查存在性先用 count 或 find:
if (score.count("小黑")) {
std::cout << score["小黑"];
} else {
std::cout << "查无此人";
}
翻车二:set 里存自定义类型,编译报错
struct Student { std::string name; int score; };
std::set<Student> s; // 编译错误!
问题:set 需要对元素做 < 比较来排序和去重,自定义结构体默认不支持 <。
修正:重载 operator<,或传自定义比较器:std::set<Student, decltype(&cmp)> s(cmp);。入门阶段先存基本类型(int、string),自定义类型等学完类和运算符重载再来。
🎯 什么时候用 map / set
| 场景 | 推荐 | 原因 |
|---|---|---|
| 按名字查分数、按学号查电话 | map |
键值对查找 O(log n) |
| 去重一串数字,还要自动排序 | set |
自动去重+有序 |
| 统计单词出现次数 | map<std::string, int> |
键是单词,值是计数 |
| 判断一个数是否出现过 | set<int> |
count 判断存在性 |
📝 本章小结
map是键值对容器,像 Python 字典,遍历按键排序set自动去重且自动排序,适合"有没有"的场景- 查存在性优先用
count或find,别轻易用[]查(会顺手建键) map的[]查不存在的键会插入默认值(int 是 0),注意副作用- 记忆口诀:map 键值成对,set 去重排队;查在不在用 count,别拿中括号瞎猜。
动手改
- 给
score再加两个同学,遍历打印,观察输出按键排序的效果 - 打印
score["小黑"],再打印score.size()——发现 size 变大了?这就是[]顺手建键的副作用 - 往
set里塞 5 个带重复的数字,打印去重后的个数和全部元素 - 用
map<std::string, int>统计一段文字里每个单词出现的次数
下一章:综合练习,把四章学的全用上 🐸