跳到主要内容C++ STL 容器详解:双向链表 list 核心用法与注意事项 | 极客日志C++算法
C++ STL 容器详解:双向链表 list 核心用法与注意事项
综述由AI生成C++ STL 中的 list 是基于双向链表实现的序列容器,支持在任意位置高效插入和删除元素,时间复杂度为 O(1)。与 vector 不同,list 不支持随机访问且内存开销较大。本文详细解析了 list 的构造函数、迭代器使用规则、常用修改操作如 push_front/back、insert/erase 以及排序合并等成员函数。重点强调了迭代器失效问题及 reserve 方法不适用于 list 的特性,帮助开发者在实际项目中合理选型并避免常见陷阱。
灭霸21 浏览 C++ STL 容器详解:双向链表 list 核心用法与注意事项
list 简介
std::list 是 C++ 标准库中一个序列式容器,底层采用双向链表结构。这意味着每个元素(节点)都存储在独立的内存空间中,通过两个指针分别指向前后节点。
这种结构赋予了 list 在常数时间内于任意位置进行插入和删除操作的能力,时间复杂度为 O(1)。相比基于数组的 vector 或 deque,list 在处理频繁修改数据的场景下更具优势,无需移动大量元素。
主要特点:
- 优点:高效的插入和删除;支持双向迭代(可从前向后,也可从后向前)。
- 缺点:不支持随机访问(无法通过下标直接访问);每个节点需额外存储前后指针,内存开销略大。
与 forward_list(单向链表)不同,list 支持双向遍历,灵活性更高。
list 的使用
构造函数
list 提供了多种构造方式,适应不同的初始化需求:
- 默认构造:创建一个空列表。
explicit list(const allocator_type& alloc = allocator_type());
- 填充构造:创建包含 n 个值为 val 的元素。
explicit list(size_type n, const value_type& val = value_type(), const allocator_type& alloc = allocator_type());
- 范围构造:利用迭代器区间初始化。
template <class InputIterator>
list(InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type());
- 拷贝构造:复制另一个 list 对象。
list(const list& x);
代码示例:
#include <iostream>
std;
{
list<> l1;
;
;
arr[] = {, , , , , , };
;
;
}
{
();
;
}
#include <list>
using
namespace
void test01()
int
list<int> l2(6, 6)
list<int> l3(l2.begin(), l2.end())
int
5
2
0
1
3
1
4
list<int> l4(arr, arr + 7)
list<int> l5(l2)
int main()
test01
return
0
特殊成员函数
- 析构函数:对象销毁时自动调用,释放所有节点占用的内存。
- 赋值运算符重载:执行深拷贝,确保新容器内部元素被正确复制。
迭代器的使用
获取迭代器通常使用 iterator 类型,它是 std::list 的内部定义类型。
- 正向遍历:使用
begin() 和 end()。注意区间是左闭右开。
- 反向遍历:使用
rbegin() 和 rend()。
虽然范围 for 循环语法简洁,但仅支持正向遍历。若需逆向操作,必须使用反向迭代器。
int arr[] = {5, 2, 0, 1, 3, 1, 4};
list<int> l1(arr, arr + 7);
list<int>::iterator it = l1.begin();
while (it != l1.end()) {
cout << *it << ' ';
it++;
}
cout << endl;
list<int>::reverse_iterator rit = l1.rbegin();
while (rit != l1.rend()) {
cout << *rit << ' ';
rit++;
}
cout << endl;
容器相关函数
size 和 empty
size():返回当前有效数据个数。
empty():判断容器是否为空。
list<int> l1;
list<int> l2(6, 6);
cout << "l1 size: " << l1.size() << endl;
cout << "l2 size: " << l2.size() << endl;
cout << "l1 empty: " << l1.empty() << endl;
cout << "l2 empty: " << l2.empty() << endl;
修改容器内容相关函数
push_front / emplace_front
push_front():在头部添加元素,涉及拷贝或移动。
emplace_front():在头部原地构造元素,避免拷贝,效率更高。
int arr[] = {5, 2, 0, 1, 3, 1, 4};
int arr1[] = {1, 3, 1, 4, 5, 2, 0};
list<int> l1(arr, arr + 7);
list<int> l2(arr1, arr1 + 7);
l1.emplace_front(6);
l2.push_front(100);
emplace_back / push_back
l1.emplace_back(6);
l2.push_back(100);
insert / emplace
- 迭代器不失效:
insert 不会导致现有迭代器失效,仅修改链接关系。
- 功能:支持插入单个元素、多个相同元素、或迭代器区间。
- emplace:同
insert,但在指定位置直接构造元素。
int arr[] = {5, 2, 0, 1, 3, 1, 4};
list<int> l3(arr, arr + sizeof(arr) / sizeof(arr[0]));
list<int>::iterator pos = l3.begin();
pos++;
l3.insert(pos, 0);
l3.insert(pos, 5, 6);
list<int> lt4(6, 5);
l3.insert(pos, lt4.begin(), lt4.end());
for (auto e : l3) {
cout << e << ' ';
}
cout << endl;
pop_front / pop_back
pop_front():删除头部元素。
pop_back():删除尾部元素。
list<int> l1;
l1.push_back(0);
l1.push_back(1);
l1.push_back(2);
l1.push_back(3);
for (auto e : l1) cout << e << ' ';
cout << endl;
l1.pop_back();
l1.pop_back();
for (auto e : l1) cout << e << ' ';
cout << endl;
erase / clear
- erase:删除指定迭代器指向的节点。关键点:被删除节点的迭代器会失效,但
erase 会返回下一个有效迭代器。务必使用返回值继续遍历。
- clear:清空所有元素。
int arr[] = {5, 2, 0, 1, 3, 1, 4};
list<int> l1(arr, arr + sizeof(arr) / sizeof(arr[0]));
l1.erase(--l1.end());
l1.clear();
resize
调整容器大小。若缩小则截断多余部分;若扩大则尾插默认值或指定值。
list<int> l1(5, 3);
for (auto e : l1) cout << e << ' ';
cout << endl;
l1.resize(7, 6);
for (auto e : l1) cout << e << ' ';
cout << endl;
l1.resize(2);
for (auto e : l1) cout << e << ' ';
cout << endl;
assign
替换容器内容。会先清空原数据,再根据传入参数(迭代器区间或值+数量)重新填充。
int arr[] = {5, 2, 0, 1, 3, 1, 4};
list<int> l1(arr, arr + 7);
list<int> l2;
l2.assign(l1.begin(), l1.end());
for (auto e : l2) cout << e << ' ';
cout << endl;
swap
交换两个容器的内容,这是一个高效操作,仅交换内部指针,不涉及元素拷贝。
list<int> l1(4, 2);
list<int> l2(4, 6);
l1.swap(l2);
for (auto e : l1) cout << e << ' ';
cout << endl;
for (auto e : l2) cout << e << ' ';
cout << endl;
list 的相关操作函数
sort
对 list 进行排序。由于是双向链表,它使用内部归并排序,时间复杂度 O(n log n)。
list<int> lt;
lt.push_back(4); lt.push_back(7); lt.push_back(5);
lt.push_back(9); lt.push_back(6); lt.push_back(0); lt.push_back(3);
lt.sort();
for (auto e : lt) cout << e << ' ';
cout << endl;
remove
移除所有等于指定值的元素。注意这是原地修改,匹配的是值而非迭代器。
list<int> lt;
lt.push_back(1); lt.push_back(4); lt.push_back(3);
lt.push_back(3); lt.push_back(2); lt.push_back(2); lt.push_back(3);
lt.remove(3);
for (auto e : lt) cout << e << ' ';
cout << endl;
unique
删除连续的重复元素。因此通常需要先排序,否则非相邻的重复元素不会被删除。
lt.sort();
lt.unique();
for (auto e : lt) cout << e << ' ';
cout << endl;
merge
将两个已排序的 list 合并为一个。合并后源列表变为空,所有元素移至目标列表。要求两个列表均有序。
list<int> lt1;
lt1.push_back(3); lt1.push_back(8); lt1.push_back(1);
list<int> lt2;
lt2.push_back(6); lt2.push_back(2); lt2.push_back(9); lt2.push_back(5);
lt1.sort();
lt2.sort();
lt1.merge(lt2);
for (auto e : lt1) cout << e << ' ';
cout << endl;
reserve
注意:std::list 不支持 reserve() 方法。因为链表内存是动态分散分配的,不存在连续内存块预分配的概念。该方法仅适用于 vector 等连续内存容器。
在实际开发中,选择 list 还是 vector 取决于具体场景。如果频繁在中间插入删除且不需要随机访问,list 是更好的选择;反之,若追求缓存友好性和顺序访问性能,vector 更优。理解这些底层差异,才能写出更健壮的 C++ 代码。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online