手撕C++ list容器:从节点到完整双向链表实现
前言
在C++标准模板库(STL)中,list
是一个非常重要的容器,它实现了双向链表的数据结构。本文将深入探讨如何从零开始实现一个完整的list
容器,包括节点设计、迭代器实现和核心功能。
整体结构设计
节点设计
首先我们需要定义链表的基本单元——节点:
cpp
template<class T> struct list_node {T _val;list_node<T>* _prev;list_node<T>* _next;list_node(const T& val = T()): _val(val), _prev(nullptr), _next(nullptr){} };
每个节点包含三个部分:
_val
: 存储数据值_prev
: 指向前一个节点的指针_next
: 指向后一个节点的指针
迭代器设计
迭代器是容器的"智能指针",我们使用模板技巧实现iterator
和const_iterator
:
cpp
template<class T, class Ref, class Ptr> struct list_iterator {typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;Node* _node;// 构造函数和运算符重载... };
这种设计巧妙之处在于通过模板参数Ref
和Ptr
区分普通迭代器和常量迭代器:
iterator
:list_iterator<T, T&, T*>
const_iterator
:list_iterator<T, const T&, const T*>
核心实现解析
构造函数与初始化
cpp
list() {empty_init(); }void empty_init() {_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0; }
这里使用了哨兵节点(头节点)技术,让链表形成环形结构,简化边界条件处理。
拷贝控制:拷贝构造与赋值
cpp
// 拷贝构造函数 list(const list<T>& lt) {empty_init();for (auto& e : lt){push_back(e);} }// 赋值运算符(使用Copy-and-Swap技术) list<T>& operator=(list<T> it) {swap(it);return *this; }void swap(list<T>& it) {std::swap(_head, it._head);std::swap(_size, it._size); }
赋值运算符采用传值方式,这实现了Copy-and-Swap惯用法:
参数
it
通过传值接收(可能是拷贝或移动构造)交换当前对象与参数的内容
返回时参数自动析构,释放旧资源
这种方法异常安全且代码简洁,同时支持拷贝和移动赋值。
迭代器操作
cpp
iterator begin() { return _head->_next; } iterator end() { return _head; }const_iterator begin() const { return _head->_next; } const_iterator end() const { return _head; }
迭代器范围是左闭右开区间[begin, end)
,end()
返回头节点,符合STL惯例。
元素访问与修改
cpp
// 插入操作 void insert(iterator pos, const T& x) {Node* newnode = new Node(x);newnode->_prev = pos._node->_prev;newnode->_next = pos._node;pos._node->_prev->_next = newnode;pos._node->_prev = newnode;_size++; }// 删除操作 iterator erase(iterator pos) {assert(pos != end());Node* next_node = pos._node->_next;pos._node->_prev->_next = pos._node->_next;pos._node->_next->_prev = pos._node->_prev;delete pos._node;_size--;return next_node; }
erase()
函数返回下一个有效迭代器,这是为了防止迭代器失效。
容量操作
cpp
size_t size() const { return _size; } bool empty() const { return _size == 0; } void clear() {auto it = begin();while (it != end()){it = erase(it);} }
维护_size
变量可以在O(1)时间内获取元素个数,比遍历链表更高效。
关键技术亮点
哨兵节点技术:简化边界条件处理,使代码更加健壮
模板迭代器:使用单一模板实现普通和常量迭代器,减少代码重复
Copy-and-Swap:优雅的赋值运算符实现,保证异常安全
RAII原则:构造函数分配资源,析构函数释放资源
STL兼容:接口设计符合STL标准,可以无缝替换
std::list
使用示例
cpp
ym::list<string> myList; myList.push_back("Hello"); myList.push_back("World"); myList.push_front("C++");// 遍历打印 for (const auto& str : myList) {cout << str << " "; } // 输出: C++ Hello World// 链式赋值 ym::list<string> list1, list2, list3; list1 = list2 = list3; // 支持链式操作