📌 相关专栏
-
【C++ 专栏】
📌 相关文章推荐
很高兴你点开这篇文章✨
这里会持续更新更多有用的内容,关注我,一起慢慢变好呀
👍 点赞 ⭐ 收藏 💬 评论
文章目录
- 前言
- [1. 整体架构:命名空间与节点结构](#1. 整体架构:命名空间与节点结构)
-
- [1.1 命名空间封装](#1.1 命名空间封装)
- [1.2 链表节点结构体](#1.2 链表节点结构体)
- [1.3 节点关系图](#1.3 节点关系图)
- [2. 哨兵位头节点与循环链表设计](#2. 哨兵位头节点与循环链表设计)
-
- [2.1 什么是哨兵位头节点?](#2.1 什么是哨兵位头节点?)
- [2.2 空链表初始化](#2.2 空链表初始化)
- [3. 迭代器设计:三模板参数复用](#3. 迭代器设计:三模板参数复用)
-
- [3.1 为什么迭代器需要封装?](#3.1 为什么迭代器需要封装?)
- [3.2 三模板参数设计(核心亮点)](#3.2 三模板参数设计(核心亮点))
- [3.3 为什么这样设计?](#3.3 为什么这样设计?)
- [4. 迭代器运算符重载(模拟指针行为)](#4. 迭代器运算符重载(模拟指针行为))
-
- [4.1 解引用 operator*](#4.1 解引用 operator*)
- [4.2 箭头 operator->](#4.2 箭头 operator->)
- [4.3 前置 ++ / 后置 ++](#4.3 前置 ++ / 后置 ++)
- [4.4 前置 -- / 后置 --](#4.4 前置 -- / 后置 --)
- [4.5 比较运算符](#4.5 比较运算符)
- [5. list 类的主体框架与构造](#5. list 类的主体框架与构造)
-
- [5.1 类成员变量](#5.1 类成员变量)
- [5.2 默认构造](#5.2 默认构造)
- [5.3 初始化列表构造](#5.3 初始化列表构造)
- [5.4 迭代器区间构造(函数模板)](#5.4 迭代器区间构造(函数模板))
- [5.5 析构函数](#5.5 析构函数)
- [6. 增删操作:insert、erase 与复用](#6. 增删操作:insert、erase 与复用)
-
- [6.1 insert:任意位置插入(O(1))](#6.1 insert:任意位置插入(O(1)))
- [6.2 erase:任意位置删除(O(1))](#6.2 erase:任意位置删除(O(1)))
- [6.3 复用:push_back、push_front、pop_back、pop_front](#6.3 复用:push_back、push_front、pop_back、pop_front)
- [6.4 clear:清空所有数据节点](#6.4 clear:清空所有数据节点)
- [7. 迭代器接口与 const 版本](#7. 迭代器接口与 const 版本)
- [8. 完整测试用例](#8. 完整测试用例)
-
- [test 1 :基础插入与遍历](#test 1 :基础插入与遍历)
- [test 2 :头插尾插与头删尾删](#test 2 :头插尾插与头删尾删)
- [test 3 :拷贝构造与 const 迭代器](#test 3 :拷贝构造与 const 迭代器)
- [test 4 :insert、erase 与 clear](#test 4 :insert、erase 与 clear)
- [9. 总结](#9. 总结)
- 本文全部代码
-
- [🐾 list.h](#🐾 list.h)
- [🐾 Test.cpp](#🐾 Test.cpp)
前言
std::list 是 C++ 标准库中的双向链表容器。与 vector 不同,list 的元素在内存中不连续存储,而是通过指针链接。实现一个正确的 list 需要解决以下核心问题:
- 节点结构:
如何存储数据与前驱后继指针?- 哨兵位头节点:
如何简化边界条件处理?- 迭代器设计:
如何用类封装节点指针,模拟指针行为?- 普通/const 迭代器复用:
如何用一套代码同时生成两种迭代器?
🐶 🐾 ✨ 🐾 🐶
1. 整体架构:命名空间与节点结构
1.1 命名空间封装
为避免与标准库 std::list 冲突,我们将实现放在自定义命名空间中:
cpp
namespace QianYv // 避免与 std::list 冲突
{
// list 实现...
}
1.2 链表节点结构体
节点是链表的基本单元,包含前驱指针、后继指针和存储的数据:
cpp
template<class T>
struct list_node
{
list_node<T>* _prev; // 前驱节点指针
list_node<T>* _next; // 后继节点指针
T _data; // 节点存储的数据
// 构造函数:默认值初始化,指针置空
list_node(const T& x = T())
: _prev(nullptr)
, _next(nullptr)
, _data(x)
{
}
};
关于 T() 的解读:
当 T 是 int 时,T() 就是 0当 T 是 string 时,T() 调用其默认构造函数- 这确保了无论是内置类型还是自定义类型,都能正确初始化
1.3 节点关系图

🐶 🐾 ✨ 🐾 🐶
2. 哨兵位头节点与循环链表设计
2.1 什么是哨兵位头节点?
哨兵位头节点是一个不存储有效数据的节点,它的作用是:
- 简化插入删除:
无需特殊判断是否为空链表 - 统一边界条件:
begin() 指向 _head->_next,end() 指向 _head - 实现循环结构:
空链表时,_head->_prev = _head->_next = _head
2.2 空链表初始化
cpp
void empty_init()
{
_head = new Node; // 创建哨兵头节点
_head->_prev = _head; // 前驱指向自己
_head->_next = _head; // 后继指向自己(形成自环)
_size = 0; // 有效元素个数为 0
}
list()
{
empty_init(); // 默认构造调用统一初始化
}
空链表结构:
🐶 🐾 ✨ 🐾 🐶
3. 迭代器设计:三模板参数复用
3.1 为什么迭代器需要封装?
list 的节点指针 node* 不能直接作为迭代器使用,因为:
- 原生指针 ++ 操作是移动一个
sizeof(Node),而链表需要移动到_next - 我们需要重载
++、--、*、->等运算符来模拟指针行为
3.2 三模板参数设计(核心亮点)
cpp
// 迭代器类:通过三个模板参数实现普通/const 迭代器复用
template<class T, class Ref, class Ptr>
struct list_iterator
{
using Self = list_iterator<T, Ref, Ptr>; // 自身类型别名
using Node = list_node<T>; // 节点类型别名
Node* _node; // 封装的节点指针
list_iterator(Node* node) : _node(node) {}
// ... 运算符重载 ...
};
三模板参数的含义:
| 模板参数 | 普通迭代器 | const 迭代器 | 作用 |
|---|---|---|---|
T |
T | T | 节点数据类型 |
Ref |
T& | const T& | operator* 返回类型 |
Ptr |
T* | const T* | operator-> 返回类型 |
3.3 为什么这样设计?
设计原理 :用一套迭代器代码,同时生成普通迭代器和 const 迭代器,避免重复写两份几乎相同的类。
cpp
using iterator = list_iterator<T, T&, T*>; // 普通迭代器
using const_iterator = list_iterator<T, const T&, const T*>; // const 迭代器
🐶 🐾 ✨ 🐾 🐶
4. 迭代器运算符重载(模拟指针行为)
4.1 解引用 operator*
cpp
Ref operator*()
{
return _node->_data; // 返回节点数据的引用
}
- 普通迭代器:
返回 T&,可修改 - const 迭代器:
返回 const T&,只读
4.2 箭头 operator->
cpp
Ptr operator->()
{
return &_node->_data; // 返回数据指针
}
🐾如果 T 是自定义类型,it->member 等价于 (*it).member
4.3 前置 ++ / 后置 ++
cpp
// 前置++:返回移动后的迭代器
Self& operator++()
{
_node = _node->_next;
return *this;
}
// 后置++:返回移动前的副本,然后移动
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
4.4 前置 -- / 后置 --
cpp
// 前置--:向前移动
Self& operator--()
{
_node = _node->_prev;
return *this;
}
// 后置--:返回移动前的副本
Self operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
4.5 比较运算符
cpp
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
🐶 🐾 ✨ 🐾 🐶
5. list 类的主体框架与构造
5.1 类成员变量
cpp
template<class T>
class list
{
using Node = list_node<T>;
private:
Node* _head; // 哨兵头节点指针
size_t _size = 0; // 有效数据节点个数
};
5.2 默认构造
cpp
list()
{
empty_init(); // 创建哨兵位头节点,形成自环
}
5.3 初始化列表构造
cpp
list(initializer_list<T> il)
{
empty_init();
for (auto& e : il)
{
push_back(e);
}
}
5.4 迭代器区间构造(函数模板)
cpp
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
使用 InputIterator 而非具体类型,可以接受任意容器的迭代器(vector、array、原生指针等)。
5.5 析构函数
cpp
~list()
{
clear(); // 先删除所有数据节点
delete _head; // 再删除哨兵头节点
_head = nullptr; // 置空避免野指针
_size = 0;
}
🐶 🐾 ✨ 🐾 🐶
6. 增删操作:insert、erase 与复用
6.1 insert:任意位置插入(O(1))
cpp
void insert(iterator pos, const T& x)
{
Node* cur = pos._node; // pos 指向的当前节点
Node* prev = cur->_prev; // 当前节点的前驱
Node* newnode = new Node(x); // 新建节点
// 调整指针:prev <-> newnode <-> cur
newnode->_prev = prev;
newnode->_next = cur;
prev->_next = newnode;
cur->_prev = newnode;
++_size;
}
6.2 erase:任意位置删除(O(1))
cpp
iterator erase(iterator pos)
{
assert(pos != end()); // 不能删除哨兵位
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
prev->_next = next; // 前驱指向后继
next->_prev = prev; // 后继指向前驱
delete pos._node; // 释放节点
--_size;
return next; // 返回被删除节点的下一个位置(避免迭代器失效)
}
6.3 复用:push_back、push_front、pop_back、pop_front
cpp
void push_back(const T& x) { insert(end(), x); } // 尾插:在哨兵位前插入
void push_front(const T& x) { insert(begin(), x); } // 头插:在第一个节点前插入
void pop_back() { erase(--end()); } // 尾删:删除最后一个节点
void pop_front() { erase(begin()); } // 头删:删除第一个节点
6.4 clear:清空所有数据节点
cpp
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it); // 逐个删除,it 自动更新
}
}
注意 :clear 只删除数据节点,保留哨兵位头节点,便于后续复用。
🐶 🐾 ✨ 🐾 🐶
7. 迭代器接口与 const 版本
cpp
// 普通迭代器
iterator begin() { return iterator(_head->_next); }
iterator end() { return iterator(_head); }
// const 迭代器(只读)
const_iterator begin() const { return const_iterator(_head->_next); }
const_iterator end() const { return const_iterator(_head); }
- begin()
指向第一个有效数据节点(_head->_next) - end()
指向哨兵位头节点(_head),作为遍历结束标志
🐶 🐾 ✨ 🐾 🐶
8. 完整测试用例
test 1 :基础插入与遍历
cpp
void test_list1()
{
QianYv::list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
QianYv::list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " "; // 1 2 3 4
++it;
}
cout << endl;
for (auto e : lt) // 范围 for
{
cout << e << " "; // 1 2 3 4
}
}
test 2 :头插尾插与头删尾删
cpp
void test_list2()
{
QianYv::list<int> lt;
lt.push_front(-2);
lt.push_front(-1);
lt.push_back(1);
lt.push_back(2);
// 预期:-1 -2 1 2
lt.pop_back(); // 删除 2
lt.pop_back(); // 删除 1
lt.pop_front(); // 删除 -1
lt.pop_front(); // 删除 -2
cout << lt.size(); // 0
}
test 3 :拷贝构造与 const 迭代器
cpp
void Print(const QianYv::list<int>& lt)
{
QianYv::list<int>::const_iterator it = lt.begin();
while (it != lt.end())
{
// *it = 10; // ❌ 编译报错:const 迭代器不可修改
cout << *it << " ";
++it;
}
}
void test_list3()
{
QianYv::list<int> lt1 = { 1, 2, 3, 4, 5, 6 };
QianYv::list<int> lt2(lt1); // 拷贝构造
Print(lt1); // 1 2 3 4 5 6
}
test 4 :insert、erase 与 clear
cpp
void test_list4()
{
QianYv::list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
// 在第二个位置插入 100
auto it = lt.begin();
++it;
lt.insert(it, 100); // 1 100 2 3 4
// 删除 100
it = lt.begin();
++it;
it = lt.erase(it); // 1 2 3 4
lt.clear();
cout << lt.size(); // 0
}
🐶 🐾 ✨ 🐾 🐶
9. 总结
核心知识点:
| 模块 | 关键实现 |
|---|---|
| 节点结构 | _prev、_next、_data |
| 哨兵位设计 | 空链表自环,简化边界条件 |
| 迭代器封装 | 类封装节点指针,重载运算符模拟指针 |
| 三模板参数 | 一套代码同时生成普通/const 迭代器 |
| 插入删除 | insert/erase 的 O(1) 指针操作 |
| 接口复用 | push_back 复用 insert(end()) |
与 vector 的对比
| 特性 | vector | list |
|---|---|---|
内存布局 |
连续数组 | 分散节点 + 指针链接 |
迭代器类型 |
随机迭代器 | (支持 +n) |
中间插入/删除 |
O(n) 需移动元素 | O(1) 只需改指针 |
迭代器失效 |
扩容时全部失效 | 仅被删除节点失效 |
适用场景 |
频繁随机访问 | 频繁中间插入/删除 |
🐶 🐾 ✨ 🐾 🐶
本文全部代码
🐾 list.h
cpp
#pragma once
#include<iostream>
#include<list>
#include<cassert>
using namespace std;
//模板参数T:支持任意数据类型(替代)----QianYv::list<int>存储整数、QianYv::<string>存储字符串,与string泛型设计一致
//默认构造函数:T()确保内置类型(如int)默认初始化为0,自定义类型自动调用其默认构造函数
//指针初始化:_prev和_next初始化为nullptr,避免野指针风险,后续由容器类统一管理
namespace QianYv //避免与 std::list冲突
{
//链表节点结构:存储数据与双向指针
template<class T>
//定义链表节点结构体
struct list_node
{
//成员变量
list_node<T>* _prev; // 前驱节点指针
list_node<T>* _next; // 后继节点指针
T _data; // 节点数据,用来存数据(int、string、对象...)
// 构造函数:默认值初始化,指针置空
list_node(const T& x = T())
//T()确保内置类型(如int)默认初始化为0,自定义类型自动调用其默认构造函数
: _prev(nullptr)
, _next(nullptr)
, _data(x)
{
}
};
//迭代器类:T->数据类型,Ref->引用类型(T& 或 const T&),Ptr->指针类型(T* 或 const T*)
template<class T, class Ref, class Ptr>
//T:接节点储存的数据类型(int...)
//Ref:数据引用的类型(普通迭代器是T&,const迭代器是const T&)
//Ptr:数据的指针类型(普通迭代器是T*,cosnt迭代器是const T*)
//这样设计的原理:为了复用一套代码,同时生成两种迭代器,避免重复写
//1.普通迭代器
//list_iterator的核心封装结构,把底层节点指针包装成一把智能指针,让用户可以用像指针一样的语法去访问链表
struct list_iterator
{
using Self = list_iterator<T, Ref, Ptr>; // 简化自身类型名
using Node = list_node<T>; // 节点类型别名
Node* _node; // _node本质是封装节点指针,模拟指针行动
// 迭代器构造:接收节点指针(_node),来初始化迭代器
list_iterator(Node* node)
: _node(node)
{
}
// 1.重载解引用operator*:获取节点数据(普通迭代器可修改,const不可)
//Ref:数据引用的类型(普通迭代器是T&,const迭代器是const T&)
Ref operator*()
{
return _node->_data;
}
// 2.重载箭头operator->:支持访问自定义类型(如struct.field)
Ptr operator->()
{
return &_node->_data;
}
// 3. 重载前置++:实现迭代器向后移动(指向后继节点)
Self& operator++()
{
_node = _node->_next;
return *this;
}
// 4. 重载后置++:先返回当前,再移动
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
// 5. 重载前置--:向前移动(指向前驱节点)
Self& operator--()
{
_node = _node->_prev;
return *this;
}
// 6. 重载后置--:先返回当前,再移动
Self operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
// 7. 重载相等判断:比较底层节点指针是否相同,用于遍历结束判断
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
};
//2.const迭代器
//list的迭代器不是原生指针而是封装list_node*的类,通过运算符重载模拟指针行为,
//同时"三模版参数"复用普通/const迭代器
template<class T>
class list
{
using Node = list_node<T>; // 节点类型别名:Node
//typedef List_node<T> Node;
public:
// 类型重定义:普通/const迭代器(复用list_iterator)
using iterator = list_iterator<T, T&, T*>;
using const_iterator = list_iterator<T, const T&, const T*>;
// typedef list_iterator<T,T&,T*> iterator;
// typedef list_iterator<T,const T&,const T*>
// -------------------------- 迭代器接口 --------------------------
//_head:哨兵位头节点
//begin()返回第一个数据节点的迭代器
iterator begin()
{
return iterator(_head->_next);
}
//end()返回哨兵位头节点的迭代器
iterator end()
{
return iterator(_head);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
// -------------------------- 初始化接口 --------------------------
// 1.空链表初始化:创建哨兵头节点,形成自环
void empty_init()
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
_size = 0;
}
// 2.默认构造
//所有构造都先调用empty_init()统一初始化逻辑
list()
{
empty_init();//创建哨兵位头节点,使其前后指针指向自身,指向循环链表
}
// 3.初始化列表构造(支持{1,2,3}形式)
list(initializer_list<T> il)
{
empty_init();
for (auto& e : il)
{
push_back(e);
}
}
//4.范围构造函数(支持[first, last)区间)
//模板参数InputTterator:支持任意输入跌打起(可以是数组指针,其他容器的迭代器等)
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
//5. 析构函数:释放所有节点,避免内存泄漏
~list()
{
clear(); // 先删除所有数据节点
delete _head; // 再删除哨兵头节点
_head = nullptr; // 置空指针,避免野指针
_size = 0;
}
// -------------------------- 插入删除接口 --------------------------
// 尾插:复用insert,简化代码
void push_back(const T& x) { insert(end(), x); }
// 头插:复用insert
void push_front(const T& x) { insert(begin(), x); }
// 尾删:复用erase
void pop_back() { erase(--end()); }
// 头删:复用erase
void pop_front() { erase(begin()); }
// 任意位置插入:调整指针实现O(1)插入
void insert(iterator pos, const T& x)
{
Node* cur = pos._node; // pos指向的当前节点
Node* prev = cur->_prev; // 当前节点的前驱
Node* newnode = new Node(x); // 新建数据节点
// 调整指针:prev <-> newnode <-> cur
newnode->_prev = prev;
newnode->_next = cur;
prev->_next = newnode;
cur->_prev = newnode;
++_size; // 有效元素个数递增
}
//任意位置删除
iterator erase(iterator pos)
{
assert(pos != end());
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
prev->_next = next;
next->_prev = prev;
delete pos._node;
--_size;
return next; // 返回合法的下一个迭代器
}
// 清空容器:保留哨兵头节点,便于后续复用
void clear()
{
iterator it = begin();
while (it != end()) it = erase(it);
}
// -------------------------- 其他接口 --------------------------
// 获取有效元素个数
size_t size() const { return _size; }
private:
Node* _head; // 哨兵头节点指针
size_t _size = 0; // 有效数据节点个数
};
}
🐾 Test.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"list.h"
using namespace std;
void test_list1()
{
QianYv::list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
QianYv::list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
//int main()
//{
// test_list1();
// return 0;
//}
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void test_list2()
{
QianYv::list<int> lt;
// 头插2个元素,尾插2个元素
lt.push_front(-2);
lt.push_front(-1);
lt.push_back(1);
lt.push_back(2);
cout << "头插+尾插后:";
for (auto e : lt)
{
cout << e << " "; // 预期输出:-1 -2 1 2
}
cout << endl;
// 尾删2次,头删2次
lt.pop_back();
lt.pop_back();
lt.pop_front();
lt.pop_front();
cout << "删除后size:" << lt.size() << endl; // 预期输出:0
cout << endl;
}
//
//int main()
//{
// test_list2();
// return 0;
//}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 用const迭代器遍历的打印函数(验证只读特性)
void Print(const QianYv::list<int>& lt)
{
QianYv::list<int>::const_iterator it = lt.begin();
while (it != lt.end())
{
// *it = 10; // 编译报错:const迭代器不可修改数据
cout << *it << " ";
++it;
}
cout << endl;
}
void test_list3()
{
// 初始化列表构造
QianYv::list<int> lt1 = { 1,2,3,4,5,6 };
// 拷贝构造
QianYv::list<int> lt2(lt1);
cout << "lt2(拷贝lt1):";
for (auto e : lt2)
{
cout << e << " "; // 预期输出:1 2 3 4 5 6
}
cout << endl;
// const迭代器遍历
const QianYv::list<int>& clt = lt1;
cout << "const迭代器遍历lt1:";
Print(clt); // 预期输出:1 2 3 4 5 6
cout << endl;
}
//int main()
//{
// test_list3();
// return 0;
//}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
void test_list4()
{
QianYv::list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
// 任意位置插入
auto it = lt.begin();
++it; //在第2个元素(2)前插入100
lt.insert(it, 100);
cout << "插入100后:";
for (auto e : lt) cout << e << " "; // 预期输出:1 100 2 3 4
cout << endl;
// 任意位置删除:删除100
it = lt.begin();
++it; //删除第2个元素:100
it = lt.erase(it);
cout << "删除100后:";
for (auto e : lt) cout << e << " "; // 预期输出:1 2 3 4
cout << endl;
// 清空容器
lt.clear();
cout << "clear后size:" << lt.size() << endl; // 预期输出:0
}
int main()
{
test_list4();
return 0;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//int main()
//{
// test_list1();
// test_list2();
// test_list3();
// test_list4();
// return 0;
//}
- 欢迎留言交流
- 期待你的评论与建议
- 留下你的想法吧

谢谢你看到这里呀
如果喜欢这篇内容,点个关注,下次更新不迷路✨
👍 点赞 ⭐ 收藏 💬 评论

