在 C++ 的 STL(标准模板库)中,list 是双向循环链表 容器,它和我们熟悉的vector、array特性截然不同,擅长处理频繁插入、删除元素的场景,是 C++ 开发中高频使用的序列式容器。
这篇笔记会从核心特性、常用操作、底层原理、适用场景四个维度,帮你彻底掌握 STL list 的使用。
一、list 核心特性(必记)
- 底层结构 :双向循环链表,每个元素都包含数据 + 前驱指针 + 后继指针 ,元素在内存中不连续存储。
- 访问效率 :不支持随机访问 (不能用
[]或at()直接访问第 n 个元素),只能通过迭代器从头 / 尾遍历查找。 - 插入 / 删除 :在任意位置 插入、删除元素的效率都是O(1)(常数时间),且不会导致迭代器失效(除了指向被删除元素的迭代器)。
- 迭代器 :属于双向迭代器 ,只能
++/--,不支持+n、-n跳跃操作。 - 对比 vector :
- vector:内存连续,支持随机访问,尾部插入 / 删除快,中间插入 / 删除慢;
- list:内存不连续,不支持随机访问,任意位置插入 / 删除都快。
二、list 必备头文件
使用list必须包含头文件,命名空间默认使用std:
cpp
#include <list> // 核心头文件
using namespace std;
三、list 常用操作(代码示例)
1. 定义与初始化
list 支持多种初始化方式,和其他 STL 容器用法一致:
cpp
// 1. 空list
list<int> l1;
// 2. 指定大小,初始值为0
list<int> l2(5); // 5个0
// 3. 指定大小+初始值
list<int> l3(5, 10); // 5个10
// 4. 拷贝初始化
list<int> l4(l3); // 拷贝l3所有元素
// 5. 区间初始化
int arr[] = {1,2,3,4,5};
list<int> l5(arr, arr+5); // 用数组初始化
2. 元素访问
list没有 [] 运算符,只能访问首尾元素:
cpp
list<int> l = {1,2,3,4,5};
// 访问第一个元素
cout << l.front() << endl; // 输出1
// 访问最后一个元素
cout << l.back() << endl; // 输出5
3. 插入元素
list 支持头部、尾部、任意位置插入,效率极高:
cpp
list<int> l;
// 1. 尾部插入
l.push_back(10);
l.emplace_back(20); // 推荐:直接在容器内构造,效率更高
// 2. 头部插入
l.push_front(5);
l.emplace_front(1); // 推荐
// 3. 任意位置插入(需要迭代器)
auto it = l.begin();
++it; // 迭代器只能++,不能+1
l.insert(it, 8); // 在第二个位置插入8
4. 删除元素
删除操作同样高效,注意区分不同删除函数:
cpp
list<int> l = {1,3,5,3,7};
// 1. 删除尾部元素
l.pop_back();
// 2. 删除头部元素
l.pop_front();
// 3. 删除指定位置元素
auto it = l.begin();
l.erase(it); // 删除迭代器指向的元素
// 4. 删除所有指定值的元素
l.remove(3); // 删除所有值为3的元素
// 5. 清空所有元素
l.clear();
- 容量操作
cpp
list<int> l = {1,2,3};
// 获取元素个数
cout << l.size() << endl; // 输出3
// 判断是否为空
cout << l.empty() << endl; // 输出0(false)
// 重新指定大小
l.resize(5); // 扩大到5个元素,新增元素默认为0
l.resize(2, 10); // 缩小到2个元素,多余元素被删除
6. 迭代器遍历
list 只能用迭代器 或范围 for遍历,不能用下标:
cpp
list<int> l = {10,20,30};
// 方式1:迭代器遍历(推荐)
for(list<int>::iterator it = l.begin(); it != l.end(); ++it){
cout << *it << " "; // 输出10 20 30
}
// 方式2:auto简化迭代器
for(auto it = l.begin(); it != l.end(); ++it){
cout << *it << " ";
}
// 方式3:范围for
for(int num : l){
cout << num << " ";
}
7. 高级操作
list 独有的高效操作,其他容器很难实现:
cpp
list<int> l1 = {1,3,5};
list<int> l2 = {2,4,6};
// 1. 拼接两个list(直接转移节点,不拷贝元素)
l1.splice(l1.end(), l2); // 把l2所有元素拼接到l1尾部
// 2. 反转链表
l1.reverse(); // {6,5,4,3,2,1}
// 3. 排序(默认升序)
l1.sort(); // {1,2,3,4,5,6}
// 4. 去重(必须先排序,否则只删除连续重复元素)
l1.push_back(6);
l1.unique(); // 删除连续重复的6
四、list 底层原理(简单理解)
list 的每个节点结构大致如下(STL 源码简化版):
cpp
template <class T>
struct ListNode {
T data; // 数据
ListNode* prev; // 前驱指针,指向前一个元素
ListNode* next; // 后继指针,指向后一个元素
};
- 链表通过
prev和next串联所有节点,内存分散在堆中; - 插入 / 删除只需要修改指针指向,不需要移动大量元素,这是它高效的核心原因。
五、list 适用场景
优先使用list的场景:
- 需要频繁在中间位置插入、删除元素;
- 不需要随机访问元素,只需要遍历操作;
- 对插入 / 删除效率要求极高,且元素个数动态变化大。
不建议使用list的场景:
- 需要频繁访问第 n 个元素(用
vector); - 内存空间紧张(list 每个节点多存两个指针,内存开销更大)。
六、常见坑点(避坑指南)
- 不能用下标访问 :
list[0]会直接编译报错; - 迭代器不能跳跃 :
it + 2非法,只能++it、--it; - 去重前先排序 :
unique()只删除连续重复元素,不排序则去重不彻底; - splice 是转移元素:不是拷贝,执行后原 list 元素会被清空。
总结
- STL list 是双向循环链表 ,核心优势是任意位置高效插入 / 删除;
- 不支持随机访问,迭代器仅支持
++/--; - 常用操作:
push_front/pop_front、insert/erase、splice、sort、reverse; - 适用频繁增删、无需随机访问的场景,和 vector 互补使用。