
🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》
✨逆境不吐心中苦,顺境不忘来时路! 🎬 博主简介:

引言:前篇文章,小编已经介绍了关于C++Vector类的相关知识.接下来我将带领大家继续深入学习C++的相关内容!本篇文章着重介绍关于C++List类以及实现List类的接口,那么这里面到底有哪些知识需要我们去学习的呢?废话不多说,带着这些疑问,下面跟着小编的节奏🎵一起学习吧!
目录
- [1. 为什么要学习List类?](#1. 为什么要学习List类?)
- [2. List类的了解](#2. List类的了解)
-
- [2.1 List类的官方文档核心说明](#2.1 List类的官方文档核心说明)
- [2.2 List类的构造函数(constructor)声明](#2.2 List类的构造函数(constructor)声明)
- [3. List的iterator定义以及相关接口](#3. List的iterator定义以及相关接口)
-
- [3.1 List的iterator使用--->begin介绍](#3.1 List的iterator使用—>begin介绍)
- [3.2 List的iterator使用--->end介绍](#3.2 List的iterator使用—>end介绍)
- [3.3 List的iterator使用--->rbegin介绍](#3.3 List的iterator使用—>rbegin介绍)
- [3.4 List的iterator使用--->rend介绍](#3.4 List的iterator使用—>rend介绍)
- 4.List的capacity
-
- [4.1 empty介绍](#4.1 empty介绍)
- [4.2 size介绍](#4.2 size介绍)
- [5. List的element access](#5. List的element access)
-
- [5.1 font介绍](#5.1 font介绍)
- [5.2 back介绍](#5.2 back介绍)
- [6. List的modifiers](#6. List的modifiers)
-
- [6.1 push_front介绍](#6.1 push_front介绍)
- [6.2 pop_front介绍](#6.2 pop_front介绍)
- [6.4 push_back介绍](#6.4 push_back介绍)
- [6.5 pop_back介绍](#6.5 pop_back介绍)
- [6.6 insert介绍](#6.6 insert介绍)
- [6.7 erase介绍](#6.7 erase介绍)
- [6.8 swap介绍](#6.8 swap介绍)
- [6.9 clear介绍](#6.9 clear介绍)
- [7. List的迭代器失效问题](#7. List的迭代器失效问题)
- [8. List的反向迭代器](#8. List的反向迭代器)
- [9. List与vector的对比](#9. List与vector的对比)
- [10. List的核心框架接口的模拟实现](#10. List的核心框架接口的模拟实现)
1. 为什么要学习List类?
std::list是C++ STL中基于双向循环链表实现的序列式容器,学习它不只是掌握一个工具,更能帮你理解数据结构的实际应用和STL的设计逻辑.下面说明学习它的核心原因:
1️⃣把"链表"理论落地,理解工程级实现
很多新手学数据结构时,只知道"链表是由节点和指针组成、插入删除快"的抽象概念,但自己手写的链表往往简陋(比如只支持基础插入删除,没有异常处理、迭代器封装).
而std::list是工业级的双向链表实现:
它封装了链表的底层细节(节点创建、内存管理、指针维护),你不用关心new/delete,只需调用接口(push_back/insert/erase)就能使用;通过使用list,你能直观感受到"链表插入删除不移动其他元素"的特性,对比数组类容器(如vector)的差异,真正理解O(1)(链表任意位置插入删除)和O(n)(数组中间插入删除)时间复杂度的实际意义.
2️⃣掌握"容器选择"的核心逻辑
C++ STL提供了多种容器(vector/list/deque/set等),核心是让你"按需选择".学习list能帮你建立"场景匹配容器"的思维:
| 特性 | std::list |
std::vector |
|---|---|---|
| 内存布局 | 非连续内存(节点分散) | 连续内存 |
随机访问([]/at) |
不支持(只能逐个遍历) | 支持(O(1)) |
| 中间插入/删除 | O(1)(有迭代器时) |
O(n)(需移动后续元素) |
| 迭代器失效 | 仅删除节点的迭代器失效 | 扩容/中间插入时全部失效 |
典型场景 :如果你需要频繁在容器中间/头部插入删除元素(比如任务队列、游戏中的角色列表、缓存淘汰列表),
list比vector高效得多;而如果需要频繁随机访问(比如按索引取元素),则优先用vector.
3️⃣掌握链表特有的操作和迭代器特性
list有很多数组类容器没有的专属功能,学习这些能加深对STL的理解:
专属成员函数 :比如sort()(链表专属排序,比通用std::sort更高效)、splice()(直接拼接两个list,无需拷贝元素,O(1))、merge()(合并有序list)、remove()(按值删除元素),这些都是利用链表结构设计的高效操作;
迭代器特性 :list的迭代器是双向迭代器 (只能++/--),而非vector的"随机访问迭代器"(可+n/-n),这能帮你理解STL迭代器的分类规则,避免误用(比如不能用std::sort直接排序list,因为std::sort需要随机访问迭代器).
4️⃣为学习其他STL容器打基础
掌握list后,你能快速理解std::forward_list(单向链表,更轻量,仅支持前向迭代),也能举一反三理解STL容器的通用设计:
所有容器都有通用接口(begin()/end()/size()/empty());
特化接口(如list::splice、vector::reserve)都是为了适配自身数据结构的特性.
⭐️5️⃣总结⭐️
学习std::list的核心价值可以归纳为3点:
①理解数据结构 :把"双向链表"的理论转化为实际使用经验,掌握链表的核心特性(插入删除高效、无随机访问);
②学会容器选择 :建立"场景匹配容器"的思维,避免无脑使用vector;
③深化STL理解 :掌握迭代器分类、容器通用/特化接口的设计逻辑,为学习其他STL容器打基础.
简单来说,不学list,你可能只会用vector解决所有问题,但学了list,你才真正开始理解"为什么STL要提供多种容器",也能写出更高效、更贴合场景的代码.
2. List类的了解
list的文档介绍
接下来关于 List类的学习我会通过上面的文档链接进行介绍和学习.
学习时一定要学会查看 List文档: List在实际中非常的重要,在实际中我们熟悉常见的接口就可以.
list中的接口比较多,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力.下面我将介绍list中一些常见的重要接口.方便大家思考和学习!
2.1 List类的官方文档核心说明

这张是C++标准库中
std::list的官方文档说明.
🔹开头定义template < class T, class Alloc = allocator > class list;
这说明
std::list是一个类模板 ,需要包含头文件<list>才能使用.模板参数:
T:你要存储的元素类型(比如int、std::string).
Alloc:内存分配器(默认用标准的allocator<T>,一般不需要手动指定).
🔹核心定位与特性
序列式容器 :元素按插入顺序存储,保持线性排列.
核心优势 :①O(1) 时间插入/删除 :只要拿到目标位置的迭代器,在序列任意位置插入或删除元素都是常数时间.
②双向迭代 :可以从前往后、从后往前遍历元素.
🔹底层实现
std::list底层是双向循环链表 (doubly-linked list).每个元素存放在独立、不连续的内存地址中,靠两个指针维持顺序:
一个指针指向前一个元素(
prev)一个指针指向后一个元素(
next)
🔹和forward_list的区别
forward_list是单向链表 ,只能向前迭代(不能反向遍历),但内存开销更小、更轻量.
std::list是双向链表,支持双向迭代,但每个节点多一个prev指针,内存开销稍大.
🔹和其他基础容器(array/vector/deque)的对比✅ 优势
在已有迭代器的位置插入、删除、移动元素时,性能远优于
vector/deque(因为不需要移动其他元素),适合频繁修改元素位置的场景(比如排序算法).❌ 劣势
①无随机访问 :不能用
[]或at()直接按索引取元素(比如要访问第6个元素,必须从头遍历到第6个位置,时间复杂度 O(n)).②额外内存开销 :每个元素要存两个指针(
prev/next),如果存储大量小元素(比如char),这部分开销会很明显.
🔹总结
std::list是为频繁插入/删除、无需随机访问 的场景设计的.如果你需要随机访问(比如按索引取元素),优先选
vector;如果需要极致轻量的单向遍历,选forward_list.
2.2 List类的构造函数(constructor)声明
这是C++标准库中std::list的 构造函数(constructor)文档,详细说明了创建std::list对象的四种核心方式,以及不同C++版本的支持情况.
🔹构造函数分类与解析
1️⃣默认构造函数(empty container constructor)
功能 :创建一个空的list容器,不包含任何元素.
语法 :explicit list (const allocator_type& alloc = allocator_type());
说明 :可选参数alloc是内存分配器,默认使用标准分配器,一般无需手动指定.
代码示例 :std::list my_list; // 创建空的int类型list
2️⃣填充构造函数(fill constructor)
功能 :创建包含n个元素的list,每个元素都是val的副本(不指定val时,使用元素类型的默认值初始化).
语法 :explicit list (size_type n, const value_type& val = value_type(), const allocator_type& alloc = allocator_type());
说明 :n是元素数量,val是初始化值,可选的alloc是分配器.
代码示例 :std::list num_list(5, 10); // 包含5个10的list:[10,10,10,10,10]
std::liststd::stringstr_list(3); // 包含3个默认空字符串的list
3️⃣范围构造函数(range constructor)
功能 :根据迭代器范围[first, last)创建list,将该范围内的元素按顺序复制到新list中.
语法 :template <class InputIterator> list (InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type());
说明 :适用于从其他容器(如vector、array)或数组的迭代器范围初始化list.
代码示例 :std::vector vec = {1,2,3,4,5};
std::list list_from_vec(vec.begin(), vec.end()); // 从vector复制元素:[1,2,3,4,5]
4️⃣拷贝构造函数(copy constructor)
功能 :创建新list,是另一个list对象x的副本,包含x的所有元素且顺序相同.
语法 :list (const list& x);
说明 :新list会复制原容器的元素和内存分配器.
代码示例 :std::list original = {10,20,30};
std::list copy_list(original); // 拷贝original的元素:[10,20,30]
🔹文档补充说明
内存分配器 :容器会保存一个内部的分配器副本,在整个生命周期中用于分配内存;拷贝构造函数会复制原容器的分配器.
版本支持 :这四种构造函数在C++98中就已支持,后续C++11、C++14等版本保持兼容,仅对部分细节做了优化.
🔹核心作用这四种构造函数覆盖了创建
std::list的常见场景:从空容器初始化,到固定数量元素的填充,再到从其他容器复制元素,满足不同的初始化需求,是使用std::list的基础.
3. List的iterator定义以及相关接口
在C++中,
std::list的迭代器(iterator)是其核心组件之一,它的设计完全适配list的 双向链表底层结构.
🔹迭代器的本质与定义
std::list::iterator是std::list类模板的嵌套类型 ,属于C++迭代器分类中的双向迭代器(Bidirectional Iterator) .①底层实现
list的迭代器本质是对链表节点指针的封装.每个链表节点包含:元素数据
指向前一个节点的指针(
prev)指向后一个节点的指针(
next)迭代器通过维护节点指针,实现双向移动(
++/--)和元素访问.②类型声明
在
std::list的类模板中,迭代器的定义简化后如下(底层细节由标准库实现):
cpp
template <class T, class Alloc = allocator<T>>
class list {
public:
// 普通迭代器(可读写)
typedef typename allocator_traits<Alloc>::template rebind<Node>::other::pointer iterator;
// const迭代器(只读)
typedef typename allocator_traits<Alloc>::template rebind<Node>::other::const_pointer const_iterator;
};
🔹核心特性
①双向迭代能力
支持前置/后置递增 :
++it、it++(向前移动到下一个节点)支持前置/后置递减 :
--it、it--(向后移动到上一个节点)
不支持随机访问 :不能用it + 5、it[2]等方式直接跳转,只能通过逐步递增/递减移动.②迭代器失效规则
这是
list迭代器的关键优势:
插入元素 :所有现有迭代器仍然有效(插入仅修改节点指针,不移动元素).
删除元素 :只有指向被删除节点的迭代器失效,其他迭代器不受影响.③元素访问
解引用(
*it):获取迭代器指向的元素.箭头操作(
it->):访问元素的成员(适用于对象类型).🔹常用场景与代码示例
①遍历
list
cpp
#include <list>
#include <iostream>
int main() {
std::list<int> my_list = {10, 20, 30, 40};
// 普通迭代器遍历(可读写)
std::list<int>::iterator it;
for (it = my_list.begin(); it != my_list.end(); ++it) {
std::cout << *it << " "; // 输出:10 20 30 40
}
std::cout << std::endl;
// const迭代器遍历(只读)
std::list<int>::const_iterator cit;
for (cit = my_list.cbegin(); cit != my_list.cend(); ++cit) {
std::cout << *cit << " "; // 输出:10 20 30 40
}
std::cout << std::endl;
return 0;
}
②插入/删除时的迭代器使用
cpp
//在迭代器位置插入元素
auto insert_pos = my_list.begin();
++insert_pos; // 指向20的位置
my_list.insert(insert_pos, 15); // list变为 [10, 15, 20, 30, 40]
// 删除迭代器指向的元素
auto erase_pos = my_list.find(20);
if (erase_pos != my_list.end()) {
my_list.erase(erase_pos); // list变为 [10, 15, 30, 40]
}
🔹常见误区
①错误使用
std::sort:std::sort需要随机访问迭代器 ,而list的迭代器是双向迭代器,因此必须使用list自带的成员函数sort()(链表专属排序,效率更高).my_list.sort(); // 正确:list成员函数
// std::sort(my_list.begin(), my_list.end()); // 错误:编译失败
②迭代器越界 :
list的迭代器没有end()之后的有效位置,遍历时必须用it != my_list.end()作为终止条件,不能用it < my_list.end()(双向迭代器不支持<比较).
🔹总结
std::list的迭代器是为双向链表量身设计的,它的核心价值在于:适配链表的双向移动需求,实现高效的插入/删除操作.
宽松的失效规则,让插入操作后无需重新获取迭代器.
3.1 List的iterator使用--->begin介绍
这是C++标准库中std::list::begin()成员函数的官方文档说明.
🔹函数定义与版本支持iterator begin();
const_iterator begin() const;
这是一个重载函数 ,从C++98开始支持,后续C++11等版本保持兼容.
两个版本的区别:
非
const对象调用时,返回普通iterator(可读写元素).
const对象调用时,返回const_iterator(只读元素,不能修改).
🔹核心功能Returns an iterator pointing to the first element in the list container.
begin()的作用是返回一个双向迭代器 ,指向list的第一个元素 .和
list::front()的关键区别:
| 方法 | 返回值类型 | 用途 |
|---|---|---|
begin() |
双向迭代器(iterator/const_iterator) |
获取遍历的起始位置,需解引用才能访问元素 |
front() |
元素的引用(T&/const T&) |
直接获取第一个元素的值,无需迭代器 |
🔹重要注意事项
如果
list是空容器(empty() == true),begin()返回的迭代器不能被解引用 (即不能写*it),否则会触发未定义行为(比如程序崩溃).该函数返回的迭代器属于双向迭代器 ,仅支持
++/--移动,不支持随机访问(不能用it + n跳转).
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :①对于非
const的list对象,返回普通iterator(可读写元素).②对于
const的list对象,返回const_iterator(仅能读取元素,不能修改).
🔹核心作用
begin()是STL容器的通用接口之一,所有容器都提供该方法.它的主要价值是:①获取容器遍历的起始位置,配合
end()完成整个容器的遍历.②通过迭代器的可读写/只读特性,保证代码的类型安全(比如
const对象只能用const_iterator,避免意外修改).
3.2 List的iterator使用--->end介绍
这是C++标准库中std::list::end()成员函数的官方文档.
🔹函数定义与版本支持iterator end();
const_iterator end() const;
这是一个重载函数 ,从C++98开始支持,后续C++11等版本保持兼容.
两个版本的区别:
非
const对象调用时,返回普通iterator(可读写元素).
const对象调用时,返回const_iterator(只读元素,不能修改).
🔹核心功能
end()的作用是返回一个双向迭代器 ,指向list的尾后位置(past-the-end) ------这是一个理论上的位置,代表最后一个元素的下一个位置,不指向任何实际元素 .和
list::back()的关键区别:
| 方法 | 返回值类型 | 用途 |
|---|---|---|
end() |
双向迭代器(iterator/const_iterator) |
作为遍历的终止标记,不能解引用 |
back() |
元素的引用(T&/const T&) |
直接获取最后一个元素的值 |
🔹关键细节
①左闭右开范围 :STL标准库的范围规则是
[begin, end),即包含begin()指向的元素,但不包含end()指向的位置.因此begin()和end()配合可以表示"容器中所有元素"的范围.②空容器处理 :如果
list为空(empty() == true),end()返回的迭代器和begin()返回的迭代器是同一个,此时也不能解引用.③迭代器特性 :返回的迭代器是双向迭代器 ,仅支持
++/--移动,不支持随机访问(不能用it + n跳转).
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :①对于非
const的list对象,返回普通iterator.②对于
const的list对象,返回const_iterator.
🔹常见误区①解引用
end()迭代器 :end()指向的是尾后位置,没有实际元素,解引用会触发未定义行为(如程序崩溃).②用
<比较迭代器 :双向迭代器不支持</>等比较运算符,遍历终止条件必须用it != my_list.end(),不能用it < my_list.end().
🔹核心作用①
end()是STL容器的通用接口,它的主要价值是:①与
begin()配合,构成"左闭右开"的遍历范围,是STL算法和容器遍历的基础模式.作为迭代器的终止标记,保证遍历的安全性和一致性.
3.3 List的iterator使用--->rbegin介绍
这是C++标准库中std::list::rbegin()成员函数的官方文档.
🔹函数定义与版本支持reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
这是一个重载函数 ,从C++98开始支持,后续C++11等版本保持兼容.
两个版本的区别:
非
const对象调用时,返回普通reverse_iterator(可读写元素).
const对象调用时,返回const_reverse_iterator(只读元素,不能修改).
🔹核心功能
rbegin()的作用是返回一个反向双向迭代器 ,指向list的最后一个元素 (也就是反向遍历的起始位置).反向迭代器的特殊行为:递增(
++rit)会向容器的开头 移动,递减(--rit)会向容器的末尾 移动.和
list::back()的关键区别:
| 方法 | 返回值类型 | 用途 |
|---|---|---|
rbegin() |
反向双向迭代器(reverse_iterator/const_reverse_iterator) |
获取反向遍历的起始位置,需解引用访问元素 |
back() |
元素的引用(T&/const T&) |
直接获取最后一个元素的值,无需迭代器 |
🔹 关键细节
①与
end()的关联 :rbegin()指向的元素,恰好是end()指向的"尾后位置"的前一个元素(即容器的最后一个实际元素).②空容器处理 :如果
list为空(empty() == true),rbegin()返回的迭代器和rend()返回的迭代器是同一个,此时不能解引用.③迭代器特性 :返回的迭代器是反向双向迭代器 ,仅支持
++/--移动,不支持随机访问.
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :①对于非
const的list对象,返回普通reverse_iterator.②对于
const的list对象,返回const_reverse_iterator.
🔹常见误区①混淆反向迭代器的移动方向 :反向迭代器的
++是向原容器的开头 移动,--是向原容器的末尾 移动,不要和正向迭代器搞反.②解引用空容器的
rbegin():空容器的rbegin()没有指向任何实际元素,解引用会触发未定义行为(如程序崩溃).
🔹核心作用①
rbegin()是STL容器的通用接口,它的主要价值是:与
rend()配合,实现容器的反向遍历,让反向遍历和正向遍历的代码模式保持一致.②无需手动移动迭代器到末尾,简化了反向遍历的实现.
3.4 List的iterator使用--->rend介绍
这是C++标准库中std::list::rend()成员函数的官方文档.
🔹函数定义与版本支持reverse_iterator rend();
const_reverse_iterator rend() const;
这是一个重载函数 ,从C++98开始支持,后续C++11等版本保持兼容.
两个版本的区别:
非
const对象调用时,返回普通reverse_iterator(可读写元素).
const对象调用时,返回const_reverse_iterator(只读元素,不能修改).
🔹核心功能
rend()的作用是返回一个反向双向迭代器 ,指向list的反向尾后位置 ------这是一个理论上的位置,代表原容器第一个元素的前一个位置,不指向任何实际元素 .与
rbegin()配合,构成反向遍历的范围[rbegin(), rend()),包含容器所有元素(按逆序排列).
🔹关键细节①与
begin()的关联 :rend()指向的位置,恰好是原容器begin()指向元素的前一个理论位置.②空容器处理 :如果
list为空(empty() == true),rend()返回的迭代器和rbegin()返回的迭代器是同一个,此时不能解引用.③迭代器特性 :返回的迭代器是反向双向迭代器 ,仅支持
++/--移动(++rit向原容器开头移动,--rit向原容器末尾移动),不支持随机访问(不能用rit + n跳转).
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :①对于非
const的list对象,返回普通reverse_iterator.②对于
const的list对象,返回const_reverse_iterator.
🔹常见误区①解引用
rend()迭代器 :rend()指向的是反向尾后位置,没有实际元素,解引用会触发未定义行为(如程序崩溃).②混淆反向迭代器的移动方向 :反向迭代器的
++是向原容器的开头 移动,--是向原容器的末尾 移动,不要和正向迭代器搞反.
🔹核心作用①
rend()是STL容器的通用接口,它的主要价值是:②与
rbegin()配合,构成"左闭右开"的反向遍历范围,让反向遍历的代码模式和正向遍历保持一致.作为反向迭代器的终止标记,保证反向遍历的安全性和一致性.
⭐️注意⭐️
①begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动.
②rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动.
4.List的capacity
4.1 empty介绍
这是C++标准库中std::list::empty()成员函数的官方文档.
🔹函数定义与版本支持bool empty() const;
这是一个const成员函数 (不会修改容器内容),从C++98开始支持,后续C++11等版本保持兼容.
🔹核心功能
empty()的作用是判断list容器是否为空(即元素数量是否为0).它是一个只读操作 ,不会对容器做任何修改.
如果你需要清空容器内容,需要使用
list::clear()函数,而不是empty().
🔹关键细节①时间复杂度 :
O(1)(常数时间).因为std::list会维护头尾指针或元素数量,判断是否为空只需一次简单的比较,效率极高.②与
size() == 0的关系 :两者功能等价,empty()是更通用、更高效的写法.对于某些旧版STL实现,list::size()的时间复杂度可能是O(n)(需要遍历计数),而empty()始终是O(1).③空容器的影响 :如果容器为空,调用
front()/back()或解引用begin()/rbegin()会触发未定义行为(如程序崩溃),因此建议在访问元素前先用empty()判断.
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :布尔值
true:容器为空(size = 0)
false:容器不为空(size > 0)
🔹常见误区①混淆
empty()与clear():empty()只是判断容器是否为空,不会修改容器;clear()会清空容器的所有元素,修改容器状态.②用
size() == 0代替empty():虽然功能等价,但empty()是STL推荐的写法,因为它对所有容器都保证O(1)时间复杂度,而size()在某些容器的旧实现中可能是O(n).
🔹核心作用
empty()是容器的通用安全检查接口,主要价值是:①在访问容器元素(如
front()/back())或遍历前,避免因空容器导致的未定义行为.②提供高效、通用的空容器判断方式,提升代码的安全性和可移植性.
4.2 size介绍
这是C++标准库中std::list::size()成员函数的官方文档.
🔹函数定义与版本支持size_type size() const;
这是一个const成员函数 (不会修改容器内容),从C++98开始支持,C++11对其实现做了性能优化.
size_type是std::list的嵌套类型,本质是无符号整数类型 (通常为size_t),用于表示容器的元素数量.
🔹核心功能
size()的作用是返回list容器中当前的元素总数量 .它是一个只读操作,不会对容器做任何修改.
🔹关键细节
时间复杂度 :C++11及之后:
O(1)(常数时间),因为std::list会维护一个内部计数器,直接返回计数值.C++98旧实现:部分编译器的
size()需要遍历整个链表计数,时间复杂度为O(n).
无符号类型注意事项 :由于返回值是无符号整数(
size_type),直接与有符号整数(如int)比较或运算可能导致溢出或逻辑错误.例如:
if (my_list.size() - 1 >= 0)永远为true(无符号数不会为负).
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :容器的元素数量,类型为
size_type(无符号整数).
🔹常见误区①用
size() > 0代替empty():虽然功能等价,但
empty()是STL推荐的写法,对所有容器都保证O(1)时间复杂度,且代码更清晰.②忽略无符号类型的溢出风险 :
例如
my_list.size() - 1当size()为0时,会得到一个极大的无符号值(溢出),而非预期的-1.
🔹核心作用
size()是容器的基础接口,主要价值是:①获取容器的实际元素数量,用于循环控制、容量规划等场景.
②结合
empty()可以更全面地判断容器状态,提升代码的健壮性.
5. List的element access
5.1 font介绍
这是C++标准库中std::list::front()成员函数的官方文档.
🔹函数定义与版本支持reference front();
const_reference front() const;
这是一个重载函数 ,从C++98开始支持,后续版本保持兼容.
两个版本的区别:
非
const对象调用时,返回普通reference(可读写元素).
const对象调用时,返回const_reference(只读元素,不能修改).
🔹核心功能
front()的作用是直接返回list容器第一个元素的引用 ,无需通过迭代器解引用,访问更直接.和
list::begin()的关键区别:
| 方法 | 返回值类型 | 用途 |
|---|---|---|
front() |
元素的引用(T&/const T&) |
直接访问/修改第一个元素 |
begin() |
双向迭代器(iterator/const_iterator) |
获取遍历起始位置,需解引用访问元素 |
🔹关键细节
①空容器风险 :如果
list为空(empty() == true),调用front()会触发未定义行为 (如程序崩溃),因此必须先用empty()判断容器是否为空.②引用特性 :返回的是引用,因此可以直接修改元素值(非
const对象时).my_list.front() = 100; // 直接修改第一个元素的值
③时间复杂度 :
O(1)(常数时间),因为std::list维护了头指针,直接访问第一个元素.
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :①对于非
const的list对象,返回普通reference(可读写).②对于
const的list对象,返回const_reference(只读).
🔹常见误区①空容器调用
front():空容器没有第一个元素,直接调用会导致程序崩溃或异常,必须先用empty()检查.②混淆
front()与begin():front()是元素的引用,begin()是迭代器,前者更适合直接访问元素,后者更适合遍历场景.
🔹核心作用
front()是容器的快速访问接口,主要价值是:①直接获取第一个元素的引用,比迭代器解引用更简洁高效.
②配合
empty()使用,保证访问的安全性.
5.2 back介绍
这是C++标准库中std::list::back()成员函数的官方文档.
🔹函数定义与版本支持reference back();
const_reference back() const;
这是一个重载函数 ,从C++98开始支持,后续版本保持兼容.
两个版本的区别:
非
const对象调用时,返回普通reference(可读写元素).
const对象调用时,返回const_reference(只读元素,不能修改).
🔹核心功能
back()的作用是直接返回list容器最后一个元素的引用 ,无需通过迭代器解引用,访问更直接.和
list::end()的关键区别:
| 方法 | 返回值类型 | 用途 |
|---|---|---|
back() |
元素的引用(T&/const T&) |
直接访问/修改最后一个元素 |
end() |
双向迭代器(iterator/const_iterator) |
指向尾后位置,作为遍历终止标记,不能解引用 |
🔹关键细节
①空容器风险 :如果
list为空(empty() == true),调用back()会触发未定义行为 (如程序崩溃),因此必须先用empty()判断容器是否为空.②引用特性 :返回的是引用,因此可以直接修改元素值(非
const对象时).my_list.back() = 300; // 直接修改最后一个元素的值
③时间复杂度 :
O(1)(常数时间),因为std::list维护了尾指针,直接访问最后一个元素.
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :①对于非
const的list对象,返回普通reference(可读写).②对于
const的list对象,返回const_reference(只读).
🔹常见误区①空容器调用
back():空容器没有最后一个元素,直接调用会导致程序崩溃或异常,必须先用empty()检查.②混淆
back()与end():back()是最后一个元素的引用,end()是尾后迭代器,前者用于直接访问元素,后者用于遍历终止.
🔹核心作用①
back()是容器的快速访问接口,主要价值是:直接获取最后一个元素的引用,比迭代器解引用更简洁高效.
②配合
empty()使用,保证访问的安全性.
6. List的modifiers
6.1 push_front介绍
这是C++标准库中std::list::push_front()成员函数的官方文档.
🔹函数定义与版本支持void push_front (const value_type& val);
从C++98开始支持,C++11新增了移动语义的重载版本(文档中未完全展示,但标准库实际支持
void push_front (value_type&& val);).这是一个修改容器的成员函数 ,会改变容器的元素数量和内容.
🔹核心功能
push_front()的作用是在list的头部(第一个元素之前)插入新元素 ,新元素的值是val的副本(或移动构造的结果).插入后容器的元素数量(
size)会增加1.
🔹关键细节①时间复杂度 :
O(1)(常数时间).因为std::list是双向链表,头插仅需修改头节点的指针,无需移动其他元素,效率极高.②迭代器失效规则 :插入操作不会使现有迭代器失效(链表结构仅新增节点,不影响其他节点的指针),这是
list相对于vector的核心优势之一.③空容器兼容 :即使容器为空,也可以调用
push_front(),插入的元素会成为容器的第一个元素.④内存分配 :新元素的内存由容器的分配器(
allocator)分配,分配失败时默认分配器会抛出std::bad_alloc异常.
🔹参数与返回值
参数 :
val:要插入的元素值,类型为value_type(即list存储的元素类型).该参数会被复制(或移动)到新插入的元素中.
返回值 :无返回值(void).
🔹常见误区①与
vector::push_front的性能差异 :
vector的头插需要移动所有现有元素(时间复杂度O(n)),而list的头插是O(1),因此频繁头插场景优先选择list.②混淆
push_front()与insert():
push_front()是头部插入的专用接口,而insert()可以在任意位置插入;push_front()本质是insert(begin(), val)的简化写法.
🔹核心作用
push_front()是list的高效插入接口,主要价值是:①实现链表头部的
O(1)插入,适合需要频繁在头部添加元素的场景(如栈结构、消息队列等).②保持链表的结构优势,插入后不影响其他元素的迭代器有效性.
6.2 pop_front介绍
这是C++标准库中std::list::pop_front()成员函数的官方文档.
🔹函数定义与版本支持void pop_front();
从C++98开始支持,后续版本保持兼容.
这是一个修改容器的成员函数 ,会改变容器的元素数量和内容.
🔹核心功能
pop_front()的作用是删除list的第一个元素 ,容器的元素数量(size)会减少1,且被删除的元素会被销毁.注意:该函数不会返回被删除的元素 ,如果需要获取被删除元素的值,需先通过
front()读取,再调用pop_front().
🔹关键细节①时间复杂度 :
O(1)(常数时间).因为std::list是双向链表,头删仅需修改头节点的指针,无需移动其他元素,效率极高.②迭代器失效规则 :只有指向被删除元素(原第一个元素)的迭代器失效,其他迭代器不受影响(链表结构仅删除节点,不影响其他节点的指针).
③空容器风险 :如果
list为空(empty() == true),调用pop_front()会触发未定义行为 (如程序崩溃),因此必须先用empty()判断容器是否为空.④内存释放 :被删除元素的内存由容器的分配器(
allocator)回收,无需手动管理.
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :无返回值(void).
🔹常见误区①空容器调用
pop_front():空容器没有第一个元素,直接调用会导致程序崩溃或异常,必须先用empty()检查.②期望返回被删除元素 :
pop_front()无返回值,若需要被删除元素的值,需先通过front()保存.③与
vector::pop_front的性能差异 :vector的头删需要移动所有现有元素(时间复杂度O(n)),而list的头删是O(1),因此频繁头删场景优先选择list.
🔹核心作用
pop_front()是list的高效删除接口,主要价值是:①实现链表头部的
O(1)删除,适合需要频繁在头部删除元素的场景(如栈结构、消息队列等).②保持链表的结构优势,删除后仅影响被删除元素的迭代器,其他迭代器仍有效.
6.4 push_back介绍
这是C++标准库中std::list::push_back()成员函数的官方文档.
🔹函数定义与版本支持void push_back (const value_type& val);
// C++11 新增移动语义重载
void push_back (value_type&& val);
从C++98开始支持基础版本,C++11新增了移动语义的重载.
这是一个修改容器的成员函数 ,会改变容器的元素数量和内容.
🔹核心功能
push_back()的作用是在list的末尾(最后一个元素之后)插入新元素 ,新元素的值是val的副本(或移动构造的结果).插入后容器的元素数量(
size)会增加1.
🔹关键细节①时间复杂度 :
O(1)(常数时间).因为std::list是双向链表,尾插仅需修改尾节点的指针,无需移动其他元素,效率稳定且高效.②迭代器失效规则 :插入操作不会使现有迭代器失效(链表结构仅新增节点,不影响其他节点的指针),这是
list相对于vector的核心优势之一.③空容器兼容 :即使容器为空,也可以调用
push_back(),插入的元素会成为容器的第一个元素.④内存分配 :新元素的内存由容器的分配器(
allocator)分配,分配失败时默认分配器会抛出std::bad_alloc异常.⑤移动语义优化 :C++11及之后,传入右值(如临时对象)时会触发移动构造,避免不必要的拷贝,进一步提升性能.
🔹参数与返回值
参数 :
val:要插入的元素值,类型为value_type(即list存储的元素类型).该参数会被复制(左值传入时)或移动(右值传入时)到新插入的元素中.
返回值 :无返回值(void).
🔹常见误区①与
vector::push_back的性能差异 :
vector的尾插是均摊O(1) (扩容时需要重新分配内存并拷贝元素),而list的尾插是稳定O(1) (无扩容开销),因此在需要频繁尾插且避免扩容开销的场景下,list更有优势.②混淆
push_back()与insert():
push_back()是尾部插入的专用接口,而insert()可以在任意位置插入;push_back()本质是insert(end(), val)的简化写法.
🔹核心作用
push_back()是list的高效插入接口,主要价值是:①实现链表尾部的
O(1)插入,适合需要频繁在尾部添加元素的场景(如队列结构、日志记录等).②保持链表的结构优势,插入后不影响其他元素的迭代器有效性.
③C++11的移动语义优化进一步减少了拷贝开销,提升性能.
6.5 pop_back介绍
这是C++标准库中std::list::pop_back()成员函数的官方文档.
🔹函数定义与版本支持void pop_back();
从C++98开始支持,后续版本保持兼容.
这是一个修改容器的成员函数 ,会改变容器的元素数量和内容.
🔹核心功能
pop_back()的作用是删除list的最后一个元素 ,容器的元素数量(size)会减少1,且被删除的元素会被销毁.注意:该函数不会返回被删除的元素 ,如果需要获取被删除元素的值,需先通过
back()读取,再调用pop_back().
🔹关键细节①时间复杂度 :
O(1)(常数时间).因为std::list是双向链表,尾删仅需修改尾节点的指针,无需移动其他元素,效率稳定且高效.②迭代器失效规则 :只有指向被删除元素(原最后一个元素)的迭代器失效,其他迭代器不受影响(链表结构仅删除节点,不影响其他节点的指针).
③空容器风险 :如果
list为空(empty() == true),调用pop_back()会触发未定义行为 (如程序崩溃),因此必须先用empty()判断容器是否为空.④内存释放 :被删除元素的内存由容器的分配器(
allocator)回收,无需手动管理.
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :无返回值(void).
🔹常见误区①空容器调用
pop_back():空容器没有最后一个元素,直接调用会导致程序崩溃或异常,必须先用empty()检查.②期望返回被删除元素 :
pop_back()无返回值,若需要被删除元素的值,需先通过back()保存.③与
vector::pop_back的对比 :
vector的尾删是O(1),但如果之前发生过扩容,容器可能存在内存碎片;而list的尾删是稳定O(1),且无内存碎片问题.
🔹核心作用
pop_back()是list的高效删除接口,主要价值是:①实现链表尾部的
O(1)删除,适合需要频繁在尾部删除元素的场景(如队列结构、日志清理等).②保持链表的结构优势,删除后仅影响被删除元素的迭代器,其他迭代器仍有效.
6.6 insert介绍
这是C++标准库中std::list::insert()成员函数的官方文档.
🔹函数定义与重载版本
insert()是一个重载函数,从C++98开始支持基础版本,C++11新增了初始化列表插入的重载:
cpp
// 1. 单元素插入
iterator insert (iterator position, const value_type& val);
// 2. 填充插入(插入n个相同值的元素)
void insert (iterator position, size_type n, const value_type& val);
// 3. 范围插入(插入另一个容器的元素范围 [first, last))
template <class InputIterator>
void insert (iterator position, InputIterator first, InputIterator last);
// 4. 初始化列表插入(C++11新增)
void insert (iterator position, initializer_list<value_type> il);
🔹核心功能
insert()的作用是在指定迭代器position指向的元素之前 插入新元素,插入后容器的size会增加插入的元素数量.由于
std::list是双向链表,插入时仅需修改节点指针,无需移动其他元素,因此任意位置的插入效率都远高于vector.
🔹关键细节①时间复杂度
单元素插入:
O(1)(仅需修改指针,无元素移动).填充插入/范围插入:
O(k)(k为插入的元素数量,每个元素插入都是O(1)).②迭代器失效规则
插入操作不会使现有迭代器失效(链表仅新增节点,不影响其他节点的指针),这是
list相对于vector的核心优势.③插入位置特性
插入是在
position指向的元素之前 完成,而非之后.例如:auto it = my_list.begin(); // 指向第一个元素
my_list.insert(it, 0); // 0 会被插入到第一个元素之前
④空容器兼容
即使容器为空,也可以传入
begin()/end()作为插入位置,插入的元素会成为容器的第一个元素.⑤内存分配
新元素的内存由容器的分配器(
allocator)分配,分配失败时默认分配器会抛出std::bad_alloc异常.
🔹参数与返回值
| 重载版本 | 参数说明 | 返回值 |
|---|---|---|
| 单元素插入 | position:插入位置的迭代器;val:要插入的元素值 |
指向第一个新插入元素的迭代器 |
| 填充插入 | position:插入位置;n:插入元素数量;val:初始化值 |
无返回值(void) |
| 范围插入 | position:插入位置;first/last:待插入元素的迭代器范围 |
无返回值(void) |
| 初始化列表插入 | position:插入位置;il:初始化列表 |
无返回值(void) |
🔹常见误区
①混淆插入位置 :插入是在
position指向的元素之前 ,而非之后,需注意迭代器的指向.②与
vector::insert的性能对比 :vector中间插入需要移动后续元素(时间复杂度O(n)),而list任意位置插入都是O(1)(单元素)或O(k)(多元素),因此频繁中间插入场景优先选择list.③忽略返回值 :单元素插入的返回值是指向新插入元素的迭代器,可用于后续操作;其他重载版本无返回值.
🔹核心作用
insert()是list最灵活的插入接口,主要价值是:①支持多种插入方式(单元素、多元素、范围、初始化列表),满足不同场景的插入需求.
②利用链表结构实现任意位置的高效插入,避免了
vector插入时的元素移动开销.③插入后迭代器失效规则宽松,仅新元素的迭代器需要关注,原有迭代器仍可正常使用.
6.7 erase介绍
这是C++标准库中std::list::erase()成员函数的官方文档.
🔹函数定义与重载版本
erase()是一个重载函数,从C++98开始支持,后续版本保持兼容:
cpp
// 1. 单元素删除
iterator erase (iterator position);
// 2. 范围删除(删除 [first, last) 区间内的元素)
iterator erase (iterator first, iterator last);
🔹核心功能
erase()的作用是删除list中指定迭代器指向的单个元素 ,或[first, last)范围内的所有元素.删除后容器的
size会减少被删除的元素数量,且被删除的元素会被销毁.由于
std::list是双向链表,删除时仅需修改节点指针,无需移动其他元素,因此任意位置的删除效率都远高于vector.
🔹关键细节①时间复杂度
单元素删除:
O(1)(仅需修改指针,无元素移动).范围删除:
O(k)(k为删除的元素数量,每个元素删除都是O(1)).②迭代器失效规则
只有指向被删除元素的迭代器 会失效,其他迭代器不受影响(链表仅删除节点,不影响其他节点的指针).这是
list相对于vector的核心优势.③返回值特性
返回一个迭代器,指向被删除元素的下一个元素 ;如果删除的是最后一个元素,则返回
end().④空容器与无效范围处理
若容器为空,调用单元素删除会触发未定义行为(需确保
position指向有效元素).若范围删除的
first == last(空范围),函数不会执行任何操作,直接返回first.⑤内存释放
被删除元素的内存由容器的分配器(
allocator)回收,无需手动管理.
🔹参数与返回值
| 重载版本 | 参数说明 | 返回值 |
|---|---|---|
| 单元素删除 | position:指向要删除的单个元素的迭代器 |
指向被删除元素的下一个元素的迭代器 |
| 范围删除 | first/last:指定删除范围的迭代器(左闭右开区间 [first, last)) |
指向被删除的最后一个元素的下一个元素的迭代器 |
🔹 常见误区
①空容器调用单元素删除 :空容器中没有有效元素,直接调用会触发未定义行为(如程序崩溃),需确保
position指向有效元素.②迭代器失效后继续使用 :被删除元素的迭代器会失效,不能再解引用或移动;但返回的新迭代器是有效的,可用于后续操作.
③混淆范围的左闭右开 :范围删除是
[first, last),包含first指向的元素,但不包含last指向的元素.④与
vector::erase的性能对比 :vector中间删除需要移动后续元素(时间复杂度O(n)),而list任意位置删除都是O(1)(单元素)或O(k)(多元素),因此频繁中间删除场景优先选择list.
🔹核心作用
erase()是list最灵活的删除接口,主要价值是:①支持单元素和范围删除,满足不同场景的删除需求.
②利用链表结构实现任意位置的高效删除,避免了
vector删除时的元素移动开销.③删除后迭代器失效规则宽松,仅被删除元素的迭代器失效,其他迭代器仍可正常使用.
6.8 swap介绍
这是C++标准库中std::list::swap()成员函数的官方文档.
🔹函数定义与版本支持void swap (list& x);
从C++98开始支持,后续版本保持兼容.
这是一个修改容器的成员函数 ,会交换两个容器的内容,但不会拷贝或移动元素.
🔹核心功能
swap()的作用是交换当前list和另一个同类型list(x)的内容 ,两个容器的大小可以不同.交换后,当前容器的元素变为
x原来的元素,x的元素变为当前容器原来的元素.
🔹关键细节①时间复杂度 :
O(1)(常数时间).因为
std::list是双向链表,交换仅需交换两个容器的头尾指针、内部计数器 等管理结构,无需拷贝或移动任何元素,效率极高.②迭代器/引用/指针有效性
交换后,所有指向原容器元素的迭代器、引用、指针仍然有效 ,只是现在属于另一个容器(因为元素本身没有移动,仅容器的管理结构被交换).这是
list::swap()的核心优势之一.③非成员函数优化
标准库还提供了非成员函数
std::swap(),对list有专门优化,效果与成员函数swap()完全一致,推荐优先使用非成员版本(更符合STL的通用编程风格).④分配器(allocator)交换规则
两个容器的分配器是否会被交换,由
allocator_traits的propagate_on_container_swap特性决定:若该特性为
true,分配器会随容器内容一起交换;若为
false(默认情况),分配器不会交换,仅交换元素内容.
🔹参数与返回值
参数 :
x:另一个同类型的list容器(模板参数T和Alloc必须与当前容器完全一致),通过引用传递.
返回值 :无返回值(void).
🔹常见误区①认为交换会拷贝元素 :
list::swap()是O(1)操作,仅交换容器的管理结构,不会拷贝或移动任何元素,这与vector::swap()的行为类似,但比手动拷贝元素高效得多.②担心迭代器失效 :
交换后,指向元素的迭代器、引用、指针仍然有效,只是归属的容器发生了变化,这是链表结构带来的天然优势.
③忽略分配器交换规则 :
若容器依赖自定义分配器,需注意
propagate_on_container_swap的设置,避免分配器不交换导致的资源管理问题.
🔹核心作用
swap()是list的高效容器交换接口,主要价值是:①实现
O(1)时间复杂度的容器内容交换,适合需要快速交换容器的场景(如算法中的临时交换、容器内容的批量替换).②保证交换后迭代器、引用、指针的有效性,避免了重新获取迭代器的开销.
③非成员函数
std::swap()的优化版本与成员函数效果一致,符合STL的通用编程范式.
6.9 clear介绍
这是C++标准库中std::list::clear()成员函数的官方文档.
🔹函数定义与版本支持void clear();
从C++98开始支持,后续版本保持兼容.
这是一个修改容器的成员函数 ,会彻底清空容器内容.
🔹核心功能
clear()的作用是删除list中的所有元素 ,被删除的元素会被销毁,容器的size会变为0.注意:该函数仅清空元素,不会释放容器本身的内存(如分配器的内部管理结构),后续插入元素时可复用这部分内存,避免频繁分配开销.
🔹关键细节①时间复杂度 :
O(n)(n为容器当前元素数量).需要遍历并销毁每个元素,因此时间复杂度与元素数量成正比.
②迭代器/引用/指针有效性
清空后,所有指向原元素的迭代器、引用、指针都会失效 (因为元素已被销毁),后续不能再使用这些迭代器访问元素.
③空容器兼容性
即使容器已经为空(
size == 0),调用clear()也是安全的,不会触发未定义行为.④内存管理
被删除元素的内存由容器的分配器(
allocator)回收,但容器本身的内存(如头尾指针、计数器等结构)会保留,用于后续插入元素时复用,提升效率.
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :无返回值(void).🔹常见误区
①认为
clear()会释放容器本身的内存 :
clear()仅释放元素的内存,容器本身的内存(如分配器的管理结构)会保留,若需彻底释放容器内存,可配合swap实现:std::list().swap(my_list); // 交换空容器,彻底释放内存
②清空后仍使用原迭代器 :
所有指向原元素的迭代器、引用、指针都会失效,必须重新获取迭代器才能访问新插入的元素.
③混淆
clear()与erase():
clear()是清空所有元素,等价于erase(begin(), end()).
erase()是删除部分元素(单元素或范围),适合局部删除场景.
🔹核心作用
clear()是容器的重置接口,主要价值是:①快速清空容器所有元素,适合需要重置容器状态的场景(如循环复用容器、清理临时数据).
②保留容器本身的内存,后续插入可复用,减少内存分配开销.
③接口简洁,无需手动遍历删除,提升代码可读性.
7. List的迭代器失效问题
大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了.因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响.
cpp
void TestListIterator1()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> l(array, array+sizeof(array)/sizeof(array[0]));
auto it = l.begin();
while (it != l.end())
{
// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给
其赋值
l.erase(it);
++it;
}
}
// 改正
void TestListIterator()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> l(array, array+sizeof(array)/sizeof(array[0]));
auto it = l.begin();
while (it != l.end())
{
l.erase(it++); // it = l.erase(it);
}
}
8. List的反向迭代器
通过前面例子知道,反向迭代器的++就是正向迭代器的--,反向迭代器的--就是正向迭代器的++,因此反向迭代器的实现可以借助正向迭代器,即:反向迭代器内部可以包含一个正向迭代器,对正向迭代器的接口进行包装即可.
cpp
template<class Iterator>
class ReverseListIterator
{
// 注意:此处typename的作用是明确告诉编译器,Ref是Iterator类中的类型,而不是静态成员变量
// 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量
// 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的
public:
typedef typename Iterator::Ref Ref;
typedef typename Iterator::Ptr Ptr;
typedef ReverseListIterator<Iterator> Self;
public:
// 构造
ReverseListIterator(Iterator it): _it(it){}
// 具有指针类似行为
Ref operator*(){
Iterator temp(_it);
--temp;
return *temp;
}
Ptr operator->(){ return &(operator*());}
// 迭代器支持移动
Self& operator++(){
--_it;
return *this;
}
Self operator++(int){
Self temp(*this);
--_it;
return temp;
}
Self& operator--(){
++_it;
return *this;
}
Self operator--(int)
{
Self temp(*this);
++_it;
return temp;
}
// 迭代器支持比较
bool operator!=(const Self& l)const{ return _it != l._it;}
bool operator==(const Self& l)const{ return _it != l._it;}
Iterator _it;
};
9. List与vector的对比
| | vector | List |
| 底层结构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
| 随机访问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素效率O(N) |
| 插入和删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容: 开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1) |
| 空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
| 迭代器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
| 迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
| 使用场景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |
|---|
10. List的核心框架接口的模拟实现
这是C++
std::list核心框架接口的模拟实现,基于双向循环链表(带哨兵头节点) 来实现,这是标准库list的底层实现方式,同时覆盖前面介绍的所有核心接口.
1️⃣实现思路梳理
std::list底层是双向循环链表 ,为了简化空链表和非空链表的操作逻辑,标准库会用一个哨兵头节点 (不存储有效数据),让链表始终形成闭环:
①定义链表节点结构(包含数据、前驱/后继指针);
②封装双向迭代器(list的迭代器不是原生指针,需重载++/--/*/->等运算符);
③实现list核心接口(构造/析构、迭代器、插入/删除、清空/交换等).
2️⃣完整模拟实现代码
cpp
#include <iostream>
#include <algorithm> // 用于swap
#include <cstddef> // 用于size_t
// 前置声明:迭代器需要用到ListNode,List需要用到迭代器
template <class T>
struct ListNode;
// 1. 迭代器封装
// 模板参数:T(元素类型)、Ref(引用类型)、Ptr(指针类型)
// 用Ref/Ptr区分普通迭代器和const迭代器,复用代码
template <class T, class Ref, class Ptr>
struct ListIterator {
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
Node* _node; // 迭代器持有节点指针
// 构造函数
ListIterator(Node* node) : _node(node) {}
// 重载++:前置++ (++it)
Self& operator++() {
_node = _node->_next;
return *this;
}
// 重载++:后置++ (it++)
Self operator++(int) {
Self tmp(*this); // 保存当前迭代器
_node = _node->_next;
return tmp;
}
// 重载--:前置-- (--it)
Self& operator--() {
_node = _node->_prev;
return *this;
}
// 重载--:后置-- (it--)
Self operator--(int) {
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
// 重载*:解引用获取元素引用
Ref operator*() {
return _node->_data;
}
// 重载->:访问元素成员 (比如it->xxx)
Ptr operator->() {
return &_node->_data;
}
// 重载!=
bool operator!=(const Self& it) const {
return _node != it._node;
}
// 重载==
bool operator==(const Self& it) const {
return _node == it._node;
}
};
// 2. 链表节点结构
template <class T>
struct ListNode {
typedef ListNode<T> Node;
T _data; // 节点数据
Node* _prev; // 前驱指针
Node* _next; // 后继指针
// 节点构造函数
ListNode(const T& data = T())
: _data(data)
, _prev(nullptr)
, _next(nullptr) {}
};
// 3. List核心类
template <class T>
class List {
typedef ListNode<T> Node;
public:
// 迭代器类型定义(普通/const)
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
// 基础构造/析构
// 空链表初始化(创建哨兵头节点)
void empty_init() {
_head = new Node; // 哨兵节点,数据为默认值
_head->_prev = _head;
_head->_next = _head;
_size = 0;
}
// 默认构造函数
List() {
empty_init();
}
// 填充构造:n个val
List(size_t n, const T& val = T()) {
empty_init();
for (size_t i = 0; i < n; ++i) {
push_back(val);
}
}
// 范围构造:[first, last)
template <class InputIterator>
List(InputIterator first, InputIterator last) {
empty_init();
while (first != last) {
push_back(*first);
++first;
}
}
// 拷贝构造(深拷贝)
List(const List<T>& lt) {
empty_init();
// 遍历原链表,尾插新节点
for (auto& e : lt) {
push_back(e);
}
}
// 赋值运算符重载(现代写法:交换)
List<T>& operator=(List<T> lt) {
swap(lt);
return *this;
}
// 析构函数
~List() {
clear(); // 清空所有有效节点
// 删除哨兵节点
delete _head;
_head = nullptr;
}
// 迭代器接口
iterator begin() {
// begin指向第一个有效元素(哨兵节点的next)
return iterator(_head->_next);
}
const_iterator begin() const {
return const_iterator(_head->_next);
}
iterator end() {
// end指向哨兵节点
return iterator(_head);
}
const_iterator end() const {
return const_iterator(_head);
}
// 容器状态接口
bool empty() const {
return _size == 0;
}
size_t size() const {
return _size;
}
// 首尾访问接口
T& front() {
// 第一个有效元素
return *begin();
}
const T& front() const {
return *begin();
}
T& back() {
// 最后一个有效元素(哨兵节点的prev)
return *(--end());
}
const T& back() const {
return *(--end());
}
// 插入接口
// 在pos迭代器前插入val
iterator insert(iterator pos, const T& val) {
Node* cur = pos._node; // 插入位置的节点
Node* prev = cur->_prev; // 插入位置的前驱节点
Node* new_node = new Node(val); // 新节点
// 调整指针:prev <-> new_node <-> cur
prev->_next = new_node;
new_node->_prev = prev;
new_node->_next = cur;
cur->_prev = new_node;
++_size;
return iterator(new_node); // 返回新插入节点的迭代器
}
// 头插
void push_front(const T& val) {
insert(begin(), val);
}
// 尾插
void push_back(const T& val) {
insert(end(), val);
}
// 删除接口
// 删除pos迭代器指向的节点
iterator erase(iterator pos) {
if (pos == end()) { // 不能删除end()(哨兵节点)
return end();
}
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
// 调整指针:prev <-> next
prev->_next = next;
next->_prev = prev;
delete cur; // 释放当前节点
--_size;
return iterator(next); // 返回被删节点的下一个迭代器
}
// 头删
void pop_front() {
erase(begin());
}
// 尾删
void pop_back() {
erase(--end()); // end()是哨兵,--end()是最后一个有效元素
}
// 清空所有有效节点(保留哨兵)
void clear() {
iterator it = begin();
while (it != end()) {
it = erase(it); // erase返回下一个迭代器,避免失效
}
_size = 0;
}
// 交换接口
void swap(List<T>& lt) {
// 交换哨兵节点和大小
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
private:
Node* _head; // 哨兵头节点
size_t _size; // 有效元素个数
};
// 测试代码
void TestList() {
// 1. 构造+尾插
List<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
std::cout << "lt1: ";
for (auto e : lt1) {
std::cout << e << " "; // 输出:1 2 3
}
std::cout << std::endl;
// 2. 头插+头删
lt1.push_front(0);
std::cout << "lt1 after push_front: ";
for (auto e : lt1) {
std::cout << e << " "; // 输出:0 1 2 3
}
std::cout << std::endl;
lt1.pop_front();
std::cout << "lt1 after pop_front: ";
for (auto e : lt1) {
std::cout << e << " "; // 输出:1 2 3
}
std::cout << std::endl;
// 3. 插入+删除
auto it = lt1.begin();
++it; // 指向2
lt1.insert(it, 10);
std::cout << "lt1 after insert: ";
for (auto e : lt1) {
std::cout << e << " "; // 输出:1 10 2 3
}
std::cout << std::endl;
it = lt1.begin();
++it; // 指向10
lt1.erase(it);
std::cout << "lt1 after erase: ";
for (auto e : lt1) {
std::cout << e << " "; // 输出:1 2 3
}
std::cout << std::endl;
// 4. 清空+大小
std::cout << "lt1 size before clear: " << lt1.size() << std::endl; // 3
lt1.clear();
std::cout << "lt1 size after clear: " << lt1.size() << std::endl; // 0
std::cout << "lt1 is empty: " << (lt1.empty() ? "yes" : "no") << std::endl; //yes
// 5. 拷贝构造+赋值
List<int> lt2(5, 10);
List<int> lt3(lt2);
std::cout << "lt3: ";
for (auto e : lt3) {
std::cout << e << " "; // 输出:10 10 10 10 10
}
std::cout << std::endl;
List<int> lt4;
lt4 = lt3;
std::cout << "lt4: ";
for (auto e : lt4) {
std::cout << e << " "; // 输出:10 10 10 10 10
}
std::cout << std::endl;
}
int main() {
TestList();
return 0;
}
3️⃣核心代码解释
①哨兵头节点的作用标准库
list用哨兵头节点 (dummy node)统一空链表和非空链表的操作:空链表:哨兵节点的
prev/next都指向自己;非空链表:
begin()是哨兵的next(第一个有效元素),end()是哨兵本身(尾后位置);优势:无需区分空/非空,插入/删除逻辑更简洁.
②迭代器的封装list的迭代器不是原生指针(因为节点内存不连续),需重载核心运算符:
++/--:移动到前驱/后继节点;
*:返回节点数据的引用;
->:返回节点数据的指针(支持it->成员);
==/!=:比较迭代器持有的节点指针。
③核心接口实现要点
| 接口 | 实现逻辑 |
|---|---|
insert |
先创建新节点,调整前驱/后继指针,size+1,返回新节点迭代器; |
erase |
调整前驱/后继指针,释放当前节点,size-1,返回下一个节点迭代器; |
push_front/push_back |
复用 insert,分别在 begin()/end() 前插入; |
pop_front/pop_back |
复用 erase,分别删除 begin()/--end(); |
clear |
遍历 erase 所有有效节点,保留哨兵; |
swap |
仅交换哨兵节点和size(O(1),无需拷贝元素); |
④迭代器失效规则模拟实现和标准库一致:
插入:所有迭代器都有效(仅新增节点,不修改其他节点指针);
删除:仅被删除节点的迭代器失效,其他迭代器有效;
清空:所有指向原元素的迭代器失效.
⑤总结这份模拟实现覆盖了
std::list的核心框架接口,关键要点:
底层结构 :双向循环链表 + 哨兵头节点,简化操作逻辑;
迭代器封装 :通过重载运算符模拟指针行为,区分普通/const迭代器;
接口设计 :核心插入/删除复用基础接口(insert/erase),保证代码复用性;
性能特性:插入/删除为O(1)(仅调整指针),遍历为O(n),符合标准库特性.
敬请期待下一篇文章内容-->C++中stack和queue介绍!



















