C++ List 容器详解:迭代器失效、排序与高效操作
目录
- 前言
- 一、list的接口使用
- 1.1 构造+迭代器+范围for
- 1.2 find+insert+erase+list的迭代器失效问题
- 1.3 reverse
- 1.4 sort + 迭代器分类
- 1.4.1 sort相关性能分析
- 1.5 merge
- 1.6 unique
- 1.7 remove 和 remove_if
- 1.8 splice
- 结语


前言
大家好啊,我是云泽Q,欢迎阅读我的文章,一名热爱计算机技术的在校大学生,喜欢在课余时间做一些计算机技术的总结性文章,希望我的文章能为你解答困惑~
一、list的接口使用
STL库中的list是个带头双向循环链表,所以其能在任意位置进行插入删除,不需要挪动数据

STL库中还有一个单链表叫forward_list,只不过用的很少

list的构造

list的迭代器是双向的,可以正着走也可以倒着走

且list也没有reserve的概念了,毕竟没有扩容了,也没有capacity了
单链表的迭代器就没有rbegin,rend,只有begin,end,只支持加加,不支持减减

list访问数据也不支持下标方括号了,因为其底层不是连续的物理空间,支持方括号的代价就太高了,只支持迭代器访问

插入删除支持的接口很多
1.1 构造+迭代器+范围for

这里说一下图中范围for的代码逻辑:
一、范围 for 的本质:迭代器遍历的 “语法糖”
C++11 引入的范围 for(for (auto e : 容器))是编译器提供的语法简化,其底层完全依赖迭代器遍历。对于代码中的 for (auto e : lt2),编译器会自动将其转换为基于迭代器的循环结构。
二、底层转换的详细步骤
以 for (auto e : lt2) 为例(lt2 是 std::list),编译器会执行以下转换:
- 获取迭代器的起始和结束位置
调用容器的 begin() 和 end() 成员函数,分别获取起始迭代器(指向第一个元素)和结束迭代器(指向 “尾后位置”,不存储实际元素)。
等价代码:
auto __begin = lt2.begin(); // 起始迭代器,指向第一个元素
auto __end = lt2.end(); // 结束迭代器,指向尾后位置
- 循环遍历逻辑以 “起始迭代器!= 结束迭代器” 为条件,循环执行以下操作:
-
- 解引用迭代器 * __begin,获取当前元素的值;
-
- 将该值赋值给循环变量 e(auto 会推导为 int 类型,因为 lt2 存储的是 int);
-
- 迭代器自增(++__begin),移动到下一个元素。
等价代码:
- 迭代器自增(++__begin),移动到下一个元素。
for (; __begin != __end; ++__begin) {auto e = *__begin; // 解引用迭代器,获取元素值cout << e << " "; // 输出元素
}
三、范围 for 的适用条件
范围 for 能工作的核心前提是:
- 容器(或对象)必须支持 **begin() 和 end() 操作 **(可以是成员函数,也可以是全局的 std::begin/std::end 函数);
- 迭代器必须支持前置自增(++)、不等于比较(!=)、解引用( * ) 操作。
以 std::list 为例:
- 它的 begin()/end() 是成员函数,返回双向迭代器;
- 该迭代器支持 ++(移动到下一个节点)、!=(判断是否到尾后)、*(获取节点值),因此完全兼容范围 for。
四、与手动迭代器遍历的对比
手动迭代器遍历的代码是:
list<int>::iterator it = lt2.begin();
while (it != lt2.end()) {cout << *it << " ";++it;
}
范围 for 只是将上述逻辑隐藏在编译器转换中,让代码更简洁、可读性更高。
1.2 find+insert+erase+list的迭代器失效问题
list是没有提供find接口的,若想使用find就要使用算法库中的find


list的insert就不会出现迭代器失效了,由于list是带头双向循环链表,在3这个结点前插入数据,不存在挪动数据扩容之类的问题

但是list的erase会出现迭代器失效,因为是把pos指向的底层的结点删除了

所以erase后就不能使用指向pos位置的迭代器了
1.3 reverse
list还有一些其他相关的操作

list单独实现了一个自己的逆置函数,就是把带头双向循环链表反转一下

1.4 sort + 迭代器分类
这里list容器内部实现了自己的sort函数,而不是用算法库中的sort的原因也牵扯到迭代器的分类
功能角度:按操作能力(是由容器的底层结构决定)

list中拥有的迭代器类型

forward_list拥有的迭代器类型

vector拥有的迭代器类型

使用角度:按遍历方向 + 读写权限

其次就是具体原因:
一、std::list的底层结构与迭代器限制
std::list是 C++ STL 中双向链表的实现,其底层结构是:
- 每个元素被封装在独立的节点中,节点包含:数据域(存储元素)、前驱指针(指向前一个节点)、后继指针(指向后一个节点);
- 节点在内存中非连续存储(地址随机),只能通过指针依次访问(无法直接跳转到第 n 个元素)。
这种结构直接决定了std::list的迭代器类型是 “双向迭代器(Bidirectional Iterator)”,它仅支持以下操作:
- 自增(++it):移动到下一个节点;
- 自减(–it):移动到上一个节点;
- 解引用( * it):获取当前节点的元素;
- 不等于比较(it1 != it2):判断是否指向同一个节点。
关键限制:双向迭代器不支持随机访问(如it + 3、it - 2这类直接跳转操作),也无法通过[]运算符访问指定位置的元素。
二、算法库std::sort的核心依赖:随机访问迭代器
标准库中的std::sort(定义在< algorithm >中)是一个通用排序算法,其内部实现通常基于快速排序(或其改进版,如 introsort),这类算法的高效性严重依赖于随机访问能力,具体表现为:
- 频繁访问 “中间元素”:快速排序需要选取 “枢轴元素”(通常是序列中间的元素),这要求能通过begin + (end - begin) / 2直接定位到中间位置;
- 高效交换元素:排序过程中需要交换任意位置的元素,依赖迭代器的+、-操作快速计算元素位置;
- 计算区间长度:需要通过end - begin快速获取序列长度,用于判断是否继续递归排序。
这些操作都要求迭代器必须是 “随机访问迭代器(Random Access Iterator)”—— 这种迭代器支持+、-、[]、<、>等操作(如std::vector、std::array的迭代器)。
三、std::list用std::sort报错的根本原因:迭代器类型不兼容
由于std::list的迭代器是双向迭代器,而std::sort要求随机访问迭代器,两者的 “能力集” 不匹配:
- 当调用std::sort(list.begin(), list.end())时,编译器会检查迭代器类型是否满足std::sort的模板要求(通过迭代器特性std::iterator_traits判断);
- 由于双向迭代器缺少std::sort必需的随机访问操作(如+、-),编译器会触发模板实例化失败,报 “迭代器类型不匹配” 的错误(通常提示 “没有与操作符 + 匹配的重载”)。
四、std::list自带sort成员函数的原因:适配链表特性的高效实现
std::list的成员函数sort是专门为链表结构设计的,其核心逻辑基于归并排序(Merge Sort),这种算法的特性完美适配链表:
- 归并排序的核心操作是 “合并两个有序序列”,对于链表而言,合并仅需调整节点的前驱 / 后继指针(无需移动元素本身),时间复杂度为 O (n),效率极高;
- 归并排序不需要随机访问,仅需顺序遍历(通过双向迭代器的++操作即可完成),完全兼容链表的迭代器能力;
- 避免元素拷贝开销:链表节点的 “值” 通常存储在堆上,归并排序通过指针调整完成排序,无需复制元素(而std::sort对链表排序时,每次交换都需要拷贝元素,开销极大)。
因此,list::sort的时间复杂度是 O (n log n),且常数因子远低于 “强行用std::sort对链表排序” 的情况(后者即使能编译,也会因频繁的迭代器移动和元素拷贝导致效率骤降)。
1.4.1 sort相关性能分析

算法库中sort的形参名称也暗示了什么类型的迭代器适用该函数
list<int> lt2 = { 1,2,3,4,5 };
//sort(lt2.begin(), lt2.end());不支持,会报错
lt2.sort();
再比如说算法库中的reverse

这里reverse要求双向迭代器,但随机迭代器(例如string的迭代器)也可以使用逆置的原因是一种继承关系
继承就是子类是一个特殊的父类(存在一个父子关系,父类满足的子类都满足),随机迭代器是一个特殊的双向迭代器,随机迭代器也是一个特殊的单向迭代器,双向迭代器也是一个特殊的单向迭代器。这些关系就如正方形是一个特殊的长方形一样

这里就意味着要求随机只能传随机,要求双向既可以传双向也可以传随机,如果形参是一个Forward Iterator,既可以传单向也可以传双向也可以传随机
后面我会单独写一篇继承的文章,继承还做了一层高度抽象

InputIterator就是只写,OutputIterator就是只读,可以认为所有的迭代器都是只写/只读的迭代器
就比如说算法库中的find

1.5 merge
归并要求两个链表必须是有序的(归并前先排序,和归并排序一样),将一个链表归并到另外一个链表


1.6 unique

去重也是要求先排序的,把重复的数据去掉只留下一个,过程类似双指针去重,一前一后两个指针,若两个指针指向的值相同,就把后一个值删掉

如图不经过排序,不相邻的值是无法完成去重的

1.7 remove 和 remove_if

remove和erase有相似支持,erase是给一个迭代器位置去删除迭代器指向的值,remove是给一个值去删除

remove_if设计一个仿函数问题,满足某个条件才删除(该条件用仿函数实现)
1.8 splice

它叫接合,本质上是一种转移,可以把一个链表里的结点挪到另外一个链表中去

但是其也可以在当前链表进行挪动,不仅限于两个链表之间的挪动

结语

