C++ list 容器详解
一、list 容器简介
list 是 C++ 标准模板库(STL)中的双向链表容器 ,定义在 <list> 头文件中。与 vector 不同,list 中的元素在内存中不连续存储,每个元素都包含指向前后元素的指针。
主要特点
| 特性 |
说明 |
| 存储方式 |
双向链表,非连续内存 |
| 插入/删除 |
O(1),任意位置高效操作 |
| 随机访问 |
不支持,需遍历 |
| 迭代器 |
双向迭代器(Bidirectional Iterator) |
| 内存开销 |
较高(需存储指针) |
二、list 的基本操作接口
接口函数速查表
| 类别 |
函数 |
时间复杂度 |
| 构造 |
list(), list(n,val), list(first,last) |
O(1)/O(n) |
| 访问 |
front(), back() |
O(1) |
| 迭代器 |
begin(), end(), rbegin(), rend() |
O(1) |
| 容量 |
empty(), size(), max_size() |
O(1) |
| 插入 |
push_front(), push_back(), insert(), emplace() |
O(1)/O(n) |
| 删除 |
pop_front(), pop_back(), erase(), remove(), clear() |
O(1)/O(n) |
| 修改 |
swap(), resize(), assign() |
O(1)/O(n) |
| 特有 |
splice(), merge(), sort(), reverse(), unique() |
O(1)/O(n) |
使用注意事项
| 注意事项 |
详细说明 |
| 迭代器稳定性 |
list 的插入/删除操作不会使其他元素的迭代器失效,只有被删除元素的迭代器会失效 |
| 内存开销 |
每个节点额外存储前后两个指针,内存占用比 vector 大 |
| 缓存友好性 |
非连续内存导致缓存命中率较低,遍历性能可能不如 vector |
| 排序要求 |
merge() 和 unique() 使用前需确保列表已排序,否则结果不确定 |
| C++11 新特性 |
推荐使用 emplace_front()、emplace_back()、emplace() 系列函数进行原地构造,避免拷贝开销 |
2.1 构造函数
cpp
复制代码
std::list<int> lst1; // 空列表
std::list<int> lst2(5, 10); // 5个元素,值为10
std::list<int> lst3(lst2); // 拷贝构造
std::list<int> lst4(lst2.begin(), lst2.end()); // 范围构造
std::list<int> lst5 = {1, 2, 3, 4, 5}; // 初始化列表
2.2 元素访问
| 函数 |
函数声明 |
接口说明 |
| front() |
reference front(); const_reference front() const; |
返回 list 的第一个节点中值的引用 |
| back() |
reference back(); const_reference back() const; |
返回 list 的最后一个节点中值的引用 |
cpp
复制代码
std::list<int> lst = {1, 2, 3, 4, 5};
int first = lst.front(); // 1
int last = lst.back(); // 5
// 修改元素
lst.front() = 10; // [10, 2, 3, 4, 5]
lst.back() = 50; // [10, 2, 3, 4, 50]
注意:list 不支持 operator[] 和 at()
list 迭代器是双向迭代器,既不支持算术运算,也不支持 [] 运算符
2.3 容量函数
| 函数 |
函数声明 |
接口说明 |
| empty() |
bool empty() const; |
检测 list 是否为空,是返回 true,否则返回 false |
| size() |
size_type size() const; |
返回 list 中有效节点的个数 |
2.4 插入删除函数
| 函数 |
函数声明 |
接口说明 |
| push_front() |
void push_front(const value_type& val); void push_front(value_type&& val); |
在 list 首元素前插入值为 val 的元素 |
| pop_front() |
void pop_front(); |
删除 list 中第一个元素 |
| push_back() |
void push_back(const value_type& val); void push_back(value_type&& val); |
在 list 尾部插入值为 val 的元素 |
| pop_back() |
void pop_back(); |
删除 list 中最后一个元素 |
| insert() |
iterator insert(const_iterator pos, const value_type& val); |
在 list position 位置中插入值为 val 的元素 ,返回指向第一个新插入元素的迭代器 |
| erase() |
iterator erase(const_iterator pos); iterator erase(const_iterator first, const_iterator last); |
删除 list position 位置的元素,返回被删除元素之后的下一个元素的迭代器 |
| swap() |
void swap(list& x); |
交换两个 list 中的元素 |
| clear() |
void clear(); |
清空 list 中的有效元素 |
三、迭代器
| 函数 |
返回值 |
说明 |
begin() |
迭代器 |
指向首元素 |
end() |
迭代器 |
指向尾元素之后 |
rbegin() |
反向迭代器 |
指向尾元素 |
rend() |
反向迭代器 |
指向首元素之前 |
cbegin() |
常量迭代器 |
指向首元素 (C++11) |
cend() |
常量迭代器 |
指向尾元素之后 (C++11) |
crbegin() |
常量反向迭代器 |
指向尾元素 (C++11) |
crend() |
常量反向迭代器 |
指向首元素之前 (C++11) |
迭代器使用示例
cpp
复制代码
std::list<int> lst = {1, 2, 3, 4, 5};
// ─────────────────────────────────────
// 1. 正向迭代器 (begin/end)
// ─────────────────────────────────────
for (auto it = lst.begin(); it != lst.end(); ++it) {
std::cout << *it << " "; // 输出: 1 2 3 4 5
}
// ─────────────────────────────────────
// 2. 反向迭代器 (rbegin/rend)
// ─────────────────────────────────────
for (auto rit = lst.rbegin(); rit != lst.rend(); ++rit) {
std::cout << *rit << " "; // 输出: 5 4 3 2 1
}
// ─────────────────────────────────────
// 3. 常量迭代器 (cbegin/cend) - 只读
// ─────────────────────────────────────
for (auto it = lst.cbegin(); it != lst.cend(); ++it) {
// *it = 10; // ❌ 错误:不能修改
std::cout << *it << " "; // 输出: 1 2 3 4 5
}
// ─────────────────────────────────────
// 4. 常量反向迭代器 (crbegin/crend) - 只读
// ─────────────────────────────────────
for (auto rit = lst.crbegin(); rit != lst.crend(); ++rit) {
// *rit = 10; // ❌ 错误:不能修改
std::cout << *rit << " "; // 输出: 5 4 3 2 1
}
// ─────────────────────────────────────
// 5. 范围 for 循环 (C++11) - 推荐
// ─────────────────────────────────────
for (const auto& elem : lst) {
std::cout << elem << " "; // 输出: 1 2 3 4 5
}
vector 与 list 对比表
| 对比项 |
vector |
list |
| 底层结构 |
动态顺序表,一段连续空间 |
带头结点的双向循环链表 |
| 随机访问 |
支持随机访问,访问某个元素效率 O(1) |
不支持随机访问,访问某个元素效率 O(N) |
| 插入和删除 |
任意位置插入和删除效率低,需要搬移元素,时间复杂度为 O(N) 插入时有可能需要增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低 |
任意位置插入和删除效率高,不需要搬移元素,时间复杂度为 O(1) |
| 空间利用率 |
底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 |
底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
| 迭代器 |
原生态指针 |
对原生态指针 (节点指针) 进行封装 |
| 迭代器失效 |
插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效 删除时,当前迭代器需要重新赋值否则会失效 |
插入元素不会导致迭代器失效 删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
| 使用场景 |
需要高效存储,支持随机访问,不关心插入删除效率 |
大量插入和删除操作,不关心随机访问 |