跳到主要内容C++ 双向链表完整实现:从节点到迭代器设计 | 极客日志C++算法
C++ 双向链表完整实现:从节点到迭代器设计
综述由AI生成完整实现了 C++ 模板类 yyq::list,模拟 std::list 双向链表。涵盖节点设计、迭代器演进(从双类到单模板)、核心操作(插入删除)、内存管理(RAII、拷贝交换)及改进建议(移动语义、异常安全)。适合学习数据结构与 STL 设计思想。
疯疯癫癫17 浏览 概述
这是一个完整的模板类 yyq::list 的实现,模仿 C++ 标准库中的 std::list。作为 STL 中最经典的双向链表容器,list 的实现展示了 C++ 模板编程、迭代器设计、链表操作和内存管理的核心技术。本文将完整分析所有代码,包括被注释的部分,不遗漏任何细节。
一、整体架构与设计
1.1 命名空间与头文件保护
namespace yyq {
- 使用自定义命名空间
yyq 避免与标准库冲突
- 模板类设计,支持任意类型的元素
#pragma once 防止头文件重复包含
1.2 链表节点设计
template <class T> struct list_node {
T _data;
list_node<T>* _next;
list_node<T>* _prev;
list_node(const T& data = T()) :_data(data), _next(nullptr), _prev(nullptr) {}
};
详细分析:
- 模板结构体:支持任意类型的数据
- 双向指针:
_next 和 _prev 实现双向链接,支持双向遍历
- 默认构造函数:
- 使用
T() 作为默认值,支持内置类型和自定义类型
T() 对于内置类型是零初始化,对于自定义类型调用默认构造函数
- 成员初始化列表:高效初始化成员变量
- 命名约定:使用下划线前缀
_data、_next、_prev,常见于成员变量命名
二、迭代器设计(核心部分)
代码展示了迭代器设计的完整演进过程,分为三个阶段:
2.1 第一阶段:两个独立的迭代器类(被注释的初始设计)
这部分代码被注释掉,展示了最初的实现思路:
2.1.1 普通迭代器 list_iterator<T>
template<class T> {
list_node<T> Node;
list_iterator<T> Self;
Node* _node;
(Node* node) :_node(node) {}
T& *() { _node->_data;
T* ->() { &_node->_data;
list_iterator<T> Self;
Self& ++()
{ _node = _node->_next; *; }
Self ++()
{ Self temp = *; _node = _node->_next; temp; }
Self& --()
{ _node = _node->_prev; *; }
Self --()
{ Self temp = *; _node = _node->_prev; temp; }
==( Self& s) { _node == s._node; }
!=( Self& s) { _node != s._node; }
};
struct
list_iterator
typedef
typedef
list_iterator
operator
return
operator
return
typedef
operator
return
this
operator
int
this
return
operator
return
this
operator
int
this
return
bool
operator
const
const
return
bool
operator
const
const
return
2.1.2 const 迭代器 list_const_iterator<T>
template<class T> struct list_const_iterator {
typedef list_node<T> Node;
typedef list_const_iterator<T> Self;
Node* _node;
list_const_iterator(Node* node) :_node(node) {}
const T& operator*() { return _node->_data;
const T* operator->() { return &_node->_data;
typedef list_iterator<T> Self;
Self& operator++()
{ _node = _node->_next; return *this; }
Self operator++(int)
{ Self temp = *this; _node = _node->_next; return temp; }
Self& operator--()
{ _node = _node->_prev; return *this; }
Self operator--(int)
{ Self temp = *this; _node = _node->_prev; return temp; }
bool operator==(const Self& s)const { return _node == s._node; }
bool operator!=(const Self& s)const { return _node != s._node; }
};
2.1.3 第一阶段设计的详细分析:
- 代码重复问题:
- 两个类几乎完全相同,只有
operator* 和 operator-> 的返回类型不同
list_iterator 返回 T& 和 T*
list_const_iterator 返回 const T& 和 const T*
- 其他所有代码(构造函数、自增自减、比较操作符)都完全相同
- 设计思想:
- 迭代器本质上是指向链表节点的指针
- 通过操作符重载提供类似指针的语法
Self 类型别名简化代码编写
- 优缺点:
- 优点:逻辑清晰,分别处理常量和非常量迭代器
- 缺点:代码重复严重,违反 DRY 原则(Don't Repeat Yourself)
2.2 第二阶段:改进的单一模板迭代器
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) {}
Ref operator*() { return _node->_data;
Ptr operator->() { return &_node->_data;
typedef list_iterator<T, Ref, Ptr> Self;
Self& operator++()
{ _node = _node->_next; return *this; }
Self operator++(int)
{ Self temp = *this; _node = _node->_next; return temp; }
Self& operator--()
{ _node = _node->_prev; return *this; }
Self operator--(int)
{ Self temp = *this; _node = _node->_prev; return temp; }
bool operator==(const Self& s)const { return _node == s._node; }
bool operator!=(const Self& s)const { return _node != s._node; }
};
2.2.1 第二阶段设计的详细分析:
- 模板参数设计:
T:元素类型
Ref:解引用操作符的返回类型(引用)
Ptr:箭头操作符的返回类型(指针)
- 类型别名系统:
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
iterator:传递 T& 和 T*,返回非常量引用和指针
const_iterator:传递 const T& 和 const T*,返回常量引用和指针
- 优势:
- 代码复用:一个模板实现两种迭代器
- 编译时多态:通过模板参数决定行为
- 类型安全:编译时检查类型一致性
- 设计模式:
- 这是模板元编程的典型应用
- 通过模板参数实现策略模式
2.3 迭代器操作符的详细分析
2.3.1 解引用操作符 operator*()
Ref operator*() { return _node->_data; }
- 返回节点数据的引用
Ref 模板参数决定是常量引用还是非常量引用
- 使得
*it 可以访问或修改元素值
2.3.2 箭头操作符 operator->()
Ptr operator->() { return &_node->_data; }
- 返回指向节点数据的指针
Ptr 模板参数决定是常量指针还是非常量指针
- 使得
it->member 可以访问结构体/类成员
2.3.3 自增操作符
Self& operator++() {
_node = _node->_next;
return *this;
}
Self operator++(int) {
Self temp = *this;
_node = _node->_next;
return temp;
}
- 参数
int 仅用于区分前置和后置版本
- 返回临时对象,不能链式调用
- 性能略低于前置版本
2.3.4 自减操作符
2.3.5 比较操作符
bool operator==(const Self& s)const { return _node == s._node; }
bool operator!=(const Self& s)const { return _node != s._node; }
- 比较底层节点指针
- 都是 const 成员函数,支持 const 对象比较
- 简单高效,时间复杂度 O(1)
三、链表类实现
3.1 成员变量
private:
Node* _head;
size_t _size;
- 哨兵节点设计:
_head 指向一个不存储有效数据的节点
- 链表为空时:
_head->_next == _head 且 _head->_prev == _head
- 简化边界条件处理,避免空指针检查
- 大小维护:
- 维护
_size 变量使 size() 操作为 O(1)
- 否则需要遍历链表计算大小,时间复杂度 O(n)
3.2 迭代器访问函数
3.2.1 begin() 和 end()
iterator begin() {
iterator it(_head->_next);
return it;
}
iterator end() {
iterator it(_head);
return it;
}
iterator it(_head->_next);
return it;
return iterator(_head->_next);
return _head->_next;
3.2.2 const 版本的 begin() 和 end()
const_iterator begin()const {
const_iterator it(_head->_next);
return it;
}
const_iterator end()const {
const_iterator it(_head);
return it;
}
- 用于 const 对象
- 返回 const_iterator,只读访问
3.3 初始化函数
void empty_init() {
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
- 所有构造函数的基础
- 创建自循环的哨兵节点
- 注意:哨兵节点的
_data 使用默认构造函数初始化
3.4 构造函数
3.4.1 默认构造函数
3.4.2 初始化列表构造函数
list(initializer_list<T> il) {
empty_init();
for (auto& e : il) {
push_back(e);
}
}
yyq::list<int> lst = {1, 2, 3, 4, 5};
3.4.3 拷贝构造函数
list(const list<T>& lt) {
empty_init();
for (auto& e : lt) {
push_back(e);
}
}
- 深拷贝:创建新节点复制数据
- 使用范围 for 循环:依赖迭代器支持
- 时间复杂度:O(n),n 为源链表大小
- 异常安全:如果 push_back 抛出异常,已分配的资源需要清理
3.5 赋值操作符
3.5.1 交换函数
void swap(list<T>& lt) {
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
- 使用
std::swap 交换指针和大小
- 效率高,不涉及元素复制
3.5.2 赋值操作符(现代实现)
list<T>& operator=(list<T> lt) {
swap(lt);
return *this;
}
- 参数
lt 是值传递,自动调用拷贝构造函数
- 交换当前对象和
lt 的资源
lt 离开作用域时自动析构,释放原资源
- 异常安全:如果拷贝构造失败,不会修改当前对象
- 自赋值安全:参数是值传递,不会影响自赋值
3.6 析构函数
~list() {
clear();
delete _head;
_head = nullptr;
}
- 先调用
clear() 删除所有数据节点
- 再删除哨兵节点
- 将指针置为
nullptr 避免悬空指针
- 注意:对于自定义类型,会调用每个元素的析构函数
3.7 清空链表
void clear() {
auto it = begin();
while (it != end()) {
it = erase(it);
}
}
- 使用
auto:简化类型声明
- 安全删除方式:使用
erase 的返回值更新迭代器
- 避免迭代器失效:直接使用
erase(it) 会使 it 失效
- 时间复杂度:O(n),n 为链表大小
四、链表操作实现
4.1 插入操作
4.1.1 通用插入函数
iterator insert(iterator pos, const T& x) {
Node* newnode = new Node(x);
Node* cur = pos._node;
Node* prev = cur->_prev;
newnode->_next = cur;
cur->_prev = newnode;
newnode->_prev = prev;
prev->_next = newnode;
++_size;
return newnode;
}
newnode->_next = cur:新节点的 next 指向当前位置
cur->_prev = newnode:当前位置的 prev 指向新节点
newnode->_prev = prev:新节点的 prev 指向前一个节点
prev->_next = newnode:前一个节点的 next 指向新节点
返回值:返回指向新插入元素的迭代器,符合 STL 规范
4.1.2 被注释的 push_back 实现
- 直接实现:不依赖 insert
- 效率:可能比
insert(end(), x) 略高(少一次函数调用)
- 代码复用:当前实现使用
insert(end(), x),更简洁
- 逻辑:在哨兵节点前插入,即链表尾部
4.1.3 头部插入
void push_front(const T& x) {
insert(begin(), x);
}
4.1.4 尾部插入
void push_back(const T& x) {
insert(end(), x);
}
注意:插入到 end() 位置实际上是在哨兵节点前插入,即链表尾部
4.2 删除操作
4.2.1 通用删除函数
iterator erase(iterator pos) {
assert(pos != end());
Node* next = pos._node->_next;
Node* prev = pos._node->_prev;
prev->_next = next;
next->_prev = prev;
delete pos._node;
--_size;
return next;
}
- 断言检查:确保不删除哨兵节点
- 连接调整:
prev->_next = next:前一个节点跳过当前节点
next->_prev = prev:后一个节点跳过当前节点
- 内存释放:
delete 节点,调用元素析构函数
- 返回值:返回下一个有效迭代器,避免迭代器失效
4.2.2 被注释的 erase 实现
- 没有返回值:调用者无法知道删除后的下一个有效位置
- 迭代器失效:调用后
pos 失效,但调用者不知道
- 当前实现更好:返回下一个迭代器,更安全
4.2.3 头部删除
void pop_front() {
erase(begin());
}
4.2.4 尾部删除
void pop_back() {
erase(--end());
}
erase(--end()):使用迭代器操作
erase(end()._node->_prev):直接访问节点指针
4.3 容量相关函数
size_t size()const { return _size;
bool empty()const { return _size == 0;
- O(1) 复杂度:维护
_size 变量
- 注释中的替代方案:
_head->_next == _head 检查链表是否为空
- const 成员函数:可以用于 const 对象
五、辅助函数
5.1 通用容器打印函数
template<class Container>
void print_container(const Container& con) {
typename Container::const_iterator it = con.begin();
while (it != con.end()) {
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : con) {
cout << e << " ";
}
cout << endl;
}
- 模板函数:可以打印任何支持迭代器的容器
typename 关键字:
- 必须使用,告诉编译器
Container::const_iterator 是一个类型
- 因为
Container 是模板参数,编译器不知道 const_iterator 是类型还是静态成员
- Bug 修复:原代码 while 循环中缺少
++it,会导致死循环,此处已修正
- 两种遍历方式:
- 传统迭代器遍历
- 范围 for 循环(依赖容器的 begin() 和 end())
- 通用性:可以用于 yyq::list、yyq::vector 等任何 STL 兼容容器
六、设计模式与编程技巧
6.1 哨兵节点模式
- 目的:简化边界条件处理
- 实现:创建不存储数据的头节点
- 优点:统一插入删除逻辑,避免空指针检查
6.2 拷贝并交换惯用法
- 目的:实现异常安全的赋值操作
- 实现:参数值传递 + swap
- 优点:自动处理自赋值,异常安全
6.3 模板元编程
- 目的:代码复用,编译时多态
- 实现:迭代器模板参数化
- 优点:消除代码重复,类型安全
6.4 RAII(资源获取即初始化)
- 目的:自动管理资源
- 实现:构造函数分配,析构函数释放
- 优点:避免资源泄漏
七、存在的问题与改进建议
7.1 存在的问题
- 异常安全性不足:
new 可能失败,抛出 std::bad_alloc
- 插入操作中如果
new 失败,应保持链表不变
- 缺少移动语义支持(C++11):
- 移动构造函数
- 移动赋值运算符
- emplace 操作
- 迭代器安全性:
- 功能不完整:
- 缺少反向迭代器
- 缺少
sort、merge、unique 等算法
- 缺少
resize、assign 等方法
7.2 改进建议
7.2.1 异常安全改进
iterator insert(iterator pos, const T& x) {
Node* newnode = nullptr;
try {
newnode = new Node(x);
} catch (...) {
throw;
}
return iterator(newnode);
}
7.2.2 添加移动语义支持
list(list&& other) noexcept : _head(other._head), _size(other._size) {
other._head = nullptr;
other._size = 0;
}
list& operator=(list&& other) noexcept {
if (this != &other) {
clear();
delete _head;
_head = other._head;
_size = other._size;
other._head = nullptr;
other._size = 0;
}
return *this;
}
template <typename... Args>
iterator emplace(iterator pos, Args&&... args) {
Node* newnode = new Node(T(std::forward<Args>(args)...));
return iterator(newnode);
}
7.2.3 添加反向迭代器
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
reverse_iterator rbegin() {
return reverse_iterator(end());
}
reverse_iterator rend() {
return reverse_iterator(begin());
}
const_reverse_iterator rbegin() const {
return const_reverse_iterator(end());
}
const_reverse_iterator rend() const {
return const_reverse_iterator(begin());
}
八、教学价值与学习要点
8.1 数据结构学习
- 双向链表的基本操作
- 哨兵节点的优势与应用
- 链表与数组的性能对比
8.2 C++ 模板编程
8.3 STL 设计思想
8.4 现代 C++ 特性
九、总结
这个 yyq::list 实现是一个优秀的教学范例,它完整展示了:
- 从简单到复杂的演进过程:
- 初始的两个独立迭代器类
- 改进的模板化迭代器
- 展示了代码重构和优化的思路
- 完整的功能实现:
- 链表节点的创建与连接
- 迭代器的完整实现
- 基本操作的实现
- 拷贝控制和资源管理
- 现代 C++ 编程实践:
- STL 兼容性设计:
虽然与标准库的 std::list 相比还有差距,但作为一个教学实现,它完美展示了双向链表和迭代器的核心原理,是学习 C++ 容器设计和模板编程的优秀范例。通过分析这个实现,可以深入理解 STL 的设计哲学和实现细节。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online