map 和 set
vector 用 0、1、2 这样的下标找元素。可很多时候我们想用名字去找:苹果多少钱、小林几分。std::map 就是一张两列的表:左边是键,右边是值,用键找值。要 #include <map>。
1#include <iostream>2#include <map>3#include <string>4 5int main() {6 std::map<std::string, double> prices;7 prices["苹果"] = 6.5;8 prices["香蕉"] = 3.2;9 std::cout << "香蕉:" << prices["香蕉"] << " 元\n";10 std::cout << "一共 " << prices.size() << " 种\n";11 return 0;12}
香蕉:3.2 元 一共 2 种
std::map<std::string, double>:键是字符串,值是 double。prices["苹果"] = 6.5 存进去,prices["香蕉"] 取出来,和 vector 的方括号很像,只是方括号里放的是键。
存和改
map 也可以用花括号一开始就放好几对,每一对写成 {键, 值}。同一个键只有一个值:再给它赋值,就是改掉原来的。点单步,看 stock 显示成一对一对的:
1#include <iostream>2#include <map>3#include <string>4 5int main() {6 std::map<std::string, int> stock{{"苹果", 5}, {"梨", 3}};7 stock["苹果"] += 2;8 stock["橙子"] = 10;9 std::cout << "苹果:" << stock["苹果"] << '\n';10 std::cout << "一共 " << stock.size() << " 种\n";11 return 0;12}
苹果:7 一共 3 种
stock["橙子"] = 10 时,表里还没有橙子,就新加一对;stock["苹果"] += 2 时已经有了,就在原来的值上改。
按键排好序
范围 for 能走遍一个 map。每一圈拿到的是一对:.first 是键,.second 是值。不管你按什么顺序放进去,map 总是按键从小到大排好。点单步,看新的一对插到哪里:
1#include <iostream>2#include <map>3#include <string>4 5int main() {6 std::map<int, std::string> rooms;7 rooms[305] = "小林";8 rooms[101] = "小王";9 rooms[210] = "小张";10 for (const auto& kv : rooms) {11 std::cout << kv.first << " 号房:" << kv.second << '\n';12 }13 return 0;14}
101 号房:小王 210 号房:小张 305 号房:小林
这里每一对的类型很长,所以写 const auto&。C++17 起还可以写成 for (const auto& [room, name] : rooms),这门课只用 C++14,写 .first 和 .second。
注意: 还有一种 std::unordered_map,查得更快,但走一遍的顺序不固定,每台电脑上都可能不一样。
m[key] 的坑
用方括号取一个不存在的键,会怎样?
这段程序显示什么?
1#include <iostream>2#include <map>3#include <string>4 5int main() {6 std::map<std::string, int> stock{{"苹果", 5}};7 std::cout << stock["香蕉"] << '\n';8 std::cout << stock.size() << '\n';9 return 0;10}
stock["香蕉"] 找不到香蕉,就悄悄新建一对 {"香蕉", 0},再把 0 给你。所以表里变成了两对。
方括号找不到就新建:数数的时候很方便(下面数单词就靠它),只想看看有没有的时候却是个坑,表里会多出一堆 0。
查一查、删一个
只想知道有没有,不想新建,用这几个:
| 写法 | 做什么 |
|---|---|
m.count(key) | 有这个键是 1,没有是 0 |
m.find(key) | 找到的那一对;找不到时等于 m.end() |
m.at(key) | 取值;没有这个键就抛出 std::out_of_range |
m.erase(key) | 删掉这个键和它的值 |
1#include <iostream>2#include <map>3#include <string>4 5int main() {6 std::map<std::string, int> scores{{"小林", 92}, {"小王", 78}};7 if (scores.count("小张") == 0) {8 std::cout << "没有小张\n";9 }10 auto it = scores.find("小王");11 if (it != scores.end()) {12 std::cout << "小王:" << it->second << '\n';13 }14 scores.erase("小王");15 std::cout << "还剩 " << scores.size() << " 人\n";16 return 0;17}
没有小张 小王:78 还剩 1 人
find 给的是一个迭代器,指着找到的那一对;-> 拿的是它指着的那一对里的成员,所以 it->second 就是值。迭代器第 27 课再讲。大多数时候 count 就够了。
数单词
map 最常见的用处之一是数数。counts[w]++:第一次见到 w,方括号新建一个 0,再加 1;以后每见一次加 1。
1#include <iostream>2#include <map>3#include <string>4#include <vector>5 6int main() {7 std::vector<std::string> words{"猫", "狗", "猫", "鱼", "猫", "狗"};8 std::map<std::string, int> counts;9 for (const auto& w : words) {10 counts[w]++;11 }12 for (const auto& kv : counts) {13 std::cout << kv.first << ":" << kv.second << '\n';14 }15 return 0;16}
狗:2 猫:3 鱼:1
点单步,看 counts 里的数怎么一个一个涨上去。「狗」排在「猫」前面,是因为按编码排。
set:不重复、排好序
std::set(在 <set> 里)只有键,没有值。同一个值放进去两次,只留一个;走一遍时总是从小到大:
1#include <iostream>2#include <set>3 4int main() {5 std::set<int> seen;6 seen.insert(42);7 seen.insert(7);8 seen.insert(42);9 seen.insert(19);10 std::cout << "有 " << seen.size() << " 个\n";11 for (int x : seen) {12 std::cout << x << " ";13 }14 std::cout << '\n';15 std::cout << "有 7 吗?" << seen.count(7) << '\n';16 return 0;17}
有 3 个 7 19 42 有 7 吗?1
insert 往里放,count 查有没有(1 或 0),erase 删掉。要「去掉重复的」「看看出现过没有」,就用 set。
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。
仓库一共几件
✓写好 total_count:走一遍 stock,把所有的数量加起来交回去。main 已经写好了,会显示两个仓库各有几件。
厨房:18 件 办公室:35 件
1#include <iostream>2#include <map>3#include <string>4 5int total_count(const std::map<std::string, int>& stock) {6 int total = 0;7 8 return total;9}10 11int main() {12 std::map<std::string, int> kitchen{{"杯子", 6}, {"盘子", 8}, {"碗", 4}};13 std::map<std::string, int> office{{"笔", 20}, {"本子", 15}};14 std::cout << "厨房:" << total_count(kitchen) << " 件\n";15 std::cout << "办公室:" << total_count(office) << " 件\n";16 return 0;17}
每个字母几次
✓数一数 word 里每个字母出现几次,按字母顺序显示。
a:3 b:1 n:2
1#include <iostream>2#include <map>3#include <string>4 5int main() {6 std::string word = "banana";7 8 return 0;9}
修好它:set 不用 push_back
✓这段程序编译不过。改好它,让它显示一共打过几个不同的号码:不同的号码:3 个。
不同的号码:3 个
1#include <iostream>2#include <set>3#include <vector>4 5int main() {6 std::vector<int> calls{110, 120, 110, 119, 120};7 std::set<int> seen;8 for (int c : calls) {9 seen.push_back(c);10 }11 std::cout << "不同的号码:" << seen.size() << " 个\n";12 return 0;13}
这一课学会了
std::map<键类型, 值类型>用键找值,要#include <map>;m[key] = value存或改。- 走一遍
map时按键从小到大,每一对用.first(键)和.second(值)。 m[key]找不到会新建一个默认值(0、空字符串):数数时正好,只想看看时是坑。- 查有没有用
count(1 或 0)或find(找不到等于end());at找不到会抛出异常;erase删除。 counts[w]++是数单词的老办法。std::set只放不重复的值、自动排好序;insert、count、erase。