C++ STL 中的 set 和 map从入门到实战指南
作者:破忒头456
一、 序列式容器 vs 关联式容器
我们之前学过 string、vector、list、deque 等,它们统称为序列式容器,因为元素在逻辑上是线性排列的,位置之间没有强关联,交换两个元素不影响“序列”性质。
而关联式容器(如 set、map、unordered_set 等)逻辑上通常是非线性的(树或哈希),元素之间通过关键字(key)来建立关联。交换两个元素可能会破坏结构,因为它们的位置是由关键字决定的。
(1)set系列:纯 key 搜索场景,比如判断一个值在不在集合中。
(2)map系列:key/value 搜索场景,比如字典(单词→释义)。
本章讲的 map/set 底层是红黑树(平衡二叉搜索树),因此增删查效率为 O(log N),且迭代器遍历是中序,得到的是有序序列。
二、 set 系列详解
2.1 set 的声明与特点
template < class T,
class Compare = less<T>,
class Alloc = allocator<T> >
class set;T是关键字的类型。- 默认要求
T支持<比较,也可以自己传仿函数。 - 底层红黑树,去重 + 升序(默认)。
- 迭代器(包括
const_iterator)不允许修改 key,否则会破坏树结构。
2.2 构造与迭代器
set<int> s1; // 空
set<int> s2 = {5,1,5,2,6}; // initializer_list,自动去重
set<int> s3(s2.begin(), s2.end()); // 区间构造
set<int> s4(s3); // 拷贝构造迭代器:
begin()/end()正向rbegin()/rend()反向- 遍历得到的是升序序列(因为中序遍历)
2.3 增删查核心接口
| 操作 | 接口 | 说明 |
|---|---|---|
| 插入 | insert(val) | 返回 pair<iterator,bool>,bool 表示是否成功(已存在则失败) |
| 插入列表 | insert({...}) | 批量插入,已存在忽略 |
| 查找 | find(key) | 返回迭代器,找不到返回 end(),O(log N) |
| 计数 | count(key) | 返回 key 的个数(set 中只能是 0 或 1) |
| 删除 | erase(key) | 删除等于 key 的元素,返回删除个数 |
| 删除 | erase(pos) | 删除迭代器位置 |
| 区间删除 | erase(first,last) | 删除 [first, last) |
| 下界 | lower_bound(key) | 返回第一个 >= key 的迭代器 |
| 上界 | upper_bound(key) | 返回第一个 > key 的迭代器 |
2.4 综合示例:set 的基本操作

2.5 multiset —— 允许重复 key
multiset与set的唯一区别是不去重,其他接口基本一致,但有一些差异:
(1)insert总是成功。
(2)find(key)返回中序第一个等于 key 的迭代器。
(3)count(key)返回实际个数(可能 >1)。
(4)erase(key)会删除所有等于 key 的元素。
示例:

三、map 系列详解
3.1 pair 类型——key/value 的载体
map底层节点存储的是 pair<const key,T>,定义如下:
template <class T1, class T2>
struct pair {
T1 first; // key
T2 second; // value
// 构造函数...
};我们可以用make_pair或{}来创建 pair。
3.2 map 的声明与特点
template < class Key,
class T,
class Compare = less<Key>,
class Alloc = allocator<pair<const Key,T>> >
class map;- Key 唯一(去重),按 Key 升序(默认)。
- 迭代器遍历中序,有序。
- 可以修改 mapped_type(即 value),不能修改 key。
3.3 构造与迭代器
与 set 类似,支持 initializer_list、区间、拷贝等。
map<string, string> dict = {
{"left", "左边"},
{"right", "右边"},
{"insert", "插入"},
{"sort", "排序"}
};迭代器访问:
auto it = dict.begin();
while (it != dict.end()) {
cout << it->first << ": " << it->second << endl; // it->first 即 key
++it;
}范围 for:
for (const auto& kv : dict) {
cout << kv.first << ": " << kv.second << endl;
}3.4 增删查接口
| 操作 | 接口 | 说明 |
|---|---|---|
| 插入 | insert({key, value}) | 返回 pair<iterator,bool>,key 存在则失败 |
| 查找 | find(key) | 返回迭代器,可修改 value |
| 计数 | count(key) | 返回 0 或 1(multimap 可>1) |
| 删除 | erase(key) | 删除所有 key,返回删除个数 |
| 删除 | erase(pos) | 删除迭代器位置 |
| 区间删除 | erase(first,last) | 删除 [first,last) |
| 下界/上界 | lower_bound/upper_bound | 同 set |
3.5 核心:operator[] —— 插入+查找+修改三合一
operator[]是 map 最强大的接口,其内部实现等价于:
mapped_type& operator[](const key_type& k) {
// 插入 {k, mapped_type()},如果已存在则返回已有迭代器
pair<iterator, bool> ret = insert({k, mapped_type()});
// 返回 value 的引用
return ret.first->second;
}行为:
- 如果k不存在,插入{k,默认值},并返回该默认值的引用,你可以通过
=赋值。 - 如果k存在,直接返回对应 value 的引用,你可以读取或修改。
因此 [] 可以用于:
- 插入加修改:
dict["new"] = "新的"; 插入:dict["new"]- 查找:
cout << dict["left"];(但如果不存在,会插入默认值,注意副作用) - 修改:
dict["left"] = "左边,剩余";
⚠️ 注意:使用 [] 查找时,若 key 不存在会插入默认 value,可能不是你想要的行为,通常用 find 来纯粹查找。
3.6 综合示例

3.7multimap —— 允许 key 冗余
multimap与map的区别就是 key 可重复,因此:
- insert总是成功。
- find返回中序第一个。
- count返回实际个数。
- erase(key)删除所有该 key。
- 不支持operator[],因为 key 不唯一,无法确定返回哪个 value。

四、总结
| 容器 | 去重 | key 可修改 | value 可修改 | 是否支持 [] | 应用场景 |
|---|---|---|---|---|---|
| set | 是 | 否(整体不可改) | — | 否 | 快速去重、存在性判断 |
| multiset | 否 | 否 | — | 否 | 允许重复的有序集合 |
| map | 是 | 否 | 是 | 是 | 字典、映射、统计 |
| multimap | 否 | 否 | 是 | 否 | 一对多映射(如一个key对应多个value |
核心要点:
- 底层红黑树,增删查 O(log N)。
- 迭代器遍历有序。
- set和map的 key 不可修改,但
map的 value 可以修改。 - operator[]是 map 的利器,但要小心副作用。
- 多利用lower_bound/upper_bound做区间操作。
到此这篇关于C++ STL 中的 set 和 map 完全指南:从入门到实战的文章就介绍到这了,更多相关C++ STL set 和 map 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
