1.1 list介绍
std::list 是 C++ 标准库中提供的一个双向链表容器,属于序列容器的一种。它基于带头结点的双向循环链表实现,支持在任意位置高效地插入和删除元素,时间复杂度为 O(1),但不支持随机访问。
与 std::vector 不同,std::list 的元素在内存中不是连续存储的,每个节点包含数据域和两个指针(前驱和后继),因此其空间开销较大,但在频繁插入/删除操作场景下性能更优。
关键特性:
- 支持双向遍历
- 插入、删除操作不会导致其他迭代器失效(除被删除节点对应的迭代器)
- 不支持下标访问(如
list[i]) - 可以在任意位置插入或删除元素
#1.2 list的使用
1.2.1 list的构造
| 构造函数( (constructor)) | 接口说明 |
|---|---|
| list (size_type n, const value_type& val =value_type()) | 构造的list中包含n个值为val的元素 |
| list() | 构造空的list |
| list (const list& x) | 拷贝构造函数 |
| list (InputIterator first, InputIterator last) | 用[first, last)区间中的元素构造list |
cpp
#include <iostream>
#include <list>
int main() {
// 1. 构造空的 list
std::list<int> lst1;
std::cout << "lst1.size() = " << lst1.size() << std::endl; // 0
// 2. 构造 n 个值为 val 的元素
std::list<int> lst2(5, 10); // 5 个 10
for (int x : lst2) std::cout << x << " "; // 10 10 10 10 10
std::cout << std::endl;
// 3. 拷贝构造
std::list<int> lst3(lst2);
std::cout << "lst3.size() = " << lst3.size() << std::endl; // 5
// 4. 用迭代器区间构造(支持任何输入迭代器)
std::list<int> lst4{1, 2, 3, 4, 5};
std::list<int> lst5(lst4.begin(), lst4.end());
return 0;
}
重点说明:
- 使用
std::list<int>(n, val)可快速初始化多个相同值。 - 拷贝构造和范围构造均能保留原始数据结构。
- 初始容量由实际元素数量决定,无需预分配。
1.2.2 list iterator的使用
| 函数声明 | 接口说明 |
|---|---|
| begin + end | 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器 |
| rbegin + rend | 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin位置 |
std::list 提供多种迭代器类型,用于正向与反向遍历。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> lst = {10, 20, 30, 40, 50};
// 正向迭代器:begin() -> end()
std::cout << "正向遍历: ";
for (auto it = lst.begin(); it != lst.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 反向迭代器:rbegin() -> rend()
std::cout << "反向遍历: ";
for (auto rit = lst.rbegin(); rit != lst.rend(); ++rit) {
std::cout << *rit << " ";
}
std::cout << std::endl;
// 举例:修改元素(通过迭代器)
auto it = lst.begin();
++it; // 指向第二个元素 20
*it = 99; // 将 20 改为 99
std::cout << "修改后: ";
for (const auto& x : lst) std::cout << x << " ";
std::cout << std::endl;
return 0;
}
核心要点:
begin()指向第一个有效元素,end()指向最后一个元素之后的位置。rbegin()指向最后一个元素(即原end()的前一个),rend()指向第一个元素之前。- 对正向迭代器执行
++向后移动;对反向迭代器执行++实际是向前移动。 - 迭代器可直接解引用并修改内容。
1.2.3 list capacity
| 函数声明 | 接口说明 |
|---|---|
| empty | 检测list是否为空,是返回true,否则返回false |
| size | 返回list中有效节点的个数 |
判断容器状态的方法,常用于条件判断。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> lst;
std::cout << "是否为空?" << (lst.empty() ? "是" : "否") << std::endl; // 是
lst.push_back(1);
lst.push_back(2);
std::cout << "当前大小: " << lst.size() << std::endl; // 2
lst.clear();
std::cout << "清空后大小: " << lst.size() << std::endl; // 0
return 0;
}
注意事项:
empty()时间复杂度为 O(1),仅检查头结点是否指向自身。size()返回有效节点数,非容量上限。clear()清空所有元素,但不释放底层内存(若需释放,可配合swap或析构)。
1.2.4 list element access
| 函数声明 | 接口说明 |
|---|---|
| front | 返回list的第一个节点中值的引用 |
| back | 返回list的最后一个节点中值的引用 |
由于 list 不支持随机访问,只能通过首尾元素获取值。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> lst = {100, 200, 300};
if (!lst.empty()) {
std::cout << "front(): " << lst.front() << std::endl; // 100
std::cout << "back(): " << lst.back() << std::endl; // 300
}
// 可修改首尾元素
lst.front() = 999;
lst.back() = 888;
std::cout << "修改后: ";
for (const auto& x : lst) std::cout << x << " ";
std::cout << std::endl;
return 0;
}
重要提示:
front()与back()返回的是引用,可作为左值使用。- 若调用空列表的
front()/back(),将引发未定义行为。 - 建议先调用
empty()判断再访问。
1.2.5 list modifiers
| 函数声明 | 接口说明 |
|---|---|
| push_front | 在list首元素前插入值为val的元素 |
| pop_front | 删除list中第一个元素 |
| push_back | 在list尾部插入值为val的元素 |
| pop_back | 删除list中最后一个元素 |
| insert | 在list position 位置中插入值为val的元素 |
| erase | 删除list position位置的元素 |
| swap | 交换两个list中的元素 |
| clear | 清空list中的有效元素 |
提供丰富的插入与删除接口,适用于动态数据管理。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> lst = {10, 20, 30};
// 插入操作
lst.push_front(5); // 头部插入 5 → [5, 10, 20, 30]
lst.push_back(40); // 尾部插入 40 → [5, 10, 20, 30, 40]
// 在指定位置插入
auto it = lst.begin();
++it; // 指向 10
lst.insert(it, 15); // 插入 15 → [5, 10, 15, 20, 30, 40]
// 删除操作
lst.pop_front(); // 移除头部 5 → [10, 15, 20, 30, 40]
lst.pop_back(); // 移除尾部 40 → [10, 15, 20, 30]
// 删除指定位置元素
it = lst.begin();
++it; // 指向 15
lst.erase(it); // 移除 15 → [10, 20, 30]
// 交换两个 list
std::list<int> lst2 = {1, 2, 3};
lst.swap(lst2); // 交换内容
std::cout << "lst 内容: ";
for (const auto& x : lst) std::cout << x << " ";
std::cout << std::endl;
return 0;
}
关键点总结:
push_front/push_back:分别在头尾添加元素。insert(pos, val):在pos前插入一个新元素。erase(pos):删除pos所指元素,返回下一个迭代器。swap:交换两个 list 内容,效率高(仅交换指针)。clear():移除所有元素,但不改变容器本身。
1.2.6 list的迭代器失效
这是 std::list 最重要的特性之一。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> lst = {1, 2, 3, 4, 5};
auto it = lst.begin();
++it; // 指向 2
// 删除 it 所指元素(即 2)
lst.erase(it);
// 此时 it 已经失效!不能再使用
// 下面代码会导致未定义行为:
// std::cout << *it << std::endl; // ❌ 危险!
// 安全做法:重新获取迭代器
auto new_it = lst.begin();
++new_it; // 现在指向原来的 3
std::cout << "新的迭代器指向: " << *new_it << std::endl; // 3
return 0;
}
核心结论:
-
插入操作不会使任何迭代器失效,因为链表节点独立分配。
-
删除操作仅使被删除节点的迭代器失效,其余迭代器仍有效。
-
被删除节点的迭代器不可再使用,否则造成未定义行为。
-
推荐在
erase后立即更新迭代器,例如:cppit = lst.erase(it); // erase 返回下一个有效迭代器
总结:list的核心优势与适用场景
| 特性 | 说明 |
|---|---|
| ✅ 高效插入/删除 | 任意位置插入删除均为 O(1) |
| ✅ 迭代器安全(除被删节点外) | 其他迭代器不受影响 |
| ❌ 不支持随机访问 | 无法使用 lst[i] |
| ❌ 内存开销大 | 每个节点额外存储前后指针 |
| ⚠️ 适合场景 | 频繁增删、需要保持顺序、不需要随机访问 |
典型应用场景:
- 实现队列、栈(结合
push_front/pop_front) - 动态维护有序集合(如任务调度)
- 需要频繁在中间插入/删除的数据结构
补充建议:最佳实践
cpp
// 推荐写法:使用 erase + 返回值
for (auto it = lst.begin(); it != lst.end(); ) {
if (*it % 2 == 0) {
it = lst.erase(it); // erase 返回下一个迭代器
} else {
++it;
}
}
// 错误写法(可能导致迭代器失效)
for (auto it = lst.begin(); it != lst.end(); ++it) {
if (*it % 2 == 0) {
lst.erase(it); // 此时 it 已失效!后续 ++it 会出错
}
}
记住:
- 删除元素后,不要继续使用该迭代器。
- 使用
erase的返回值来更新迭代器,是最安全的方式。
✅ 本文全面覆盖了 std::list 的构造、迭代器、容量、元素访问、修改操作及迭代器失效机制,并辅以真实可运行代码示例,帮助开发者深入理解其行为与最佳实践。