目录
- 前言
- 一、源码及框架分析
-
- [1.1 源码文件分析](#1.1 源码文件分析)
- [1.2 源码框架分析](#1.2 源码框架分析)
- [1.4 源码命名缺陷](#1.4 源码命名缺陷)
- [1.5 框架优势](#1.5 框架优势)
- 结语


🎬 云泽Q :个人主页
🔥 专栏传送入口 : 《C语言》《数据结构》《C++》《Linux》《蓝桥杯系列》
⛺️遇见安然遇见你,不负代码不负卿~
前言
大家好啊,我是云泽Q,欢迎阅读我的文章,一名热爱计算机技术的在校大学生,喜欢在课余时间做一些计算机技术的总结性文章,希望我的文章能为你解答困惑~
一、源码及框架分析
SGI-STL3.0 版本源代码,map 和 set 的源代码在map/set/stl_map.h/stl_set.h/stl_tree.h 等几个头文件中。
1.1 源码文件分析
对源码了解不多的兄弟们,我在这里说一下这几个头文件的包含关系:
一、核心层级与依赖关系
在 SGI-STL3.0 中,这几个头文件是分层依赖的结构,核心是「底层红黑树 → 容器封装 → 用户接口」:
cpp
stl_tree.h (底层红黑树实现)
↓ 被依赖
stl_map.h / stl_set.h (基于红黑树封装 map/set 容器)
↓ 被包含
map / set (对外用户接口头文件)

二、逐文件解析
1. stl_tree.h:最底层的红黑树核心
- 这是所有容器的基石,实现了完整的红黑树(rb_tree)数据结构:
-
- 包含红黑树节点定义、颜色调整、旋转、插入 / 删除 / 查找等核心算法
-
- 提供了红黑树的迭代器、分配器等基础设施
- map 和 set 本质上都是红黑树的上层适配器,所以它们的底层逻辑完全复用 stl_tree.h 的代码,没有自己独立的树实现。
2. stl_map.h:map 容器的封装层
- 基于 stl_tree.h 封装出 std::map 和 std::multimap:
-
- 将红黑树的节点存储为 pair<const Key, T>(键值对,键不可修改)
-
- 实现 operator[]、find、insert 等 map 特有的接口
-
- 约束:键(Key)必须唯一,默认按升序排序
- 它不实现树结构,只复用红黑树能力,把「键值对」适配到红黑树的节点存储模型。
3. stl_set.h:set 容器的封装层
- 基于 stl_tree.h 封装出 std::set 和 std::multiset:
-
- 将红黑树的节点存储为单个 Key 值(元素本身就是键)
-
- 实现 insert、find 等接口,不提供 operator[](因为没有值,只有元素)
-
- 约束:元素必须唯一,默认按升序排序
- 同样是红黑树的适配器,把「单个元素」适配到红黑树的节点存储模型。
4. map / set:对外的用户接口头文件
- 这两个是用户直接 #include 的头文件,内部逻辑很简单:
-
- map 头文件:#include <stl_map.h> + 命名空间包装,让用户直接用 std::map
-
- set 头文件:#include <stl_set.h> + 命名空间包装,让用户直接用 std::set
- 作用:对用户隐藏底层 stl_tree.h 的复杂实现,只暴露容器的简洁接口。
总的来说 stl_tree.h 是底层红黑树引擎 ,stl_map.h 和 stl_set.h 是两个不同的方向盘 ,分别把红黑树改造成「键值对映射」和「元素集合」,而 map 和 set 是给用户握的方向盘外壳。本质上来说:map 和 set 是同一种树(红黑树)的两种不同用法,所以它们共享底层 stl_tree.h,只在上层封装逻辑上有区别。
所以SGI-STL 3.0 源码 (map/set 均基于stl_tree.h红黑树实现)

1.2 源码框架分析
下面我们就说一下map和set是怎么复用同一棵红黑树的,
一、 核心结构:三层封装与依赖关系
从图中可以清晰看到,map 和 set 并不是自己实现红黑树,而是 ** 组合(has-a)** 了一个红黑树对象。
1. 底层基石:rb_tree (红黑树核心)
文件 :stl_tree.h
职责:它是最底层的引擎。
- 定义了 __rb_tree_node 节点结构,包含 value_field (存储数据) 和 parent/left/right 指针。
- 实现了红黑树的插入insert(Value)、删除、旋转、查找find(Key)等核心算法逻辑。
- 泛型设计 :rb_tree它的模板参数是 template <class Key, class Value...>。
核心代码:typedef _rb_tree_node< Value > rb_tree_node;,也就是说,树的结点里只存Value,这里的 Value 决定了节点里存什么(是存 Key 还是 pair<const Key, T>)。这个底层红黑树只认两个东西,用Key排序/查找,在节点里存Value - 节点结构:
cpp
template <class Value>
struct __rb_tree_node {
typedef __rb_tree_node<Value>* link_type;
Value value_field; // 这里存的就是上面决定的 Value 类型
};
流程:map 告诉 rb_tree:Value 是 pair<const Key, T>。
rb_tree 实例化出 __rb_tree_node<pair<const Key, T>>。
节点内部的 value_field 就是一个键值对。
2. 中间封装层:map 容器类
文件 :stl_map.h
职责 :它是对 rb_tree 的适配器(Adapter)。
- 对外暴露接口:给用户提供 insert、find、erase 等成员函数。
- 类型定义 :
typedef Key key_type;排序用的键 = Key
typedef pair<const Key, T> value_type;节点里存的东西 = 键+值 一整套(固定写法!),Key不能改,T可以改 - 核心 :
typedef rb_tree<key_type, value_type, ...> rep_type;,把「排序键」和「存储数据」传给红黑树,生成一个专属map的红黑树 → 起别名 rep_type - 组合关系:它内部都有一个 rb_tree 类型的成员变量 rep_type t(创建一台红黑树!这就是map的底层)。
- map调用红黑树的方法:
insert(value_type) { t.insert(...); }find(key_type) { return t.find(...); } - 举个例子 :map<int, string>
Key = int(学号),Value = pair<const int, string>(学号 + 姓名)
底层红黑树:用 int 排序,节点存 pair
3. 中间封装层:set 容器类
文件 :stl_set.h
职责 :它是对 rb_tree 的适配器(Adapter)。
- 对外暴露接口:给用户提供 insert、find、erase 等成员函数。
- 类型定义 :
typedef Key key_type;排序用的键 = Key
typedef Key value_type;节点里存的东西 = Key(和键一模一样!) - 核心 :
typedef rb_tree<key_type, value_type, ...> rep_type;传给红黑树:键和存的东西是同一个 - 组合关系:它内部都有一个 rb_tree 类型的成员变量 rep_type t(创建一台红黑树!这就是set的底层)。
- set调用红黑树的方法:
insert(value_type) { t.insert(...); }find(key_type) { return t.find(...); },所有的 set::insert 底层调用的都是 rb_tree::insert,set::find 底层调用的都是 rb_tree::find。 - 举个例子 :set< int >
Key = int,Value = int
底层红黑树:用 int 排序,节点存 int
4. 实际调用层:用户使用的 map / set
文件:标准头文件 < map > / < set >
职责:这些文件会包含 #include <stl_map.h> 并做简单的命名空间包装,让用户直接用 std::map。
总结图谱:

极简结构图
cpp
【底层:stl_tree.h 红黑树】
只做两件事:用Key排序 | 存Value
↓ 套壳 ↓
【stl_map.h】 【stl_set.h】
传给红黑树: 传给红黑树:
Key = 键 Key = 元素
Value = 键+值(pair) Value = 元素
底层存:键值对 底层存:单一元素
对外:map容器 对外:set容器
要点补充 + 总结:
-
通过上面对框架的分析,我们可以看到源码中 rb_tree 用了一个巧妙的泛型思想实现,rb_tree 无论是实现 key 的搜索场景,还是 key/value 的搜索场景不是直接写死的,而是由第二个模板参数 Value 决定_rb_tree_node 中存储的数据类型。
-
set 实例化 rb_tree 时第二个模板参数给的是 key,map 实例化 rb_tree 时第二个模板参数给的是 pair<const key, T>,这样一颗红黑树既可以实现 key 搜索场景的 set,也可以实现 key/value 搜索场景的 map。
-
要注意一下,源码里面模板参数是用 T 代表 value,而内部写的 value_type 不是我们日常 key/value 场景中说的 value,源码中的value_type 反而是红黑树结点中存储的真实的数据的类型。
-
rb_tree 第二个模板参数 Value 已经控制了红黑树结点中存储的数据类型,为什么还要传第一个模板参数 Key 呢?尤其是 set,两个模板参数是一样的,这是很多兄弟这时的一个疑问。要注意的是对于 map 和 set,find/erase 时的函数参数都是 Key,所以第一个模板参数是传给 find/erase 等函数做形参的类型的。对于 set 而言两个参数是一样的,但是对于 map 而言就完全不一样了,map insert 的是 pair 对象,但是 find 和 ease 的是 Key 对象。
1.4 源码命名缺陷
吐槽一下,这里源码命名风格比较乱,大佬有时写代码也不规范,乱弹琴。
-
set 模板参数用的 Key 命名,map 用的是 Key 和 T 命名,而 rb_tree 用的又是 Key 和 Value
-
value_type 双重含义
用户视角 :在map<int, string>中,value_type 通常指 string(那个值)。
源码视角 :在 rb_tree 和 set/map 的内部实现中,value_type 指代的是节点中存储的完整数据类型。对 map 来说,源码里的 value_type 是 pair<const int, string>。对 set 来说,源码里的 value_type 是 int。
1.5 框架优势
- 极致的代码复用(STL 的灵魂)
✅ 只写 1 次红黑树代码,就能实现 4 个容器:map / set / multimap / multiset不用给每个容器单独写树,无冗余! - 解决「查找」的痛点(最关键)
map 查找:只需要传 key,不用传完整的 pair
set 查找:传元素本身,红黑树因为分开了 Key 和 Value,两种查找都能支持 - 约束数据安全
map 的 key 是 const:不能改(改了会乱序)
set 的元素也是 const:不能改
红黑树统一保证有序性
结语
