C++ std::list 深度详解|接口、迭代器失效、模拟实现、与 vector 对比
本文完整整理 STL list 全套知识点,底层带头双向循环链表,常用接口、迭代器失效重点、手写模拟实现、反向迭代器原理、和 vector 对比,面试高频考点,可直接发布博客。

std::list 底层:带头哨兵节点的双向循环链表 。每个节点独立堆开辟,节点存
_prev前驱指针、_next后继指针、数据。end()返回哨兵头结点迭代器,begin()返回第一个有效节点。

一、list 常用接口
1.1 构造函数
表格
| 构造函数 | 功能说明 |
|---|---|
list() |
构造空 list |
list(size_type n, const value_type& val) |
构造 n 个 val 元素 |
list(const list& x) |
拷贝构造,深拷贝 |
list(InputIterator first, InputIterator last) |
迭代器区间[first,last)构造 |
示例代码
cpp
#include <list>
#include <iostream>
using namespace std;
void TestListConstruct()
{
list<int> l1;
list<int> l2(5,10);
list<int> l3(l2);
int arr[] = {1,2,3,4};
list<int> l4(arr, arr+sizeof(arr)/sizeof(int));
}
1.2 迭代器接口
表格
| 接口 | 说明 |
|---|---|
begin() / end() |
正向迭代器;begin()第一个有效节点;end()哨兵头结点 |
rbegin() / rend() |
反向迭代器;rbegin()等价end();rend()等价begin() |
注意:
- 正向迭代器
++向后走(向链表尾部);- 反向迭代器
++向前走(向链表头部)。
cpp
void TestListIterator()
{
list<int> l = {1,2,3,4,5};
//正向遍历
list<int>::iterator it = l.begin();
while(it != l.end())
{
cout << *it << " ";
++it;
}
//反向遍历
list<int>::reverse_iterator rit = l.rbegin();
while(rit != l.rend())
{
cout << *rit << " ";
++rit;
}
}
1.3 容量与元素访问
表格
| 接口 | 说明 |
|---|---|
size() |
有效节点个数 |
empty() |
判空 |
front() |
返回第一个元素引用 |
back() |
返回最后一个元素引用 |
❗list不支持随机访问,没有
operator[]下标运算符 !不能写l[0],编译报错。
1.4 修改接口 modifiers
表格
| 接口 | 功能 |
|---|---|
push_front(val) |
头插元素 |
pop_front() |
头删 |
push_back(val) |
尾插 |
pop_back() |
尾删 |
insert(pos,val) |
在 pos 迭代器之前 插入元素;插入不会让任何迭代器失效 |
erase(pos) |
删除 pos 位置节点;仅被删除的迭代器失效,其他全部有效;返回下一个有效迭代器 |
swap(l2) |
交换两个 list,只交换哨兵头结点指针,O (1) |
clear() |
清空全部有效节点;所有迭代器失效 |
cpp
void TestListMod()
{
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_front(0);
auto pos = l.begin();
l.insert(pos, 100); //pos迭代器仍然有效,insert不会失效迭代器
pos = l.erase(pos); //erase后原来pos失效,要用返回值接收新迭代器
}
二、list 迭代器失效(面试超级重点)
vector:连续数组,扩容全部迭代器失效;erase 之后当前及后面迭代器失效。 list 底层双向链表,节点独立堆开辟,插入只修改指针,不移动、不释放原有节点内存。
✅list 迭代器失效规则:
- insert、push_back、push_front:插入操作,不会造成任何迭代器失效!
- erase:只有被删除节点对应的迭代器失效,其他所有迭代器、引用、指针依旧有效。
clear():销毁全部节点,所有迭代器全部失效。
❌错误遍历删除代码
cpp
void TestBadErase()
{
int array[] = {1,2,3,4,5,6};
list<int> l(array, array+sizeof(array)/sizeof(array[0]));
auto it = l.begin();
while(it != l.end())
{
l.erase(it); // erase后it已经失效!
++it; // 对失效迭代器++,未定义行为,崩溃
}
}
✅两种正确写法
cpp
//写法1:接收erase返回值(推荐)
void TestGoodErase1()
{
int array[] = {1,2,3,4,5,6};
list<int> l(array, array+sizeof(array[0]));
auto it = l.begin();
while(it != l.end())
{
it = l.erase(it); //接收erase返回的下一个有效迭代器
}
}
//写法2:后置++,it++先拷贝旧值再自增
void TestGoodErase2()
{
int array[] = {1,2,3,4,5,6};
list<int> l(array, array+sizeof(array[0]));
auto it = l.begin();
while(it != l.end())
{
l.erase(it++);
}
}
面试问答:为什么 list 插入不会迭代器失效? list 每个节点独立在堆上,插入只是修改相邻节点的 prev、next 指针,原有节点内存地址完全不变,指向旧节点的迭代器依旧有效。
三、list 模拟实现完整代码 + 逐段剖析
底层三部分:
list_node<T>链表节点结构体;list_iterator<T>迭代器类,封装节点指针,重载* -> ++ -- != ==;list<T>容器类,保存哨兵头节点_head;- 反向迭代器适配器
ReverseIterator。
cpp
#include <cassert>
#include <algorithm>
namespace bit
{
//1.链表节点
template<class T>
struct list_node
{
list_node<T>* _prev;
list_node<T>* _next;
T _data;
list_node(const T& x = T())
:_prev(nullptr)
,_next(nullptr)
,_data(x)
{}
};
//2.list迭代器封装节点指针
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;
}
//->运算符
Ptr operator->()
{
return &_node->_data;
}
//前置++
Self& operator++()
{
_node = _node->_next;
return *this;
}
//后置++
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
//前置--
Self& operator--()
{
_node = _node->_prev;
return *this;
}
//后置--
Self operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const Self& it) const
{
return _node != it._node;
}
bool operator==(const Self& it) const
{
return _node == it._node;
}
};
// =========反向迭代器适配器模板(重点typename考点)=========
template<class Iterator>
class ReverseListIterator
{
public:
// typename告诉编译器 Iterator::Ref 是一个类型,不是静态成员变量
typedef typename Iterator::Ref Ref;
typedef typename Iterator::Ptr Ptr;
typedef ReverseListIterator<Iterator> Self;
Iterator _it;
public:
ReverseListIterator(Iterator it)
:_it(it)
{}
Ref operator*()
{
Iterator temp(_it);
--temp;
return *temp;
}
Ptr operator->()
{
return &(operator*());
}
//反向迭代器++等价正向迭代器--
Self& operator++()
{
--_it;
return *this;
}
Self operator++(int)
{
Self tmp(*this);
--_it;
return tmp;
}
//反向迭代器--等价正向迭代器++
Self& operator--()
{
++_it;
return *this;
}
Self operator--(int)
{
Self tmp(*this);
++_it;
return tmp;
}
bool operator!=(const Self& l) const
{
return _it != l._it;
}
bool operator==(const Self& l) const
{
return _it == l._it;
}
};
// =========list容器本体=========
template<class T>
class list
{
public:
typedef list_node<T> Node;
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
typedef ReverseListIterator<iterator> reverse_iterator;
typedef ReverseListIterator<const_iterator> const_reverse_iterator;
//构造:创建哨兵头结点,双向自环
list()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
//迭代器区间构造
template<class InputIterator>
list(InputIterator first, InputIterator last)
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
while(first != last)
{
push_back(*first);
++first;
}
}
//拷贝构造
list(const list<T>& l)
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
auto it = l.begin();
while(it != l.end())
{
push_back(*it);
++it;
}
}
//现代swap拷贝赋值
list<T>& operator=(list<T> l)
{
swap(l);
return *this;
}
//析构,释放全部节点,释放哨兵头结点
~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);
}
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
//size
size_t size() const
{
size_t cnt = 0;
auto it = begin();
while(it != end())
{
++cnt;
++it;
}
return cnt;
}
bool empty() const
{
return begin() == end();
}
//尾插
void push_back(const T& val)
{
insert(end(), val);
}
//头插
void push_front(const T& val)
{
insert(begin(), val);
}
// insert 在pos迭代器之前插入val,list核心函数
iterator insert(iterator pos, const T& val)
{
Node* newNode = new Node(val);
Node* cur = pos._node;
Node* prev = cur->_prev;
prev->_next = newNode;
newNode->_prev = prev;
newNode->_next = cur;
cur->_prev = newNode;
return iterator(newNode);
}
// erase删除pos节点,返回下一个迭代器
iterator erase(iterator pos)
{
assert(pos != end());
Node* del = pos._node;
Node* prev = del->_prev;
Node* next = del->_next;
prev->_next = next;
next->_prev = prev;
delete del;
return iterator(next);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
void clear()
{
auto it = begin();
while(it != end())
{
it = erase(it);
}
}
void swap(list<T>& l)
{
std::swap(_head, l._head);
}
T& front()
{
return *begin();
}
T& back()
{
return *(--end());
}
private:
Node* _head; //双向循环链表哨兵头结点
};
}
模拟实现高频面试考点剖析
- 迭代器为什么是类,不是原生指针? list 节点内存离散,不能直接用裸指针做迭代器,封装一层类,重载
*、->、++、--、==、!=,对外提供和指针一样的行为。 typename Iterator::Ref的typename干什么? 模板里面编译器不知道Iterator::Ref是类型名 还是静态成员变量,typename强制告诉编译器这是一个类型,否则编译报错。反向迭代器必考。- list 的
insert为什么迭代器不会失效? 只修改节点内部 prev/next 指针,原有节点堆内存地址完全不变。 - list 没有
operator[]? 底层链表,随机访问需要遍历,O (N),标准直接不提供下标。想要访问第 n 个元素只能迭代器循环走 n 步。 - list 的
size()模拟实现版本需要遍历统计;STL 标准 list 内部存了_size 成员变量 O (1) 取 size。
四、list vs vector 完整对比表(面试必背)
表格
| 对比项 | vector | list |
|---|---|---|
| 底层结构 | 动态顺序表,一段连续堆内存 | 带头结点双向循环链表,节点离散堆分配 |
| 随机访问 | ✅支持operator[] O(1) |
❌不支持下标访问,访问元素 O (N) |
| 任意位置插入删除 | 效率 O (N),需要搬移元素;插入可能扩容,代价更大 | 效率 O (1),仅修改指针,不需要移动元素 |
| 空间、缓存 | 连续内存,缓存命中率高;无内存碎片 | 节点分散开辟,容易产生内存碎片,缓存命中率差 |
| 迭代器本质 | 原生 T * 指针 | 封装节点指针的类迭代器 |
| 迭代器失效规则 | 扩容:全部迭代器失效;erase:当前及后面迭代器失效 | insert 完全不失效;erase 仅被删除迭代器失效,其余全部有效;clear 全部失效 |
| 适用场景 | 频繁随机访问;尾插尾删,中间插入删除少 | 大量任意位置插入删除;不关心随机访问 |
总结选型:
- 需要下标随机访问,选
vector;- 频繁在中间位置插入删除,不需要随机访问,选
list。
五、面试问答汇总
Q1:list 的 erase 之后,其他迭代器还能用吗?
A:除了传入 erase 的那个迭代器失效,其他所有迭代器、引用、指针都可以正常使用。erase 会返回下一个有效迭代器。
Q2:list insert 会不会迭代器失效?A:不会,插入节点只会修改指针,原有节点地址不变,全部旧迭代器保持有效。
Q3:list 为什么没有 operator \[\]?A:底层链表,要访问第 n 个元素必须从头遍历,时间复杂度 O (N),提供下标没有意义,STL 不实现。
Q4:反向迭代器原理?A:适配器模式,内部保存正向迭代器;反向迭代器
++等价正向--;反向--等价正向++;解引用的时候先把正向迭代器减一再取值。
Q5:list swap 效率?A:O (1),只交换哨兵头结点的指针,完全不拷贝节点数据。
Q6:list 和 vector 哪个删除中间元素快?A:list O (1);vector 需要挪动后面所有元素 O (N)。