一、链表节点(list_node)的定义
链表节点是 list 容器的基本组成单元,用于存储数据并维护节点间的连接关系。
template<class T>
struct list_node
{
T _data; // 存储节点数据
list_node<T>* _next; // 指向后一个节点的指针
list_node<T>* _prev; // 指向前一个节点的指针
// 构造函数,默认值为T的默认构造
list_node(const T& x = T())
:_data(x)
, _next(nullptr)
, _prev(nullptr)
{}
};
解析:
- 节点采用双向链表设计,通过
_next
和_prev
指针维护前后节点关系,支持双向遍历。 - 构造函数允许通过参数初始化数据,默认参数
T()
确保即使未提供初始值,也能正确构造(调用 T 的默认构造函数)。
二、迭代器(__list_iterator)的实现
迭代器是容器与算法交互的桥梁,用于遍历容器元素。list 的迭代器需要模拟指针的行为,适配双向链表的特性。
template<class T, class Ref, class Ptr>
struct __list_iterator
{
typedef list_node<T> Node; // 节点类型别名
typedef __list_iterator<T, Ref, Ptr> self; // 迭代器自身类型别名
Node* _node; // 指向当前节点的指针
// 迭代器构造函数:通过节点指针初始化
__list_iterator(Node* node)
:_node(node)
{}
// 前置++:移动到下一个节点,返回自身引用
self& operator++()
{
_node = _node->_next;
return *this;
}
// 前置--:移动到上一个节点,返回自身引用
self& operator--()
{
_node = _node->_prev;
return *this;
}
// 后置++:先记录当前状态,再移动,返回原状态
self operator++(int)
{
self tmp(*this); // 拷贝当前迭代器
_node = _node->_next;
return tmp;
}
// 后置--:先记录当前状态,再移动,返回原状态
self operator--(int)
{
self tmp(*this);
_node = _node->_prev;
return tmp;
}
// 解引用:返回节点数据的引用(Ref适配const/非const)
Ref operator*()
{
return _node->_data;
}
// ->运算符:返回节点数据的指针(Ptr适配const/非const)
Ptr operator->()
{
return &_node->_data;
}
// 不等比较:判断两个迭代器是否指向不同节点
bool operator!=(const self& s)
{
return _node != s._node;
}
// 相等比较:判断两个迭代器是否指向同一节点
bool operator==(const self& s)
{
return _node == s._node;
}
};
解析:
- 模板参数
Ref
(引用类型)和Ptr
(指针类型)用于适配普通迭代器(T&
、T*
)和 const 迭代器(const T&
、const T*
),避免重复实现。 - 迭代器本质是对节点指针的封装,通过重载
++
、--
实现双向移动,重载*
、->
实现数据访问,重载==
、!=
实现迭代器比较。 - 前置 / 后置自增 / 自减的区别:前置返回修改后的自身(效率更高),后置返回修改前的副本(需要额外拷贝)。
三、list 类的核心实现
list 类是对双向链表的封装,提供了容器的各种操作接口,内部通过 “哨兵节点”(头节点)简化边界条件处理。
3.1 类型定义与成员变量
template<class T>
class list
{
typedef list_node<T> Node; // 节点类型别名
public:
// 普通迭代器:Ref为T&,Ptr为T*
typedef __list_iterator<T, T&, T*> iterator;
// const迭代器:Ref为const T&,Ptr为const T*
typedef __list_iterator<T, const T&, const T*> const_iterator;
private:
Node* _head; // 哨兵节点(头节点,不存储有效数据)
size_t _size; // 容器中有效元素的个数
};
解析:
- 哨兵节点
_head
的作用:统一链表的空状态和非空状态处理,避免操作时判断边界(如插入第一个元素或删除最后一个元素时的特殊处理)。 _size
记录有效元素个数,避免每次需要大小时遍历链表(O (1) 时间复杂度)。
3.2 迭代器接口(begin/end)
// const版本:返回指向第一个元素的const迭代器
const_iterator begin() const
{
return const_iterator(_head->_next); // 第一个有效元素是_head的下一个节点
}
// const版本:返回指向链表末尾的const迭代器(哨兵节点)
const_iterator end() const
{
return const_iterator(_head);
}
// 普通版本:返回指向第一个元素的迭代器
iterator begin()
{
return _head->_next; // 隐式转换为iterator(利用迭代器构造函数)
}
// 普通版本:返回指向链表末尾的迭代器(哨兵节点)
iterator end()
{
return _head;
}
解析:
begin()
返回指向第一个有效元素的迭代器(_head->_next
),end()
返回指向哨兵节点的迭代器(作为尾后标记)。- 区分普通迭代器和 const 迭代器:const 对象调用 const 版本,确保不能修改元素;非 const 对象调用普通版本,支持读写。
3.3 初始化与构造函数
// 初始化哨兵节点(空链表状态)
void empty_init()
{
_head = new Node; // 创建哨兵节点(数据为默认值,无实际意义)
_head->_next = _head; // 哨兵节点的next指向自身
_head->_prev = _head; // 哨兵节点的prev指向自身
_size = 0; // 初始元素个数为0
}
// 默认构造函数
list()
{
empty_init();
}
// 拷贝构造函数(深拷贝)
list(const list<T>& lt)
{
empty_init(); // 先初始化当前链表为空白状态
// 遍历源链表,将每个元素插入到当前链表尾部
for (auto e : lt)
{
push_back(e);
}
}
// 交换两个list的内容
void swap(list<T>& lt)
{
std::swap(_head, lt._head); // 交换哨兵节点指针
std::swap(_size, lt._size); // 交换元素个数
}
// 赋值运算符重载(现代写法:利用拷贝构造+交换)
list<T>& operator=(list<T> lt) // 传值参数,自动拷贝一份源对象
{
swap(lt); // 交换当前对象与拷贝的临时对象
return *this; // 临时对象销毁时自动释放原数据
}
// 析构函数
~list()
{
clear(); // 清空所有有效元素
delete _head; // 释放哨兵节点
_head = nullptr; // 避免野指针
}
解析:
empty_init()
:初始化哨兵节点形成 “自环”,使空链表的操作与非空链表统一(无需额外判断_head是否为nullptr
)。- 拷贝构造:采用 “深拷贝”,通过遍历源链表插入元素,确保新链表与原链表独立(修改新链表不影响原链表)。
- 赋值运算符:利用 “值传递” 产生临时副本,再通过 swap 交换,既简化实现,又自动处理自赋值问题(临时副本与自身交换不影响)。
- 析构函数:先调用
clear()
删除所有有效节点,再删除哨兵节点,避免内存泄漏。
3.4 元素操作:插入与删除
// 在指定位置插入元素
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node; // 获取迭代器指向的节点
Node* newnode = new Node(x); // 创建新节点
Node* prev = cur->_prev; // 记录当前节点的前一个节点
// 调整指针关系:prev <-> newnode <-> cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
++_size; // 元素个数+1
return iterator(newnode); // 返回指向新节点的迭代器
}
// 删除指定位置的元素
iterator erase(iterator pos)
{
Node* cur = pos._node; // 获取要删除的节点
Node* prev = cur->_prev; // 前一个节点
Node* next = cur->_next; // 后一个节点
delete cur; // 释放当前节点内存
// 调整指针关系:prev <-> next
prev->_next = next;
next->_prev = prev;
--_size; // 元素个数-1
return iterator(next); // 返回指向删除节点下一个位置的迭代器
}
解析:
insert
:通过调整指针将新节点插入到pos
之前,时间复杂度 O (1)(双向链表的优势)。erase
:删除pos
指向的节点后,返回下一个节点的迭代器(原迭代器失效,需用返回值更新)。
3.5 首尾操作(push/pop)
基于insert
和erase
实现的便捷接口:
// 尾插:在end()位置(哨兵节点前)插入
void push_back(const T& x)
{
insert(end(), x);
}
// 头插:在begin()位置(第一个元素前)插入
void push_front(const T& x)
{
insert(begin(), x);
}
// 头删:删除begin()位置的元素
void pop_front()
{
erase(begin());
}
// 尾删:删除end()前一个位置的元素(--end())
void pop_back()
{
erase(--end());
}
解析:
- 利用已实现的
insert
和erase
简化首尾操作,无需重复编写指针调整逻辑,体现代码复用。
3.6 清空与大小
// 清空所有有效元素(保留哨兵节点)
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it); // 循环删除,用返回值更新迭代器(原迭代器失效)
}
}
// 返回元素个数
size_t size()
{
return _size;
}
解析:
clear()
只删除有效元素,保留哨兵节点,使链表仍可继续使用(如需完全销毁需调用析构函数)。size()
直接返回_size
,效率 O (1)(优于遍历计数的 O (n))。
四、辅助结构与函数
用于测试 list 容器的示例结构和打印函数:
// 测试用结构体AA
struct AA
{
AA(int a1 = 0, int a2 = 0)
:_a1(a1)
, _a2(a2)
{}
int _a1;
int _a2;
};
// 打印容器内容的模板函数(支持所有有const迭代器的容器)
template<typename Container>
void print_container(const Container& con)
{
// 注意:需要用typename声明迭代器类型(依赖模板参数)
typename Container::const_iterator it = con.begin();
while (it != con.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
解析:
AA
结构体用于测试 list 存储自定义类型的情况。print_container
是通用打印函数,通过 const 迭代器遍历容器,确保不会修改元素,typename
用于告知编译器Container::const_iterator
是类型(而非静态成员)。
总结
该实现完整模拟了 C++ 标准库中 list 容器的核心功能,采用双向链表 + 哨兵节点设计,通过迭代器封装节点访问,提供了高效的插入、删除操作(O (1))。关键特点包括:
- 哨兵节点简化边界处理;
- 迭代器模板适配 const / 非 const 场景;
- 拷贝构造和赋值运算符实现深拷贝,确保容器独立性;
- 接口设计与标准库一致,便于理解和使用。