应直接使用 std::list 替代手写双向链表,它专为频繁插入/删除优化,支持双向迭代器、O(1) 插删、迭代器删除后不失效(除被删者),但不支持随机访问,遍历查找为 O(n),需警惕迭代器失效、算法误用及错误删除模式。

怎么用 std::list 替代手写双向链表
直接用 std::list,别自己实现——它就是标准库里专为频繁插入/删除设计的双向链表,底层已做指针管理、内存安全和迭代器失效防护。
常见错误是把它当 std::vector 用:比如反复 list[i] 访问(不支持随机访问)、或用 ++it 遍历时意外跳过元素(没检查 it != lst.end())。
-
std::list迭代器是双向的,支持++it和--it,但不支持it + n - 插入删除都是
O(1),但遍历查找是O(n),别在大 list 里反复调用std::find后再删 - 构造时别传错参数:
std::list<int> lst(5, 42)</int>创建含 5 个 42 的 list;std::list<int> lst{1,2,3}</int>是初始化列表
push_front / push_back 和 insert 怎么选
看位置:头尾固定操作用 push_front 或 push_back;中间插入必须用 insert,且一定要传有效迭代器(不能传已失效的、或 end() 以外的 const 迭代器去改)。
典型翻车现场:lst.insert(lst.begin(), x) 没问题,但 lst.insert(it, x) 前若 it 来自另一个被清空的 list,就会未定义行为。
立即学习“C++免费学习笔记(深入)”;
-
push_front/push_back返回 void,效率最高,适用于队列/栈场景 -
insert(it, val)在it之前插入,it必须属于当前 list,且不能是end()(除非你真想插到末尾——但这时该用push_back) - 批量插入用
insert(pos, first, last),注意first和last要同属一个容器,且不能是当前 list 的迭代器(会迭代器失效)
删除元素时 erase 和 remove 的区别
erase 删迭代器指向的单个/区间元素,remove 删值匹配的所有节点——但 remove 不是成员函数,是算法,要配合 erase 一起用,否则只“逻辑删除”不释放内存。
新手常写 lst.remove(x) 就以为删干净了,其实没问题;但若写 std::remove(lst.begin(), lst.end(), x),那只是把匹配元素挪到末尾,没真正删。
- 删一个位置:
lst.erase(it),返回下一个有效迭代器(可用来安全遍历删除) - 删所有值为
x的节点:lst.remove(x)(成员函数,安全高效) - 删满足条件的节点:
lst.remove_if([](int v) { return v % 2 == 0; }) - 别混用
std::remove和std::list,它专为随机访问容器设计,对 list 是 O(n²)
迭代器失效后还用会发生什么
std::list 的插入操作(除 splice 特殊情况)不会使其他迭代器失效,这是它和 std::vector 最关键的区别;但删除操作会让被删节点的迭代器立刻失效——继续解引用或递增它,就是未定义行为。
最隐蔽的坑:循环中边遍历边删,却没用 erase 返回值更新迭代器:
for (auto it = lst.begin(); it != lst.end(); ++it) {
if (*it == 0) lst.erase(it); // 错!it 失效,++it 崩溃
}
- 安全写法:
it = lst.erase(it)(删完 it 指向下一节点),或删前先auto next = std::next(it) - 用范围 for 循环?别删——
for (auto& x : lst)中删x会导致迭代器失效,编译可能过,运行必崩 -
splice移动节点不分配内存,也不使迭代器失效,适合归并两个 list
std::list 看似简单,但迭代器生命周期、删除语义、算法适配这三处,稍不注意就掉进未定义行为的坑里。实际项目中,比“怎么用”更关键的是“什么时候不该用”——比如需要频繁按索引查,就该换 std::deque 或带索引映射的结构。










