list
1. list的介绍及使用

1.1 list的介绍

如图,list实际上就是一个双向链表
1.2 list的使用
list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已 达到可扩展的能力。以下为list中一些常见的重要接口。
1.2.1 list的构造

1. list (size_type n, const value_type& val = value_type())
函数解释与功能
创建一个包含n个元素的list容器,所有元素均初始化为val。若不指定val,则使用元素类型的默认值(如int默认 0,std::string默认空字符串)。
实例
// 创建包含5个值为3的int列表
std::list<int> countList(5, 3); // 元素: [3, 3, 3, 3, 3]
// 创建包含3个空字符串的string列表(使用默认值)
std::list<std::string> strList(3); // 元素: ["", "", ""]
应用场景
- 快速初始化固定长度、统一初始值的列表,如计数器列表、标记列表。
- 批量初始化状态变量(如设置多个对象的初始状态为
false/0)。
2. list()
函数解释与功能
默认构造函数,创建一个空的list容器,不包含任何元素,后续可通过push_back()/push_front()等方法动态添加元素。
实例
// 创建空的int列表
std::list<int> emptyList;
// 动态添加元素
emptyList.push_back(10); // 列表变为: [10]
emptyList.push_front(5); // 列表变为: [5, 10]
应用场景
- 初始元素数量未知,需在程序运行中动态构建列表(如根据用户输入、外部数据逐步添加元素)。
- 作为临时存储容器,后续根据条件筛选 / 补充数据。
3. list (const list& x)
函数解释与功能
拷贝构造函数,通过深拷贝另一个list容器x的所有元素,创建一个新的独立list容器。修改新容器不会影响原容器x。
实例
// 原列表
std::list<int> originalList = {1, 2, 3};
// 拷贝构造新列表
std::list<int> copyList(originalList); // copyList: [1, 2, 3]
// 修改新列表,原列表不受影响
copyList.push_back(4); // copyList: [1, 2, 3, 4];originalList 仍为 [1, 2, 3]
应用场景
- 数据备份:复制现有列表内容,后续修改新列表时不破坏原数据。
- 函数传参:以值传递方式传入
list,避免函数内部修改影响外部原数据。
4. list (InputIterator first, InputIterator last)
函数解释与功能
迭代器区间构造函数,使用[first, last)(左闭右开区间)内的元素创建新list。支持从其他容器(如vector、array)或同一list的子区间复制元素。
实例
// 从vector的子区间构造list
std::vector<int> vec = {10, 20, 30, 40};
std::list<int> listFromVec(vec.begin() + 1, vec.end()); // 元素: [20, 30, 40]
// 从list的子区间构造新list
std::list<int> originalList = {1, 2, 3, 4};
std::list<int> subList(++originalList.begin(), --originalList.end()); // 元素: [2, 3]
应用场景
- 跨容器数据转换:将
vector/array中的部分数据快速转换为list。 - 数据筛选:从现有列表中提取子区间,创建新的过滤列表(如保留中间部分元素)。
1.2.2 list iterator的使用
此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。

C++ std::list 迭代器接口详解
begin() + end()
函数解释与功能
begin():返回指向list第一个元素的正向迭代器。end():返回指向list最后一个元素的下一个位置的正向迭代器(尾后迭代器),不指向任何有效元素,仅作为遍历的终止标记。- 二者构成左闭右开区间
[begin(), end()),表示list的所有有效元素范围。
实例
#include <iostream>
#include <list>
int main() {
std::list<int> nums = {1, 2, 3, 4, 5};
// 1. 正向遍历list
std::cout << "正向遍历:";
for (std::list<int>::iterator it = nums.begin(); it != nums.end(); ++it) {
std::cout << *it << " "; // 输出:1 2 3 4 5
}
std::cout << std::endl;
// 2. 修改元素值
for (auto it = nums.begin(); it != nums.end(); ++it) {
*it *= 2; // 每个元素乘以2
}
std::cout << "修改后:";
for (auto num : nums) {
std::cout << num << " "; // 输出:2 4 6 8 10
}
return 0;
}
应用场景
- 正向遍历
list容器,实现元素的读取、修改操作。 - 结合 STL 算法(如
std::find、std::copy)对list元素进行处理。 - 作为区间参数传递给其他容器的构造函数或算法,实现数据复制 / 筛选。
rbegin() + rend()
函数解释与功能
rbegin():返回指向list最后一个元素 的反向迭代器,等价于正向迭代器的end()位置。rend():返回指向list第一个元素的前一个位置 的反向迭代器,等价于正向迭代器的begin()位置。- 二者构成反向遍历的左闭右开区间
[rbegin(), rend()),遍历方向为从尾到头。
实例
#include <iostream>
#include <list>
int main() {
std::list<int> nums = {1, 2, 3, 4, 5};
// 1. 反向遍历list
std::cout << "反向遍历:";
for (std::list<int>::reverse_iterator it = nums.rbegin(); it != nums.rend(); ++it) {
std::cout << *it << " "; // 输出:5 4 3 2 1
}
std::cout << std::endl;
// 2. 反向修改元素(倒序修改)
for (auto it = nums.rbegin(); it != nums.rend(); ++it) {
*it += 10; // 从最后一个元素开始,每个元素加10
}
std::cout << "反向修改后:";
for (auto num : nums) {
std::cout << num << " "; // 输出:11 12 13 14 15
}
return 0;
}
应用场景
- 逆序遍历
list容器,实现从尾到头的元素读取、修改。 - 实现数据的逆序处理(如逆序输出日志、倒序遍历任务队列)。
- 结合 STL 算法(如
std::copy)将list元素逆序复制到其他容器中。
补充说明:
-
list迭代器支持++/--操作,但不支持+n/-n的随机访问,因此无法直接通过下标访问元素,只能通过迭代器逐步移动遍历。 -
begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
-
rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
1.2.3 list capacity
empty()
函数解释与功能
检测list容器是否为空。若容器中无任何元素,返回true;否则返回false。该函数时间复杂度为 O (1),直接判断容器状态,无需遍历元素。
实例
#include <iostream>
#include <list>
int main() {
std::list<int> nums;
// 初始状态为空
if (nums.empty()) {
std::cout << "list为空" << std::endl; // 输出:list为空
}
nums.push_back(10);
// 添加元素后非空
if (!nums.empty()) {
std::cout << "list不为空,元素个数:" << nums.size() << std::endl; // 输出:list不为空,元素个数:1
}
return 0;
}
应用场景
- 容器操作前的空状态检查,避免对空容器执行
front()/back()/pop()等操作导致未定义行为。 - 循环遍历前判断容器是否有元素,减少无效循环执行。
- 数据结构中作为队列 / 栈的判空条件(如任务队列、缓冲区)。
size()
函数解释与功能
返回list容器中有效元素的个数。该函数直接返回容器维护的元素计数,时间复杂度为 O (1)(C++11 及以上标准,现代 STL 实现均为 O (1))。
实例
#include <iostream>
#include <list>
int main() {
std::list<int> nums = {1, 2, 3, 4, 5};
std::cout << "初始元素个数:" << nums.size() << std::endl; // 输出:5
nums.push_back(6);
std::cout << "添加元素后个数:" << nums.size() << std::endl; // 输出:6
nums.pop_back();
std::cout << "删除元素后个数:" << nums.size() << std::endl; // 输出:5
return 0;
}
应用场景
- 动态获取容器当前元素数量,用于进度统计、数据规模判断。
- 结合循环条件,实现遍历次数控制(如处理前 N 个元素)。
- 容器扩容 / 缩容前的容量规划(如根据元素个数决定后续操作)。
补充说明:
empty()和size()都是容器状态查询接口,无修改容器内容的副作用。- 对于空
list,size()返回 0,此时empty()必然返回true,二者可以互相验证容器状态。
1.2.4 list element access

front()
函数解释与功能
返回list容器中第一个元素的引用 ,支持直接读取或修改该元素。空容器调用该函数会触发未定义行为,使用前需通过empty()判断容器非空。等价于*begin(),但更简洁直观。
实例
#include <iostream>
#include <list>
int main() {
std::list<int> taskList = {1, 2, 3};
// 读取队首任务
std::cout << "当前队首任务:" << taskList.front() << std::endl; // 输出:1
// 修改队首任务优先级
taskList.front() = 10;
std::cout << "修改后队首任务:" << taskList.front() << std::endl; // 输出:10
return 0;
}
应用场景
- 队列(FIFO)场景中,快速访问 / 修改队首元素(如获取下一个待处理任务)。
- 无需迭代器遍历,直接修改
list头部元素,提升操作效率。 - 结合
push_front()/pop_front()实现队列头部的入队 / 出队逻辑。
back()
函数解释与功能
返回list容器中最后一个元素的引用 ,支持直接读取或修改该元素。空容器调用该函数会触发未定义行为,使用前需通过empty()判断容器非空。等价于*(--end()),但更简洁直观。
实例
#include <iostream>
#include <list>
int main() {
std::list<int> logList = {100, 200, 300};
// 读取最新日志
std::cout << "最新日志ID:" << logList.back() << std::endl; // 输出:300
// 修改最新日志状态
logList.back() = 301;
std::cout << "修改后最新日志ID:" << logList.back() << std::endl; // 输出:301
return 0;
}
应用场景
- 栈(LIFO)场景中,快速访问 / 修改栈顶元素(如获取最后入栈的任务)。
- 无需迭代器遍历,直接修改
list尾部元素,提升操作效率。 - 结合
push_back()/pop_back()实现栈或队列尾部的入队 / 出队逻辑。
补充说明:
front()和back()返回的是引用,修改返回值会直接影响容器内的元素。- 若需只读访问,可使用
const front()/const back(),避免意外修改数据。 - 空容器调用这两个函数是常见错误,建议结合
if (!list.empty())做安全判断。
1.2.5 list modifiers

push_front
函数解释与功能
在 list 容器的首元素之前插入一个值为val的新元素,时间复杂度为 O (1),插入后该元素成为新的首元素。
实例
#include <list>
#include <iostream>
int main() {
std::list<int> lst = {2, 3, 4};
lst.push_front(1); // 在首元素前插入1
for (auto num : lst) std::cout << num << " "; // 输出:1 2 3 4
return 0;
}
应用场景
- 实现栈结构(头部作为栈顶)的入栈操作。
- 优先队列中,快速将高优先级任务插入到队首。
- 动态维护列表的头部元素,无需遍历尾部即可快速插入。
pop_front
函数解释与功能
删除 list 容器的第一个元素,时间复杂度为 O (1)。空容器调用会触发未定义行为,需配合empty()判断容器非空。
实例
#include <list>
#include <iostream>
int main() {
std::list<int> lst = {1, 2, 3, 4};
lst.pop_front(); // 删除首元素1
for (auto num : lst) std::cout << num << " "; // 输出:2 3 4
return 0;
}
应用场景
- 队列(FIFO)场景中,处理队首元素后的出队操作。
- 栈结构中,弹出栈顶元素(头部作为栈顶时)。
- 清理列表的头部过期数据(如旧日志、过时任务)。
push_back
函数解释与功能
在 list 容器的尾部插入一个值为val的新元素,时间复杂度为 O (1),插入后该元素成为新的尾元素。
实例
#include <list>
#include <iostream>
int main() {
std::list<int> lst = {1, 2, 3};
lst.push_back(4); // 在尾部插入4
for (auto num : lst) std::cout << num << " "; // 输出:1 2 3 4
return 0;
}
应用场景
- 队列(FIFO)场景中,向队尾添加新任务。
- 动态收集数据(如日志记录、用户输入数据),在列表尾部追加元素。
- 实现栈结构(尾部作为栈顶)的入栈操作。
pop_back
函数解释与功能
删除 list 容器的最后一个元素,时间复杂度为 O (1)。空容器调用会触发未定义行为,需配合empty()判断容器非空。
实例
#include <list>
#include <iostream>
int main() {
std::list<int> lst = {1, 2, 3, 4};
lst.pop_back(); // 删除尾元素4
for (auto num : lst) std::cout << num << " "; // 输出:1 2 3
return 0;
}
应用场景
- 栈(LIFO)场景中,弹出栈顶元素(尾部作为栈顶时)。
- 清理列表的尾部数据(如撤销操作,删除最后一步操作记录)。
- 双端队列场景中,处理队尾元素后的出队操作。
insert
函数解释与功能
在 list 容器的指定迭代器position位置前插入一个值为val的元素,时间复杂度为 O (1)(已知迭代器位置时)。支持多种重载(如插入多个元素、区间元素),核心重载为单元素插入。
实例
#include <list>
#include <iostream>
int main() {
std::list<int> lst = {1, 3, 4};
auto it = lst.begin();
std::advance(it, 1); // 指向元素3的位置
lst.insert(it, 2); // 在3之前插入2
for (auto num : lst) std::cout << num << " "; // 输出:1 2 3 4
return 0;
}
应用场景
- 在有序列表中保持顺序插入元素(如按优先级排序的任务队列)。
- 动态调整列表结构,如在特定节点前插入标记或分隔符。
- 结合
std::find等算法定位目标位置后插入元素,无需遍历整个列表。
erase
函数解释与功能
删除 list 容器中迭代器position指向的元素,时间复杂度为 O (1)(已知迭代器位置时)。也支持删除区间[first, last)的元素,空迭代器或无效迭代器调用会触发未定义行为。
实例
#include <list>
#include <iostream>
int main() {
std::list<int> lst = {1, 2, 3, 4};
auto it = lst.begin();
std::advance(it, 2); // 指向元素3的位置
lst.erase(it); // 删除元素3
for (auto num : lst) std::cout << num << " "; // 输出:1 2 4
return 0;
}
应用场景
- 删除列表中特定位置的元素(如根据条件筛选后删除无效数据)。
- 清理任务队列中已完成的任务节点。
- 批量删除满足条件的元素(注意迭代器失效问题,需使用
erase的返回值更新迭代器)。
swap
函数解释与功能
交换两个 list 容器的内容,时间复杂度为 O (1),仅交换内部指针,不拷贝元素。交换后两个容器的元素、迭代器均保持有效。
实例
#include <list>
#include <iostream>
int main() {
std::list<int> lst1 = {1, 2, 3};
std::list<int> lst2 = {4, 5, 6};
lst1.swap(lst2); // 交换两个list的内容
std::cout << "lst1: ";
for (auto num : lst1) std::cout << num << " "; // 输出:4 5 6
std::cout << "\nlst2: ";
for (auto num : lst2) std::cout << num << " "; // 输出:1 2 3
return 0;
}
应用场景
- 高效交换两个列表的内容,避免元素拷贝开销(如临时列表与主列表交换)。
- 实现列表的快速重置(如将当前列表与空列表交换,间接清空内容)。
- 算法中交换两个容器的数据,如排序算法中的分区交换。
clear
函数解释与功能
清空 list 容器中的所有有效元素,时间复杂度为 O (n)(销毁所有元素并释放内存)。清空后容器的size()变为 0,迭代器失效。
实例
#include <list>
#include <iostream>
int main() {
std::list<int> lst = {1, 2, 3, 4};
lst.clear(); // 清空所有元素
std::cout << "是否为空:" << std::boolalpha << lst.empty() << std::endl; // 输出:true
std::cout << "元素个数:" << lst.size() << std::endl; // 输出:0
return 0;
}
应用场景
- 容器复用前清空旧数据(如循环处理任务时,每次迭代前清空列表)。
- 释放大型列表占用的内存,避免内存泄漏。
- 数据重置场景,如重置用户会话数据、清空缓存列表。
补充说明:
push_front/pop_front、push_back/pop_back均为 O (1) 操作,适合双端队列或栈场景。insert/erase操作依赖迭代器位置,需注意迭代器失效问题(erase会使指向被删除元素的迭代器失效,其他迭代器不受影响)。swap仅交换内部状态,无元素拷贝开销,性能远高于手动赋值交换。
1.2.6 list的迭代器失效
前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入 时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭 代器,其他迭代器不会受到影响

迭代器失效的原因分析
1. 错误版本的问题
std::list的erase(it)操作有两个关键特性:
- 当调用
l.erase(it)时,it指向的节点会被从链表中移除并释放,it本身会立即失效(它指向的内存已被释放,成为野迭代器)。 - 但
erase函数会返回一个指向被删除节点的下一个节点的有效迭代器。
在错误版本中:
l.erase(it); // it 已失效
++it; // 对失效的it执行自增,触发未定义行为(如崩溃、死循环、内存错误)
erase(it)执行后,it已经指向无效内存,此时再执行++it,操作的是已释放的内存地址,导致迭代器失效问题。
2. 修正版本的原理
修正版本通过两种方式避免了失效:
- 方式 1:
l.erase(it++)it++是后置自增 ,执行逻辑是:先返回it的当前值(旧迭代器),再将it自增(指向原节点的下一个节点)。- 因此
erase接收到的是当前it(旧值),执行删除操作;而it已提前自增到下一个有效节点,删除后it仍然有效。
- 方式 2:
it = l.erase(it)erase(it)会返回指向被删除节点下一个节点的迭代器,将返回值赋值给it,直接让it指向有效节点,避免操作失效的旧it。
补充说明
std::list是双向链表结构,删除单个节点时,仅被删除节点的迭代器会失效,其他节点的迭代器不受影响 (与vector不同,vector删除元素会导致后续所有迭代器失效)。因此问题的核心是:删除后如何让it正确指向被删除节点的下一个有效节点,而不是继续操作已失效的it。
1.3 std::list 迭代器失效全场景解析与通用避坑指南
一、迭代器失效核心原理回顾
std::list底层是双向循环链表 ,每个节点独立存储并通过指针连接。这决定了其迭代器失效规则与vector等连续容器有本质区别:
- 插入操作 :仅修改指针链接,不移动或销毁现有节点,因此不会导致任何迭代器失效(包括插入位置前后的迭代器)
- 删除操作 :仅销毁被删除节点,仅指向被删除节点的迭代器失效,其他迭代器完全有效
- 特殊操作 (如
clear()、swap()、merge()等):可能有特殊的失效规则,需单独注意
二、完整迭代器失效场景详解
1. 插入操作(无迭代器失效)
所有插入操作(push_front/push_back/insert)都不会导致任何迭代器失效,包括指向插入位置前后节点的迭代器。
原理:插入仅在目标位置创建新节点并调整前后指针,不改变现有节点的内存地址和链接关系。
示例(安全):
#include <list>
#include <iostream>
int main() {
std::list<int> l = {1, 3, 4};
auto it = l.begin(); // 指向1
++it; // 指向3
l.insert(it, 2); // 在3前插入2,it仍有效指向3
for (auto num : l) std::cout << num << " "; // 1 2 3 4
std::cout << "\n当前it指向:" << *it << std::endl; // 输出3,迭代器有效
return 0;
}
2. 删除操作(仅被删节点迭代器失效)

示例(merge 操作):
std::list<int> l1 = {1, 3, 5};
std::list<int> l2 = {2, 4, 6};
auto it1 = l1.begin(); // 指向1
auto it2 = l2.begin(); // 指向2
l1.merge(l2); // 合并后l2为空
std::cout << *it1 << std::endl; // 有效,输出1
// std::cout << *it2 << std::endl; // 无效,it2指向的元素已转移到l1
三、通用避坑方法与最佳实践
1. 迭代器失效的核心避坑原则
- 删除必更新 :调用
erase()后,必须通过返回值更新迭代器 ,或使用it++技巧,严禁继续使用原迭代器 - 判空再操作 :调用
pop_front()/pop_back()前,先用empty()判断容器非空,避免空容器操作导致的未定义行为 - 避免悬垂迭代器:容器销毁后,所有关联迭代器立即失效,不可再使用
- 区间操作谨慎 :删除区间
[first, last)后,整个区间内的迭代器全部失效,需重新获取有效迭代器 - 特殊操作留意 :
merge()/splice()等操作会改变元素所属容器,需注意原容器迭代器的有效性
2. 遍历删除的标准安全范式
范式 1:条件删除(推荐)
// 遍历并删除所有偶数元素
auto it = l.begin();
while (it != l.end()) {
if (*it % 2 == 0) {
it = l.erase(it); // 关键:用返回值更新迭代器
} else {
++it; // 仅当不删除时才自增
}
}
范式 2:批量删除(区间删除)
// 删除所有元素(等价于clear(),但更灵活)
auto first = l.begin();
auto last = l.end();
l.erase(first, last); // 区间内迭代器全部失效,之后需重新初始化迭代器
3. 迭代器有效性的额外保障技巧
-
使用
std::advance/std::next安全移动迭代器auto it = l.begin();
std::advance(it, 2); // 安全移动2步,等价于++it; ++it;
auto next_it = std::next(it); // 获取下一个迭代器,不修改原it -
保存关键迭代器前先判断有效性
if (it != l.end()) {
auto saved_it = it; // 仅当it有效时才保存
// ...后续使用saved_it
} -
C++20 范围 for 循环替代(不涉及删除时)
// 只读遍历,无需手动管理迭代器
for (const auto& elem : l) {
std::cout << elem << " ";
} -
避免在迭代过程中修改容器结构(除删除当前元素外)
- 如需在遍历中插入元素,建议先记录位置,遍历结束后统一插入
- 如需复杂的增删组合操作,考虑使用临时容器缓存操作结果
四、常见误区澄清
-
误区 :
std::list的迭代器永远不会失效纠正 :删除操作会导致被删节点的迭代器失效,
clear()会导致所有迭代器失效 -
误区 :
erase(it++)与it = erase(it)效果完全相同纠正 :效果相同,但
it = erase(it)更直观且符合现代 C++ 风格,推荐使用 -
误区 :
swap()会导致迭代器失效纠正 :
swap()仅交换容器内部指针,迭代器仍指向原元素,只是所属容器改变,不会失效 -
误区:插入操作会导致插入位置后的迭代器失效
纠正 :
std::list的插入操作不会导致任何迭代器失效,与vector不同
五、总结
std::list的迭代器失效规则相对简单,核心是仅被删除节点的迭代器失效,插入操作不会导致任何迭代器失效。掌握以下要点即可安全使用:
-
删除元素时,必须通过
erase()的返回值更新迭代器 或使用it++技巧 -
调用
pop_front()/pop_back()前,先用empty()判断容器非空 -
特殊操作(
merge()/splice()等)需注意迭代器所属容器的变化// 只读遍历,无需手动管理迭代器
for (const auto& elem : l) {
std::cout << elem << " ";
} -
避免在迭代过程中修改容器结构(除删除当前元素外)
- 如需在遍历中插入元素,建议先记录位置,遍历结束后统一插入
- 如需复杂的增删组合操作,考虑使用临时容器缓存操作结果
四、常见误区澄清
-
误区 :
std::list的迭代器永远不会失效纠正 :删除操作会导致被删节点的迭代器失效,
clear()会导致所有迭代器失效 -
误区 :
erase(it++)与it = erase(it)效果完全相同纠正 :效果相同,但
it = erase(it)更直观且符合现代 C++ 风格,推荐使用 -
误区 :
swap()会导致迭代器失效纠正 :
swap()仅交换容器内部指针,迭代器仍指向原元素,只是所属容器改变,不会失效 -
误区:插入操作会导致插入位置后的迭代器失效
纠正 :
std::list的插入操作不会导致任何迭代器失效,与vector不同
五、总结
std::list的迭代器失效规则相对简单,核心是仅被删除节点的迭代器失效,插入操作不会导致任何迭代器失效。掌握以下要点即可安全使用:
- 删除元素时,必须通过
erase()的返回值更新迭代器 或使用it++技巧 - 调用
pop_front()/pop_back()前,先用empty()判断容器非空 - 特殊操作(
merge()/splice()等)需注意迭代器所属容器的变化 - 容器销毁后,所有关联迭代器立即失效