一、std::list 与 std::forward_list 概述
std::list
和 std::forward_list
都是 C++ STL 中的链表容器,用于存储动态大小的元素集合。
两者的核心区别在于链表结构和遍历方式:
特性 | std::list | std::forward_list |
---|---|---|
链表类型 | 双向链表 | 单向链表 |
插入/删除效率 | 任意位置 O(1) | 任意位置 O(1) |
随机访问 | 不支持 | 不支持 |
内存占用 | 较高(每个节点包含前后指针) | 较低(仅有后继指针) |
适用场景 | 需要频繁中间插入/删除 | 内存敏感且仅需单向遍历 |
二、std::list 详解
1. 核心特性
-
双向链表结构 :每个节点包含
prev
和next
指针,支持从前到后或从后到前的遍历。 -
高效插入与删除:在任意位置插入或删除元素的时间复杂度为 O(1)。
-
不支持随机访问 :不能使用下标
[]
或at()
访问元素。 -
迭代器稳定性:插入或删除元素不会导致其他迭代器失效(除非删除的正是该迭代器所指向的元素)。
2. 常用函数示例
cpp
#include <list>
#include <iostream>
int main() {
// 1. 初始化
std::list<int> l1; // 空 list
std::list<int> l2(5, 100); // 5 个 100: {100, 100, 100, 100, 100}
std::list<int> l3 = {1, 2, 3}; // 列表初始化
// 2. 插入与删除
l1.push_back(4); // 尾部插入
l1.push_front(0); // 头部插入
l1.pop_back(); // 尾部删除
l1.pop_front(); // 头部删除
l1.insert(++l1.begin(), 5); // 插入到指定位置
l1.erase(l1.begin()); // 删除指定位置
// 3. 遍历
for (const auto& val : l1) {
std::cout << val << " ";
}
// 4. 其他操作
l1.sort(); // 排序
l1.reverse(); // 反转
l1.merge(l2); // 合并两个有序链表
l1.unique(); // 去重
return 0;
}
3. 性能优势
-
插入和删除中间元素效率高;
-
迭代器稳定;
-
节点独立分配内存,适合动态增长场景。
三、std::forward_list 详解
1. 核心特性
-
单向链表结构:每个节点仅包含指向下一个节点的指针。
-
插入与删除效率高:操作复杂度同样为 O(1)。
-
仅支持单向遍历:无法向前访问。
-
内存占用更低:每个节点少一个指针,节省空间。
2. 常用函数示例
cpp
#include <forward_list>
#include <iostream>
int main() {
// 1. 初始化
std::forward_list<int> fl1; // 空链表
std::forward_list<int> fl2(5, 100); // 5 个 100
std::forward_list<int> fl3 = {1, 2, 3}; // 列表初始化
// 2. 插入与删除
fl1.push_front(0); // 头部插入
fl1.pop_front(); // 头部删除
auto it = fl1.before_begin(); // 获取头节点前的位置
fl1.insert_after(it, 5); // 在指定位置后插入
fl1.erase_after(it); // 删除指定位置后的元素
// 3. 遍历
for (const auto& val : fl3) {
std::cout << val << " ";
}
// 4. 其他操作
fl1.sort(); // 排序
fl1.reverse(); // 反转
fl1.merge(fl2); // 合并两个有序链表
fl1.unique(); // 去重
return 0;
}
3. 性能优势
-
节省内存;
-
插入/删除操作高效;
-
适用于单向链表或嵌入式等内存敏感场景。
四、std::list 与 std::forward_list 对比
特性 | std::list | std::forward_list |
---|---|---|
链表类型 | 双向链表 | 单向链表 |
插入/删除效率 | 任意位置 O(1) | 任意位置 O(1) |
随机访问 | 不支持 | 不支持 |
内存占用 | 较高 | 较低 |
迭代器稳定性 | 插入/删除不影响其他迭代器 | 同上 |
适用场景 | 双向遍历、频繁插入删除 | 内存敏感、单向遍历 |
五、性能优化建议
1. std::list 优化
-
减少频繁插入/删除带来的内存碎片;
-
尽量在插入时维护顺序,减少排序调用;
-
合并多个已排序链表使用
merge()
。
2. std::forward_list 优化
-
仅需单向遍历时优先使用;
-
合理使用
before_begin()
在头部插入; -
避免频繁中间插入造成性能下降。
六、常见陷阱与解决方案
1. 不支持随机访问
链表无法通过下标访问元素,应使用迭代器。
cpp
std::list<int> l = {1, 2, 3};
auto it = l.begin();
std::advance(it, 2); // 移动到第3个元素
std::cout << *it; // 输出 3
2. 删除节点后迭代器失效
删除节点会使指向该节点的迭代器失效,应重新获取。
cpp
std::list<int> l = {1, 2, 3};
auto it = l.begin();
++it; // 指向 2
it = l.erase(it); // 删除 2,返回指向 3 的新迭代器
3. 内存占用较高
在内存敏感场景中优先使用 std::forward_list
。
cpp
std::forward_list<int> fl = {1, 2, 3};
七、典型应用场景
1. std::list 的应用
-
频繁中间插入/删除;
-
需要双向遍历;
-
内存不敏感的任务队列、日志系统。
cpp
std::list<std::string> tasks = {"Task1", "Task2"};
tasks.push_back("Task3");
tasks.insert(++tasks.begin(), "Task1.5");
2. std::forward_list 的应用
-
内存有限;
-
单向遍历;
-
仅操作头部元素(如队列、栈)。
cpp
std::forward_list<int> queue;
queue.push_front(1);
queue.push_front(2);
queue.pop_front(); // 删除头部元素
八、总结
容器 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
std::list | 插入/删除高效,支持双向遍历 | 内存占用高,不支持随机访问 | 频繁中间插入/删除 |
std::forward_list | 占用低,插入/删除高效 | 仅单向遍历 | 内存敏感、单向操作 |