🐸 青蛙编程网
首页 / C++ / STL / 4. map 与 set

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,别拿中括号瞎猜。

动手改

  1. 给 score 再加两个同学,遍历打印,观察输出按键排序的效果
  2. 打印 score["小黑"],再打印 score.size()——发现 size 变大了?这就是 [] 顺手建键的副作用
  3. 往 set 里塞 5 个带重复的数字,打印去重后的个数和全部元素
  4. 用 map<std::string, int> 统计一段文字里每个单词出现的次数

下一章:综合练习,把四章学的全用上 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。