在 STL 容器中,map和set是高频使用的关联式容器,其底层均基于红黑树 实现 ------ 一种自平衡的二叉搜索树,能保证增删查改的时间复杂度为O(logN)。本文将从 SGI-STL 源码框架出发,讲解如何通过泛型思想复用红黑树,分别实现自定义的mymap和myset,核心掌握红黑树的泛型封装 、KeyOfT 仿函数设计 、迭代器实现 以及map的operator[]重载等关键技术点。
一、核心设计思路:红黑树的泛型复用
SGI-STL 中map和set的核心设计精髓是复用同一棵红黑树,通过模板参数和仿函数区分两者的存储逻辑,避免代码冗余。
1.1 map 与 set 的本质区别
set:仅存储键值(Key),且键值唯一,不允许修改,支持基于 Key 的查找、插入、删除;map:存储键值对(pair<const Key, T>) ,Key 唯一且不可修改,T 为映射值可修改,支持通过 Key 访问映射值(如operator[])。
1.2 红黑树的泛型适配
红黑树本身不关心存储的是单一 Key 还是键值对,只需通过三个核心模板参数实现泛型适配:
cpp
template<class K, class T, class KeyOfT>
class RBTree;
K:键的类型,为find/erase等操作提供参数类型;T:红黑树节点实际存储的数据类型(set中 T=K,map中 T=pair<const K, V>);KeyOfT:键提取仿函数,用于从 T 类型中提取 Key 进行比较,解决红黑树节点数据比较的统一化问题。
1.3 KeyOfT 仿函数的核心作用
由于set和map的存储类型 T 不同,直接比较 T 无法满足红黑树的二叉搜索特性(如map的 pair 默认比较会同时对比 Key 和 Value),因此需要为set和map分别实现 KeyOfT 仿函数,统一从 T 中提取 Key 进行比较:
SetKeyOfT:从 K 类型中直接返回自身(因为 set 的 T=K);MapKeyOfT:从 pair<const K, V> 中返回 first(即 Key)。
一、实现 mymap 和 myset
基于上述泛型红黑树,分别封装myset和mymap,核心是实现各自的 KeyOfT 仿函数,并复用红黑树的所有接口。
5.1 封装 myset
myset的存储类型为 K,KeyOfT 仿函数直接返回 K 本身,且set的迭代器为 const 迭代器(键不可修改)。
namespace bit
{
// 自定义set
template<class K>
class set
{
// set的键提取仿函数:T=K,直接返回key
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
// 复用红黑树的迭代器
typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;
// 迭代器接口
iterator begin() { return _t.Begin(); }
iterator end() { return _t.End(); }
const_iterator begin() const { return _t.Begin(); }
const_iterator end() const { return _t.End(); }
// 插入:返回pair<iterator, bool>
pair<iterator, bool> insert(const K& key)
{
return _t.Insert(key);
}
// 查找:根据Key查找
iterator find(const K& key)
{
return _t.Find(key);
}
private:
// 红黑树:K=键类型,T=const K(不可修改),KeyOfT=SetKeyOfT
RBTree<K, const K, SetKeyOfT> _t;
};
}
5.2 封装 mymap
mymap的存储类型为pair<const K, V>,KeyOfT 仿函数返回 pair 的 first;核心实现operator[],支持通过 Key 直接访问 / 修改映射值。
namespace bit
{
// 自定义map
template<class K, class V>
class map
{
// map的键提取仿函数:T=pair<const K,V>,返回first(Key)
struct MapKeyOfT
{
const K& operator()(const pair<const K, V>& kv)
{
return kv.first;
}
};
public:
// 复用红黑树的迭代器
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator const_iterator;
// 迭代器接口
iterator begin() { return _t.Begin(); }
iterator end() { return _t.End(); }
const_iterator begin() const { return _t.Begin(); }
const_iterator end() const { return _t.End(); }
// 插入:返回pair<iterator, bool>
pair<iterator, bool> insert(const pair<const K, V>& kv)
{
return _t.Insert(kv);
}
// 查找:根据Key查找
iterator find(const K& key)
{
return _t.Find(key);
}
// 核心:operator[]重载,支持map[key] = value
V& operator[](const K& key)
{
// 插入键值对,若Key已存在则返回原有迭代器
pair<iterator, bool> ret = insert(make_pair(key, V()));
// 返回映射值的引用,支持修改
return ret.first->second;
}
private:
// 红黑树:K=键类型,T=pair<const K,V>,KeyOfT=MapKeyOfT
RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
}
map::operator[]的核心原理
operator[]的本质是插入 + 返回引用,一行代码实现:
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.first->second;
}
- 若 Key 不存在:插入
pair(key, V())(V () 为默认构造),返回新插入的映射值引用,支持赋值(如map["a"] = 1);- 若 Key 已存在:插入失败,返回原有映射值的引用,支持修改(如
map["a"] = 2)。
六、测试:验证 mymap 和 myset 的功能
编写测试代码,验证myset的有序插入、查找,以及mymap的insert、operator[]、迭代器遍历等功能。
6.1 测试 myset
void test_set()
{
bit::set<int> s;
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
// 插入数据
for (auto e : a)
{
s.insert(e);
}
// 中序遍历:有序输出
for (auto e : s)
{
cout << e << " ";
}
cout << endl; // 输出:1 2 3 4 5 6 7 14 15 16
// 查找
auto it = s.find(6);
if (it != s.end())
{
cout << "找到:" << *it << endl; // 输出:找到:6
}
}
6.2 测试 mymap
void test_map()
{
bit::map<string, string> dict;
// 插入键值对
dict.insert(make_pair("sort", "排序"));
dict.insert(make_pair("left", "左边"));
dict.insert(make_pair("right", "右边"));
// operator[]修改已有值
dict["left"] = "左边,左侧";
// operator[]插入新值
dict["insert"] = "插入";
dict["string"]; // 插入默认值:string("")
// 遍历map:有序输出Key
for (auto& kv : dict)
{
cout << kv.first << " : " << kv.second << endl;
}
// 查找
auto it = dict.find("sort");
if (it != dict.end())
{
cout << "找到:" << it->first << " : " << it->second << endl;
}
}
6.3 主函数调用
int main()
{
cout << "测试myset:" << endl;
test_set();
cout << endl << "测试mymap:" << endl;
test_map();
return 0;
}
编译与运行
编译时直接使用 g++,无需额外链接库:
g++ mymap_set.cpp -o mymap_set
./mymap_set
运行结果符合预期,set输出有序序列,map支持operator[]的插入和修改,迭代器遍历有序。
七、核心总结
本文通过复刻 SGI-STL 的设计思路,实现了基于红黑树的mymap和myset,核心知识点梳理如下:
- 泛型复用 :红黑树通过模板参数
K、T、KeyOfT实现泛型,适配set(T=K)和map(T=pair<const K,V>)的不同存储需求;- KeyOfT 仿函数:解决红黑树节点数据的统一比较问题,从 T 中提取 Key 进行二叉搜索,是泛型复用的关键;
- 双向迭代器 :基于红黑树的中序遍历实现,
++和--操作是难点,需处理右子树 / 左子树存在和不存在的情况,以及end()的特殊处理;- 红黑树自平衡 :新节点默认红色,通过变色 和旋转 + 变色处理连续红节点,保证根节点始终为黑色;
- map::operator[] :本质是
insert操作的封装,实现「插入 + 返回引用」,支持一键式访问 / 修改映射值。
通过本次实现,不仅能深入理解map和set的底层原理,还能掌握泛型编程 、仿函数设计 、迭代器封装等 C++ 核心技术,为手撕 STL 容器打下坚实基础。