本文将从list的基础介绍、常用接口使用、迭代器失效问题、模拟实现带你彻底吃透list容器。
一、list 容器基础介绍
1.1 底层结构
list的底层是带头结点的双向循环链表,这是它所有特性的根源:
- 每个节点包含数据域 + 前驱指针 + 后继指针
- 链表首尾相连,形成环状结构
- 不占用连续内存空间,节点动态开辟与释放
1.2 核心迭代器边界
begin():指向第一个有效数据节点end():指向头结点(最后一个元素的下一个位置)- 支持正向迭代器 与反向迭代器,遍历更灵活
二、list 常用接口使用详解
2.1 构造函数
list提供 4 种常用构造方式,覆盖空对象、拷贝、区间、指定值初始化:
| 构造函数 | 功能说明 |
|---|---|
list() |
构造空 list |
list(size_type n, const value_type& val = value_type()) |
构造含 n 个值为 val 的 list |
list(const list& x) |
拷贝构造 |
list(InputIterator first, InputIterator last) |
用 [first,last) 区间元素构造 |
2.2 迭代器使用
迭代器可暂时理解为指向节点的指针,是访问 list 的核心工具:
- 正向迭代器
begin():首元素迭代器end():尾元素下一个位置迭代器++:向后移动迭代器
- 反向迭代器
rbegin():等价于end(),反向起始rend():等价于begin(),反向结束++:向前移动迭代器
2.3 容量操作
empty():判断 list 是否为空,空返回 truesize():返回有效节点个数
2.4 元素访问
list不支持随机访问 (无[]与at),只能访问首尾元素:
front():返回第一个元素的引用back():返回最后一个元素的引用
2.5 增删改查
这是list最高效的操作,时间复杂度 O (1):
| 函数 | 功能 |
|---|---|
push_front(val) |
头插 |
pop_front() |
头删 |
push_back(val) |
尾插 |
pop_back() |
尾删 |
insert(pos, val) |
pos 位置插入 val |
erase(pos) |
删除 pos 位置元素 |
swap(list) |
交换两个 list |
clear() |
清空有效元素 |
三、list 迭代器失效问题
迭代器失效本质:迭代器指向的节点被销毁,变成野指针 。list迭代器失效规则:
- 插入操作:不会导致任何迭代器失效
- 删除操作 :仅失效被删除节点的迭代器,其他迭代器不受影响
错误示例
cpp
void TestListIterator1() {
int array[] = {1,2,3,4,5,6,7,8,9,0};
list<int> l(array, array+sizeof(array)/sizeof(array[0]));
auto it = l.begin();
while (it != l.end()) {
l.erase(it); // 删除后it失效
++it; // 对失效迭代器++,未定义行为
}
}
正确写法
cpp
void TestListIterator() {
int array[] = {1,2,3,4,5,6,7,8,9,0};
list<int> l(array, array+sizeof(array)/sizeof(array[0]));
auto it = l.begin();
while (it != l.end()) {
// 后置++:先传旧迭代器删除,再让it指向下一节点
l.erase(it++);
// 或 it = l.erase(it);
}
}
四、list 模拟实现
1. 节点结构
cpp
template<class T>
struct list_node {
list_node* _next;
list_node* _prev;
T _data;
list_node(const T& val = T())
:_next(nullptr)
,_prev(nullptr)
,_data(val)
{}
};
2. 迭代器(重点:三个模板参数)
cpp
template<class T, class Ref, class Ptr>
struct list_iterator {
typedef list_node<T> Node;
typedef list_iterator self;
Node* _node;
list_iterator(Node* node) :_node(node) {}
self& operator++() {
_node = _node->_next;
return *this;
}
self& operator--() {
_node = _node->_prev;
return *this;
}
Ref operator*() {
return _node->_data;
}
Ptr operator->() {
return &_node->_data;
}
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
{
public:
typedef list_node<T> Node;
// 普通迭代器
typedef list_iterator<T, T&, T*> iterator;
// const迭代器
typedef list_iterator<T, const T&, const T*> const_iterator;
list()
{
empty_init();
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
const_iterator cbegin() const
{
return const_iterator(_head->_next);
}
const_iterator cend() const
{
return const_iterator(_head);
}
size_t size() const
{
return _size;
}
bool empty() const
{
return _size == 0;
}
T& front()
{
return *begin();
}
const T& front() const
{
return *cbegin();
}
T& back()
{
return *(--end());
}
const T& back() const
{
return *(--cend());
}
void push_back(const T& x)
{
Node* newnode = new Node(x);
Node* tail = _head->_prev;
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
_size++;
}
void pop_back()
{
erase(--end());
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_front()
{
erase(begin());
}
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
_size++;
return iterator(newnode);
}
iterator erase(iterator pos)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
_size--;
return iterator(next);
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
private:
// 空链表初始化
void empty_init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
private:
Node* _head;
size_t _size;
};
五、list 与 vector 核心对比
两者同为序列容器,底层结构决定性能差异
| 维度 | vector | list |
|---|---|---|
| 底层结构 | 动态顺序表(连续空间) | 带头双向循环链表 |
| 随机访问 | 支持,O (1) | 不支持,O (N) |
| 插入删除 | 任意位置效率低,O (N),需搬移 + 增容 | 任意位置效率高,O (1),无需搬移 |
| 空间利用率 | 连续内存,碎片少,缓存命中率高 | 节点动态开辟,碎片多,缓存低 |
| 迭代器 | 原生态指针 | 封装节点指针 |
| 迭代器失效 | 插入可能全失效;删除当前失效 | 插入不失效;删除仅当前失效 |
| 适用场景 | 需随机访问、少插入删除 | 大量插入删除、不关心随机访问 |
六、总结
list是双向循环链表,插入删除效率极高,不支持随机访问- 迭代器
++分方向:正向向后,反向向前 - 迭代器仅在删除当前节点时失效,插入不会失效
- 频繁插入删除选
list,需要随机访问选vector