C++--list


一、list 核心概念

1.1 本质

list带头结点的双向循环链表 ,底层节点动态开辟,非连续内存空间 。迭代器是对节点指针的封装(不是原生指针)。

1.2 与 vector 的本质区别

vector list
底层结构 连续空间(动态数组) 带头结点的双向循环链表
迭代器

六、list vs vector 完整对比

vector list
底层结构 动态数组,连续空间 带头结点双向循环链表
随机访问 O(1) O(n)
头插/头删 O(n),需搬移所有元素 O(1)
任意位置插入/删除 O(n) O(1)
增容代价 开辟新空间+拷贝+释放旧空间 无增容,每次插入只申请一个节点
空间利用率 ,无额外指针开销 低,每个节点额外存两个指针
缓存利用率 ,连续内存预读友好 低,节点分散在堆中
内存碎片 不易产生内存碎片 小节点频繁申请释放,易产生碎片
迭代器类型 随机访问迭代器 双向迭代器
迭代器底层 原生指针 T* 封装的节点指针
插入时迭代器失效 会(扩容导致) 不会
删除时迭代器失效 当前及之后全部失效 仅被删节点失效
operator[] 支持 不支持(无法随机访问)
reserve/capacity 没有
适用场景 高效存储 + 随机访问,少插入删除 大量插入删除,不关心随机访问

七、复习自测问答

Q1:list 的底层结构是什么?

答: 带头结点的双向循环链表

Q2:list 的迭代器是原生指针吗?

答: 不是。list 的迭代器是对节点指针的封装 (必须支持 ++-- 等操作跳转到相邻节点,原生指针做不到),而 vector 的迭代器才是原生指针 T*

Q3:list 插入元素会导致迭代器失效吗?

答: 不会。因为链表插入只改指针指向,不搬移已有节点,也不扩容。

Q4:list 删除元素会导致哪些迭代器失效?

答: 只有被删除节点的迭代器失效,其他节点的迭代器不受影响。

Q5:list 有 reservecapacity 吗?为什么?

答: 没有。list 是非连续空间,每个节点独立申请内存,不需要预留空间,也没有"容量"的概念。

Q6:list 支持 operator[] 随机访问吗?

答: 不支持。底层是链表,访问第 n 个元素需要 O(n) 遍历。只能用迭代器或 range-for。

Q7:list 的 end() 指向哪里?

答: 指向头结点 (哨兵节点),不是最后一个有效元素。front() 返回头结点下一个节点的值,back() 返回头结点上一个节点的值。

Q8:反向迭代器如何实现?

答: 内部包含一个正向迭代器。反向 ++ 调用正向 --,反向 -- 调用正向 ++operator* 返回前一个位置的值,保证区间对称。

Q9:什么时候用 list,什么时候用 vector?

答:

  • list :大量任意位置插入/删除,不关心随机访问
  • vector :需要随机访问、高效存储、关心缓存效率,插入删除较少
  • 实际中 vector 的使用频率远高于 list(CPU 缓存优势)

Q10:push_back 在 list 和 vector 中的复杂度分别是多少?

答: list 是 O(1) (只改两个指针)。vector 是均摊 O(1)(可能触发扩容,单次最坏 O(n))。

复习建议:重点掌握 list 与 vector 的底层区别、迭代器失效规则差异、以及各自的适用场景。第三、六章是面试高频考点。

生指针 T* | 对节点指针的封装 |

| 随机访问 | O(1) | O(n) |

| 任意位置插入删除 | O(n),需搬移元素 | O(1),只改指针 |


二、核心接口速查

2.1 构造函数

构造函数 说明
list<T> l; 空的 list
list<T> l(n, val); n 个 val
list<T> l(first, last); 迭代器区间构造
list<T> l(const list& x); 拷贝构造

2.2 迭代器

接口 说明
begin() / end() 正向,++ 向后移动
rbegin() / rend() 反向,++ 向前移动(rbeginend 位置,rendbegin 位置)

2.3 容量与元素访问

接口 说明
empty() 判空
size() 有效节点个数
front() 第一个元素值的引用
back() 最后一个元素值的引用

2.4 增删改

接口 说明 复杂度
push_front(val) 头插 O(1)
pop_front() 头删 O(1)
push_back(val) 尾插 O(1)
pop_back() 尾删 O(1)
insert(pos, val) 在 pos 前插入 O(1)
erase(pos) 删除 pos 位置节点 O(1)
swap(l1, l2) 交换两个 list O(1)
clear() 清空所有有效元素 O(n)

注意:find 不是 list 的成员函数,是 <algorithm> 中的算法。


三、list 迭代器失效(重点!与 vector 不同)

3.1 核心规则

操作 迭代器是否失效
插入(insert) 不会失效(链表不搬移节点,只改指针)
删除(erase) 只有被删除节点的迭代器失效,其他迭代器不受影响
resize / reserve list 没有这两个接口(非连续空间,无 capacity 概念)

3.2 删除时的安全写法

错误写法

cpp 复制代码
auto it = l.begin();
while (it != l.end()) {
    l.erase(it);   // it 指向的节点被删除,it 失效
    ++it;           // 失效的 it 再 ++ 崩溃
}

正确写法一(后置 ++):

cpp 复制代码
auto it = l.begin();
while (it != l.end()) {
    l.erase(it++);  // it++ 先返回旧值,再指向下一个,erase 删除旧值
}

正确写法二(用返回值):

cpp 复制代码
auto it = l.begin();
while (it != l.end()) {
    it = l.erase(it);  // C++11 起 erase 返回下一个有效迭代器
}

3.3 list vs vector 迭代器失效对比(面试高频)

场景 vector list
插入元素 可能全部失效(扩容导致空间重分配) 永不失效
删除元素 当前及之后全部失效(元素前移) 仅被删节点失效
失效原因 底层连续空间被释放或元素搬移 节点被删除,指针变野指针

四、反向迭代器实现原理

4.1 核心思想

反向迭代器内部包含一个正向迭代器,通过对正向迭代器进行包装实现:

  • 反向迭代器的 ++ = 正向迭代器的 --
  • 反向迭代器的 -- = 正向迭代器的 ++

4.2 解引用的对称设计

cpp 复制代码
// operator*:返回的是 _it 前一个位置的值
Ref operator*() {
    Iterator temp(_it);
    --temp;
    return *temp;
}

原因rbegin() 内部存的是正向 end(),需要 -- 后才指向最后一个有效元素。这是为了保证开始位置和结束位置对称 ,使得 [rbegin(), rend()) 的区间完整覆盖所有元素。

4.3 typename 关键字的作用

cpp 复制代码
typedef typename Iterator::Ref Ref;

告诉编译器Iterator::Ref 是一个类型而不是静态成员变量,否则编译器无法区分。


五、list 模拟实现要点

  1. 带头结点的双向循环链表 :头结点不存数据,end() 指向头结点
  2. 节点结构 :包含 _prev_next 指针和 _data
  3. insert:在指定位置前插入新节点,只改 4 个指针
  4. erase:删除指定节点,只改 2 个指针,返回下一节点
  5. 迭代器类 :需重载 operator*operator->operator++operator--operator==operator!=

相关推荐
killerbasd1 小时前
总结 7。10
人工智能·算法·机器学习
林间码客1 小时前
RAG系统评估指南:从入门到实践
人工智能·算法·机器学习
qq_416409131 小时前
性价比高的openclaw选哪家
c++
凌波粒1 小时前
LeetCode--47.全排列 II(回溯算法)
算法·leetcode·职场和发展
学究天人1 小时前
数学公理体系大全:Comprehensive Collection of Mathematical Axiom Systems(卷8)
线性代数·算法·机器学习·数学建模·动态规划·图论·抽象代数
ctrl_v助手2 小时前
HALCON 学习笔记1
笔记·数码相机·学习·算法·计算机视觉
汉克老师2 小时前
GESP2026年6月认证C++八级( 第三部分编程题(2、堆石子))精讲
c++·排列组合·快速幂·费马小定理·gesp8级·逆元·组合数计算
信竞星球_少儿编程题库2 小时前
2026年全国青少年信息素养大赛算法应用主题赛(星火征途C++小学组复赛真题)
c++
Edward111111112 小时前
一AI名词
java·开发语言·数据库·学习