概述
与 string、vector 相比,list 的底层是链表结构,这意味着它不支持随机访问,因此没有重载运算符 [] 接口,也没有 reserve 扩容接口。list 的优势在于频繁的中间插入和删除操作。
1. 底层结构验证
list 采用带头双向链表实现。节点包含数据域、前驱指针和后继指针。SGI 库中的 list 成员变量仅有一个头节点(哨兵位),通过头尾相连形成循环结构,简化了边界处理。
使用 struct 包装节点而非 class,是因为 C++ 中 struct 默认成员为公有,无需额外声明友元即可在类内部访问,代码更简洁。
2. 迭代器设计
vector 和 string 使用原生指针作为迭代器,因为内存连续。而 list 的节点在堆上分散,原生指针无法满足解引用和自增的核心需求。因此需要封装迭代器类,重载 operator* 和 operator->。
通常使用 struct 包装迭代器,利用其默认公有特性,避免友元声明的繁琐。注意,迭代器本身不需要析构函数,它只负责遍历,不负责销毁节点,节点生命周期由 list 管理。
普通迭代器和 const 迭代器的区别主要在于解引用返回值的类型不同,可通过模板参数区分,避免代码冗余。
3. 核心接口实现
构造与析构
无参构造初始化一个只有哨兵位的空链表。析构函数复用清除逻辑,最后释放哨兵位。
// 构造函数示意
List() { _head = new Node(); _head->_next = _head; _head->_prev = _head; }
~List() { clear(); delete _head; }
拷贝与赋值
拷贝构造需重新分配节点并尾插数据。赋值重载建议采用'传值交换'策略,先创建临时对象再 swap,既安全又高效。
插入与删除
任意位置插入时,需调整前后节点的指针指向。删除操作返回新位置,防止迭代器失效。头尾插删可复用通用插入删除逻辑。
// 插入逻辑示意
Node* newNode = new Node(val);
newNode->_next = pos->_next;
pos->_next->_prev = newNode;
pos->_next = newNode;
newNode->_prev = pos;
_size++;
4. 特殊接口说明
STL 中的 sort、merge、unique 等接口在 list 上有特殊实现。例如 sort 使用归并排序,因为 list 不支持随机访问,无法使用快速排序。remove 接口删除指定值的节点,而非迭代器位置,这与 erase 不同。
注意,list 的 sort 效率不如 vector 调用算法库中的 sort,大数据量下建议避免直接使用。
5. 打印与调试
自定义类型打印需重载 << 运算符,或在迭代器中重载 -> 运算符以便直接访问成员。泛型打印函数需注意 typename 的使用,告知编译器后续标识符为类型。
typename Container::const_iterator it = con.begin();
while (it != con.end()) {
cout << *it << " ";
++it;
}
通过上述步骤,我们可以完整模拟出 C++ list 的基本功能,深入理解容器底层的内存管理与迭代器机制。


