C++学习之旅【C++List类介绍—入门指南与核心概念解析】


🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》

《C++知识内容》《Linux系统知识》

✨逆境不吐心中苦,顺境不忘来时路! 🎬 博主简介:

引言:前篇文章,小编已经介绍了关于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)(需移动后续元素)
迭代器失效 仅删除节点的迭代器失效 扩容/中间插入时全部失效

典型场景 :如果你需要频繁在容器中间/头部插入删除元素(比如任务队列、游戏中的角色列表、缓存淘汰列表),listvector高效得多;而如果需要频繁随机访问(比如按索引取元素),则优先用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::splicevector::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:你要存储的元素类型(比如 intstd::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());
说明 :适用于从其他容器(如vectorarray)或数组的迭代器范围初始化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::iteratorstd::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;
};

🔹核心特性

①双向迭代能力

支持前置/后置递增 :++itit++(向前移动到下一个节点)

支持前置/后置递减 :--itit--(向后移动到上一个节点)
不支持随机访问 :不能用it + 5it[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 跳转).
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :

①对于非 constlist 对象,返回普通 iterator(可读写元素).

②对于constlist对象,返回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 跳转).
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :

①对于非 constlist 对象,返回普通 iterator.

②对于 constlist 对象,返回 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() 返回的迭代器是同一个,此时不能解引用.

迭代器特性 :返回的迭代器是反向双向迭代器 ,仅支持 ++/-- 移动,不支持随机访问.
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :

①对于非 constlist 对象,返回普通 reverse_iterator.

②对于 constlist 对象,返回 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 跳转).
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :

①对于非 constlist 对象,返回普通 reverse_iterator.

②对于 constlist 对象,返回 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_typestd::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() - 1size() 为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 维护了头指针,直接访问第一个元素.
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :

①对于非 constlist 对象,返回普通 reference(可读写).

②对于 constlist 对象,返回 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 维护了尾指针,直接访问最后一个元素.
🔹参数与返回值
参数 :无参数,直接调用即可.
返回值 :

①对于非 constlist 对象,返回普通 reference(可读写).

②对于 constlist 对象,返回 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_traitspropagate_on_container_swap 特性决定:

若该特性为 true,分配器会随容器内容一起交换;

若为 false(默认情况),分配器不会交换,仅交换元素内容.
🔹参数与返回值
参数
x:另一个同类型的 list 容器(模板参数 TAlloc 必须与当前容器完全一致),通过引用传递.
返回值 :无返回值(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介绍!


相关推荐
孞㐑¥2 小时前
算法—双指针
开发语言·c++·经验分享·笔记·算法
带土12 小时前
11. C++封装
开发语言·c++
沛沛rh452 小时前
Rust入门一:从内存安全到高性能编程
开发语言·安全·rust
a程序小傲2 小时前
国家电网Java面试被问:API网关的JWT令牌验证和OAuth2.0授权码流程
java·开发语言·spring boot·后端·面试·职场和发展·word
实战产品说2 小时前
从豆包日报下架,看到的字节战略和市场机会
人工智能·经验分享·学习·产品经理
tqs_123452 小时前
单例模式代码
java·开发语言·单例模式
C系语言2 小时前
安装Python版本opencv命令
开发语言·python·opencv
風清掦2 小时前
【江科大STM32学习笔记-03】GPIO通用输入输出口
笔记·stm32·单片机·学习
2501_944526422 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 多语言国际化实现
android·java·开发语言·javascript·flutter·游戏