List介绍:
list 是 C++ STL 中基于双向循环链表实现的容器,与 vector(动态数组)形成鲜明互补。前者擅长频繁的插入 / 删除操作,后者优势在随机访问。
本文将全面讲解 list 的核心特性、常用用法,通过维度化对比梳理 list 与 vector 的差异,并结合实战场景给出容器选型建议,帮你精准掌握 list 的使用场景和避坑要点。
一、list 核心认知:双向链表的本质
1. list 的底层特性
list 的底层是双向循环链表(每个节点包含数据、前驱指针、后继指针),无连续内存依赖,核心特性如下:
- 不支持随机访问(无法用
[]/at()访问元素),只能通过迭代器遍历,访问任意元素时间复杂度 O (n); - 任意位置插入 / 删除元素效率极高(仅需修改指针指向),时间复杂度 O (1)(前提是已找到目标位置);
- 无扩容概念:每个元素独立分配内存,插入时直接新建节点,删除时释放节点,无需整体扩容 / 拷贝;
- 迭代器稳定性:仅被删除节点的迭代器失效,其他迭代器不受影响(与 vector 形成核心差异)。
2. list 基础用法示例
cpp
#include <iostream>
#include <list>
#include <algorithm> // 用于find
using namespace std;
int main() {
// 1. 初始化
list<int> lst1; // 空链表
list<int> lst2(5, 10); // 5个元素,每个值为10
list<int> lst3 = {1,2,3,4,5}; // 列表初始化
// 2. 增删改查
lst1.push_back(6); // 尾部插入,size=1
lst1.push_front(0); // 头部插入,size=2 → {0,6}
lst2.pop_back(); // 尾部删除,size=4
lst3.pop_front(); // 头部删除,size=4 → {2,3,4,5}
// 查找元素(需遍历,无随机访问)
auto it = find(lst3.begin(), lst3.end(), 3);
if (it != lst3.end()) {
lst3.insert(it, 9); // 在3前插入9 → {2,9,3,4,5}
lst3.erase(it); // 删除原3的位置(此时it指向3)→ {2,9,4,5}
}
// 遍历(仅支持迭代器/范围for)
for (int num : lst3) cout << num << " "; // 输出:2 9 4 5
cout << endl;
// 3. 专属方法
lst3.reverse(); // 反转链表 → {5,4,9,2}
lst3.sort(); // 排序 → {2,4,5,9}
lst1.merge(lst2); // 合并两个已排序的链表(需先排序)
return 0;
}
3. list 的专属核心方法(区别于 vector)
list 提供了链表特有的操作方法,是其适配场景的关键:
| 方法 | 作用 |
|---|---|
push_front() |
头部插入元素(vector 无此高效方法,头部插入需移动所有元素) |
pop_front() |
头部删除元素(同理,vector 头部删除效率低) |
reverse() |
反转链表(时间复杂度 O (n),无需额外内存) |
sort() |
链表专属排序(STL 的 sort 算法不支持 list,因需随机访问迭代器) |
merge() |
合并两个已排序的 list(原地合并,无额外内存分配) |
splice() |
转移另一个 list 的元素到当前链表(仅修改指针,效率极高) |
二、list 与 vector 核心维度对比
| 对比维度 | list(双向循环链表) | vector(动态数组) |
|---|---|---|
| 底层存储 | 非连续内存,每个节点独立分配(数据 + 前驱 + 后继指针) | 连续内存(数组),物理地址连续 |
| 随机访问 | 不支持(无[]/at()),只能迭代器遍历,访问任意元素 O (n) |
支持([]/at()),随机访问时间复杂度 O (1) |
| 插入 / 删除性能 | 任意位置 O (1)(找到位置后仅改指针),头部 / 尾部插入删除效率极高 | 尾部 O (1)(平均),中间 / 头部 O (n)(需移动元素),触发扩容时额外增加拷贝开销 |
| 扩容机制 | 无扩容概念,插入元素直接新建节点,无内存拷贝 | 容量不足时扩容(通常 2 倍),需申请新内存→拷贝数据→释放旧内存 |
| 迭代器类型 | 双向迭代器(仅支持 ++/--,不支持 +- 整数偏移) | 随机访问迭代器(支持 ++/--/+- 整数偏移,如it+3) |
| 迭代器失效 | 仅被删除节点的迭代器失效,其他迭代器完全有效 | 扩容后所有迭代器失效;插入 / 删除后,失效位置后的迭代器全部失效 |
| 内存开销 | 额外内存开销大(每个节点需存储两个指针) | 内存开销小(仅存储数据,连续内存缓存友好) |
| 空间利用率 | 无内存浪费(按需分配节点),但碎片化严重 | 可能存在内存浪费(扩容后 capacity>size),但连续内存利用率高 |
| 专属方法 | push_front/pop_front/reverse/sort/merge/splice | reserve/resize/shrink_to_fit(内存预分配 / 调整) |
三、迭代器失效场景对比(易踩坑点)
迭代器失效是 list 和 vector 最核心的差异之一,也是新手最易出错的地方,单独梳理如下:
1. list 的迭代器失效场景(仅 1 种)
只有指向被删除节点的迭代器会失效,其余迭代器(包括前驱、后继)均不受影响:
cpp
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> lst = {1,2,3,4,5};
auto it1 = lst.begin(); // 指向1
auto it2 = lst.begin() + 1; // 错误:list迭代器不支持+偏移
auto it2 = next(lst.begin(), 1); // 正确:指向2
auto it3 = lst.end(); // 指向末尾
lst.erase(it2); // 删除2,仅it2失效
cout << *it1 << endl; // 正确:1(it1有效)
// cout << *it2 << endl; // 错误:it2失效
cout << *next(it1, 1) << endl; // 正确:3(迭代器有效)
return 0;
}
2. vector 的迭代器失效场景(多且易踩)
对比之下,vector 的迭代器失效场景远多于 list:
- 扩容(push_back/insert):所有迭代器失效;
- 中间 / 头部插入:插入位置后的迭代器失效;
- 中间 / 头部删除:删除位置后的迭代器失效;
- 清空 / 析构:所有迭代器失效
四、实战场景选型:什么时候用 list?什么时候用 vector?
容器选型的核心是匹配场景的性能需求,以下是精准的选型指南:
优先选择 list 的场景
- 频繁在任意位置插入 / 删除元素:如高频的增删操作(如任务队列、实时数据更新),尤其是头部 / 中间操作;
- 需要迭代器高度稳定:插入 / 删除后,除被操作节点外,其他迭代器仍可正常使用;
- 元素数量不确定且无需随机访问:如仅需遍历、增删,无需按索引快速取值。
优先选择 vector 的场景
- 需要随机访问元素 :如按索引快速取值(
vec[5])、二分查找(需随机访问迭代器); - 遍历性能要求高:连续内存的缓存友好性,遍历速度远快于 list;
- 元素数量可预估:可通过 reserve 预分配容量,避免扩容开销;
- 内存开销敏感:vector 的内存额外开销远低于 list(无指针存储)。
五、List总结
- list 基于双向循环链表实现,核心优势是任意位置插入 / 删除效率 O (1)、迭代器稳定性高,核心劣势是无随机访问、内存开销大;
- vector 基于连续数组实现,核心优势是随机访问 O (1)、缓存友好,核心劣势是中间 / 头部增删效率低、迭代器易失效;
- 选型核心原则:需随机访问 / 遍历优先选 vector,需频繁任意位置增删优先选 list;
- list 使用需避坑:不支持 std::sort、注意内存碎片化、优先用 emplace 系列方法。
六、Lsit的模拟实现
1.基础链表节点:
cpp
//链表节点
template<class T>
struct list_node
{
T _data;
list_node<T>* _next;
list_node<T>* _prev;
list_node(const T& data=T())
:_data(data)
,_next(nullptr)
,_prev(nullptr)
{ }
};
2.Lsit的迭代器:
cpp
//迭代器
template<class T,class ref,class ptr>
struct list_iterator
{
typedef list_node<T> Node;
typedef list_iterator<T, ref, ptr> Self;
Node* _node;
list_iterator(Node* node)
:_node(node)
{}
ref operator*()
{
return _node->_data;
}
Self& operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
Self& operator--(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
Self& operator++()
{
_node=_node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
bool operator==(const Self& s)const
{
return _node == s._node;
}
bool operator!=(const Self& s)const
{
return _node != s._node;
}
};
3.List的基本框架:
cpp
//链表
template<class T>
class list
{
typedef list_node<T> Node;//表节点
public:
//普通迭代器/const迭代器
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T,const T&,const T*> const_iterator;
//构造
list()
{
empty_init();
}
//空初始化
void empty_init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
//析构
~list()
{
clear();
delete _head;
_head = nullptr;
}
//拷贝构造
list(const list<T>& x)
{
empty_init();
for (auto& e : x)
{
push_back(e);
}
}
//清理数据
void clear()
{
auto it = begin();
while (it != end())
{
it = erase(it);
}
}
//判空
void empty()
{
return _size == 0;
}
//插入
void insert(iterator pos, const T& x)
{
Node* newnode= new Node(x);
Node* cur = pos._node;
Node* prev = cur->_prev;
newnode->_next = cur;
cur->_prev = newnode;
newnode->_prev = prev;
prev->_next = newnode;
++_size;
}
//删除pos位置节点
iterator erase(iterator pos)
{
assert(pos!=end());
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
prev->_next = next;
next->_prev = prev;
delete[] pos._node;
--_size;
return next;
}
void swap(const list<T>& x)
{
std::swap(_head, x._head);
std::swap(_size, x._size);
}
//赋值
list<T>& operator=(const list<T> x)
{
swap(x);
return *this;
}
//尾插
void push_back(const T& x)
{
insert(end(), x);
}
//头插
void push_front(const T& x)
{
insert(begin(), x);
}
//头删
void pop_front(const T& x)
{
erase(begin());
}
//尾删
void pop_back(const T& x)
{
erase(--end());
}
//迭代器相关
iterator begin()
{
return _head->_next;
}
iterator end()
{
return _head;
}
const_iterator begin()const
{
return _head->_next;
}
const_iterator end()const
{
return _head;
}
//大小
size_t size()
{
return _size;
}
private:
Node* _head;
size_t _size;
};