C++ STL 之 list 与 forward_list 详解:用法→原理→面试
三段式:先讲调用,再讲原理,最后给口语化面试答案。list 和 forward_list 是 STL 中唯二的链表容器,前者是双向链表,后者是单向链表,核心优势是任意位置 O(1) 插入/删除。
一、用法速查
1.1 初始化
cpp
#include <list>
#include <forward_list>
#include <iostream>
using namespace std;
int main() {
// --- list ---
list<int> l1; // 空链表
list<int> l2(5); // 5个元素,默认0
list<int> l3(5, 1); // 5个1
list<int> l4{1, 2, 3, 4, 5}; // 初始化列表
list<int> l5(l4); // 拷贝构造
// --- forward_list ---
forward_list<int> fl1; // 空单向链表
forward_list<int> fl2(5); // 5个元素,默认0
forward_list<int> fl3(5, 1); // 5个1
forward_list<int> fl4{1, 2, 3}; // 初始化列表
for (int x : l4) cout << x << " "; // 1 2 3 4 5
cout << "\n";
}
1.2 元素访问
cpp
#include <list>
#include <forward_list>
#include <iostream>
using namespace std;
int main() {
list<int> l{10, 20, 30, 40};
cout << l.front() << "\n"; // 10
cout << l.back() << "\n"; // 40
auto it = l.begin();
advance(it, 2); // O(N) 只能步步走
cout << *it << "\n"; // 30
forward_list<int> fl{10, 20, 30};
cout << fl.front() << "\n"; // 10
// fl.back() 不存在!forward_list 只有单向头访问
}
list 没有 operator[] 和 at(),不支持随机访问。要走到第 N 个元素必须用 advance 或 next,时间复杂度 O(N)。
1.3 头尾增删
cpp
#include <list>
#include <forward_list>
#include <iostream>
using namespace std;
void print(const list<int>& l) {
for (int x : l) cout << x << " ";
cout << "\n";
}
void print_fl(const forward_list<int>& fl) {
for (int x : fl) cout << x << " ";
cout << "\n";
}
int main() {
// --- list: 双端操作 ---
list<int> l;
l.push_back(1); // 1
l.push_front(0); // 0 1
l.push_back(2); // 0 1 2
print(l); // 0 1 2
l.pop_back(); // 0 1
l.pop_front(); // 1
print(l); // 1
// --- forward_list: 只有前端操作 ---
forward_list<int> fl;
fl.push_front(2); // 2
fl.push_front(1); // 1 2
fl.push_front(0); // 0 1 2
print_fl(fl); // 0 1 2
fl.pop_front(); // 1 2
print_fl(fl); // 1 2
// fl.push_back(3); // 编译错误!无 push_back
// fl.pop_back(); // 编译错误!无 pop_back
}
1.4 insert / erase
cpp
#include <list>
#include <iostream>
using namespace std;
void print(const list<int>& l) {
for (int x : l) cout << x << " ";
cout << "\n";
}
int main() {
list<int> l{1, 2, 3, 4, 5};
// insert:在迭代器之前插入
auto it = l.begin();
++it; ++it; // 指向3
l.insert(it, 99); // 在3之前插入99
print(l); // 1 2 99 3 4 5
// erase:删除迭代器指向的元素
it = l.begin();
++it; // 指向2
it = l.erase(it); // 删除2,返回下一个(99)
print(l); // 1 99 3 4 5
cout << *it << "\n"; // 99
// erase 区间
auto first = l.begin();
auto last = l.begin();
advance(first, 1); // 指向99
advance(last, 3); // 指向4
l.erase(first, last); // 删除99, 3
print(l); // 1 4 5
}
1.5 splice --- list 的核心操作(重点)
splice 把节点从一个 list 转移到另一个 list,不拷贝元素,只调整指针,时间复杂度 O(1)。
#mermaid-svg-5P1LHgwlLV8wcq2z{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5P1LHgwlLV8wcq2z .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5P1LHgwlLV8wcq2z .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5P1LHgwlLV8wcq2z .error-icon{fill:#552222;}#mermaid-svg-5P1LHgwlLV8wcq2z .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5P1LHgwlLV8wcq2z .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5P1LHgwlLV8wcq2z .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5P1LHgwlLV8wcq2z .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5P1LHgwlLV8wcq2z .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5P1LHgwlLV8wcq2z .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5P1LHgwlLV8wcq2z .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5P1LHgwlLV8wcq2z .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5P1LHgwlLV8wcq2z .marker.cross{stroke:#333333;}#mermaid-svg-5P1LHgwlLV8wcq2z svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5P1LHgwlLV8wcq2z p{margin:0;}#mermaid-svg-5P1LHgwlLV8wcq2z .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5P1LHgwlLV8wcq2z .cluster-label text{fill:#333;}#mermaid-svg-5P1LHgwlLV8wcq2z .cluster-label span{color:#333;}#mermaid-svg-5P1LHgwlLV8wcq2z .cluster-label span p{background-color:transparent;}#mermaid-svg-5P1LHgwlLV8wcq2z .label text,#mermaid-svg-5P1LHgwlLV8wcq2z span{fill:#333;color:#333;}#mermaid-svg-5P1LHgwlLV8wcq2z .node rect,#mermaid-svg-5P1LHgwlLV8wcq2z .node circle,#mermaid-svg-5P1LHgwlLV8wcq2z .node ellipse,#mermaid-svg-5P1LHgwlLV8wcq2z .node polygon,#mermaid-svg-5P1LHgwlLV8wcq2z .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5P1LHgwlLV8wcq2z .rough-node .label text,#mermaid-svg-5P1LHgwlLV8wcq2z .node .label text,#mermaid-svg-5P1LHgwlLV8wcq2z .image-shape .label,#mermaid-svg-5P1LHgwlLV8wcq2z .icon-shape .label{text-anchor:middle;}#mermaid-svg-5P1LHgwlLV8wcq2z .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5P1LHgwlLV8wcq2z .rough-node .label,#mermaid-svg-5P1LHgwlLV8wcq2z .node .label,#mermaid-svg-5P1LHgwlLV8wcq2z .image-shape .label,#mermaid-svg-5P1LHgwlLV8wcq2z .icon-shape .label{text-align:center;}#mermaid-svg-5P1LHgwlLV8wcq2z .node.clickable{cursor:pointer;}#mermaid-svg-5P1LHgwlLV8wcq2z .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5P1LHgwlLV8wcq2z .arrowheadPath{fill:#333333;}#mermaid-svg-5P1LHgwlLV8wcq2z .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5P1LHgwlLV8wcq2z .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5P1LHgwlLV8wcq2z .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5P1LHgwlLV8wcq2z .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5P1LHgwlLV8wcq2z .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5P1LHgwlLV8wcq2z .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5P1LHgwlLV8wcq2z .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5P1LHgwlLV8wcq2z .cluster text{fill:#333;}#mermaid-svg-5P1LHgwlLV8wcq2z .cluster span{color:#333;}#mermaid-svg-5P1LHgwlLV8wcq2z div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5P1LHgwlLV8wcq2z .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5P1LHgwlLV8wcq2z rect.text{fill:none;stroke-width:0;}#mermaid-svg-5P1LHgwlLV8wcq2z .icon-shape,#mermaid-svg-5P1LHgwlLV8wcq2z .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5P1LHgwlLV8wcq2z .icon-shape p,#mermaid-svg-5P1LHgwlLV8wcq2z .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5P1LHgwlLV8wcq2z .icon-shape .label rect,#mermaid-svg-5P1LHgwlLV8wcq2z .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5P1LHgwlLV8wcq2z .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5P1LHgwlLV8wcq2z .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5P1LHgwlLV8wcq2z :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 原始链表 L2
摘除节点 B
将 B 插入 L1
完成 O(1) 转移
cpp
#include <list>
#include <iostream>
using namespace std;
void print(const list<int>& l) {
for (int x : l) cout << x << " ";
cout << "\n";
}
int main() {
list<int> l1{1, 2, 3, 4, 5};
list<int> l2{10, 20, 30};
// splice 整个 l2 到 l1 的 begin() 位置
l1.splice(l1.begin(), l2);
print(l1); // 10 20 30 1 2 3 4 5
cout << "l2.size: " << l2.size() << "\n"; // 0,l2 被掏空
// splice 单个节点
list<int> l3{100, 200};
auto it = l1.begin();
advance(it, 2); // 指向30
l1.splice(it, l3, l3.begin());
print(l1); // 10 20 100 30 1 2 3 4 5
// splice 区间
list<int> l4{1, 2, 3};
list<int> l5{10, 20, 30, 40, 50};
auto first = l5.begin();
auto last = l5.begin();
advance(first, 1); // 指向20
advance(last, 4); // 指向50
l4.splice(l4.end(), l5, first, last); // 把20,30,40接到l4末尾
print(l4); // 1 2 3 20 30 40
print(l5); // 10 50
}
1.6 remove / remove_if / unique
cpp
#include <list>
#include <iostream>
using namespace std;
void print(const list<int>& l) {
for (int x : l) cout << x << " ";
cout << "\n";
}
int main() {
list<int> l{1, 3, 3, 2, 5, 3, 4, 5, 5};
l.remove(3); // 删除所有值为3的元素
print(l); // 1 2 5 4 5 5
l.remove_if([](int x) { return x > 3; });
print(l); // 1 2
// unique:删除相邻重复(只保留一个)
list<int> l2{1, 1, 2, 3, 3, 3, 4, 4};
l2.unique();
print(l2); // 1 2 3 4
// unique 备注:标准要求二元谓词必须是等价关系
// a + 1 == b 不满足自反性和对称性,因此是未定义行为
// 生产代码中务必使用符合等价关系的谓词
}
1.7 merge / sort / reverse
cpp
#include <list>
#include <iostream>
using namespace std;
void print(const list<int>& l) {
for (int x : l) cout << x << " ";
cout << "\n";
}
int main() {
// merge:合并两个已排序的 list
list<int> l1{1, 3, 5, 7};
list<int> l2{2, 4, 6, 8};
l1.merge(l2); // l1 归并 l2,l2 变空
print(l1); // 1 2 3 4 5 6 7 8
// sort:list 有自己的 sort(归并排序)
list<int> l3{5, 1, 4, 2, 3};
l3.sort();
print(l3); // 1 2 3 4 5
l3.sort(greater<int>()); // 降序
print(l3); // 5 4 3 2 1
// reverse
list<int> l4{1, 2, 3, 4, 5};
l4.reverse();
print(l4); // 5 4 3 2 1
}
1.8 forward_list 的 insert_after / erase_after
forward_list 是单向链表,只能在当前节点之后 插入或删除,没有 insert 和 erase,只有 insert_after 和 erase_after。它还有一个特殊的 before_begin 迭代器,指向首元素之前。
cpp
#include <forward_list>
#include <iostream>
using namespace std;
void print(const forward_list<int>& fl) {
for (int x : fl) cout << x << " ";
cout << "\n";
}
int main() {
forward_list<int> fl{1, 2, 3, 4, 5};
// insert_after:在指定位置之后插入
auto it = fl.begin(); // 指向1
fl.insert_after(it, 99); // 在1之后插入99
print(fl); // 1 99 2 3 4 5
// 在头部插入:用 before_begin
fl.insert_after(fl.before_begin(), 0);
print(fl); // 0 1 99 2 3 4 5
// erase_after:删除指定位置之后的元素
it = fl.begin(); // 指向0
fl.erase_after(it); // 删除0后面的1
print(fl); // 0 99 2 3 4 5
// erase_after 区间
auto first = fl.before_begin();
auto last = fl.begin();
advance(last, 3); // 指向3
fl.erase_after(first, last); // 删除 first 之后到 last 之前的元素
print(fl); // 3 4 5
}
1.9 常用函数速查
| 方法 | list | forward_list | 含义 | 复杂度 |
|---|---|---|---|---|
push_back |
✓ | ✗ | 尾部插入 | O(1) |
pop_back |
✓ | ✗ | 尾部删除 | O(1) |
push_front |
✓ | ✓ | 头部插入 | O(1) |
pop_front |
✓ | ✓ | 头部删除 | O(1) |
insert / erase |
✓ | ✗ | 任意位置插/删 | O(1) |
insert_after / erase_after |
✗ | ✓ | 指定位置之后插/删 | O(1) |
splice |
✓ | ✗ | 节点转移 | O(1) |
remove / remove_if |
✓ | ✓ | 按值删除 | O(N) |
unique |
✓ | ✓ | 去重相邻重复 | O(N) |
merge |
✓ | ✓ | 归并已排序链表 | O(N) |
sort |
✓ | ✓ | 排序 | O(NlogN) |
reverse |
✓ | ✓ | 反转 | O(N) |
size |
✓ (O(1) C++11起) | ✗ | 元素个数 | O(1) / ✗ |
back |
✓ | ✗ | 尾部元素 | O(1) |
二、底层原理
2.1 双向链表节点结构
std::list 底层是双向循环链表(gcc 实现带哨兵节点)。每个节点包含三个字段:
┌──────────────────────────────┐
│ list_node │
│ ┌─────────┬─────────┬─────┐ │
│ │ prev* │ next* │ T │ │
│ │ (前驱) │ (后继) │(数据)│ │
│ └─────────┴─────────┴─────┘ │
└──────────────────────────────┘
prev:指向前一个节点,首节点的 prev 指向哨兵或尾部next:指向后一个节点,尾节点的 next 指向哨兵或首部T:存储的元素数据
在 x64 架构下,每个 list 节点额外开销 = 2 个指针 = 16 字节(不含堆分配器自身的 cookie)。forward_list 只有 1 个 next 指针,每节点开销 8 字节。
对比 vector 的内存开销:
| 容器 | 每个元素额外开销 | 原因 |
|---|---|---|
vector |
0 字节 | 连续存储,只有数据本身 |
list |
16 字节 | 每个节点 2 个指针 |
forward_list |
8 字节 | 每个节点 1 个 next 指针 |
以 list<int> 为例:int 占 4 字节,每个节点额外 16 字节指针开销------有效载荷只占 20%。
2.2 节点级分配 vs 连续分配
最根本的区别在于内存布局和分配方式:
- vector:一次分配一块连续大内存,元素紧密排列。插入尾部 O(1) 均摊,中间插入 O(N)------需要挪动后续所有元素。
- list:每个节点单独分配在堆上,节点间通过指针连接。任意位置插入/删除 O(1)------只需修改相邻节点指针,不涉及元素拷贝。
- forward_list:同 list,每个节点单独分配,但指针少一半。
#mermaid-svg-KRrKLxAJYtX73SOj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KRrKLxAJYtX73SOj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KRrKLxAJYtX73SOj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KRrKLxAJYtX73SOj .error-icon{fill:#552222;}#mermaid-svg-KRrKLxAJYtX73SOj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KRrKLxAJYtX73SOj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KRrKLxAJYtX73SOj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KRrKLxAJYtX73SOj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KRrKLxAJYtX73SOj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KRrKLxAJYtX73SOj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KRrKLxAJYtX73SOj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KRrKLxAJYtX73SOj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KRrKLxAJYtX73SOj .marker.cross{stroke:#333333;}#mermaid-svg-KRrKLxAJYtX73SOj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KRrKLxAJYtX73SOj p{margin:0;}#mermaid-svg-KRrKLxAJYtX73SOj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KRrKLxAJYtX73SOj .cluster-label text{fill:#333;}#mermaid-svg-KRrKLxAJYtX73SOj .cluster-label span{color:#333;}#mermaid-svg-KRrKLxAJYtX73SOj .cluster-label span p{background-color:transparent;}#mermaid-svg-KRrKLxAJYtX73SOj .label text,#mermaid-svg-KRrKLxAJYtX73SOj span{fill:#333;color:#333;}#mermaid-svg-KRrKLxAJYtX73SOj .node rect,#mermaid-svg-KRrKLxAJYtX73SOj .node circle,#mermaid-svg-KRrKLxAJYtX73SOj .node ellipse,#mermaid-svg-KRrKLxAJYtX73SOj .node polygon,#mermaid-svg-KRrKLxAJYtX73SOj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KRrKLxAJYtX73SOj .rough-node .label text,#mermaid-svg-KRrKLxAJYtX73SOj .node .label text,#mermaid-svg-KRrKLxAJYtX73SOj .image-shape .label,#mermaid-svg-KRrKLxAJYtX73SOj .icon-shape .label{text-anchor:middle;}#mermaid-svg-KRrKLxAJYtX73SOj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KRrKLxAJYtX73SOj .rough-node .label,#mermaid-svg-KRrKLxAJYtX73SOj .node .label,#mermaid-svg-KRrKLxAJYtX73SOj .image-shape .label,#mermaid-svg-KRrKLxAJYtX73SOj .icon-shape .label{text-align:center;}#mermaid-svg-KRrKLxAJYtX73SOj .node.clickable{cursor:pointer;}#mermaid-svg-KRrKLxAJYtX73SOj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KRrKLxAJYtX73SOj .arrowheadPath{fill:#333333;}#mermaid-svg-KRrKLxAJYtX73SOj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KRrKLxAJYtX73SOj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KRrKLxAJYtX73SOj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KRrKLxAJYtX73SOj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KRrKLxAJYtX73SOj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KRrKLxAJYtX73SOj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KRrKLxAJYtX73SOj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KRrKLxAJYtX73SOj .cluster text{fill:#333;}#mermaid-svg-KRrKLxAJYtX73SOj .cluster span{color:#333;}#mermaid-svg-KRrKLxAJYtX73SOj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KRrKLxAJYtX73SOj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KRrKLxAJYtX73SOj rect.text{fill:none;stroke-width:0;}#mermaid-svg-KRrKLxAJYtX73SOj .icon-shape,#mermaid-svg-KRrKLxAJYtX73SOj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KRrKLxAJYtX73SOj .icon-shape p,#mermaid-svg-KRrKLxAJYtX73SOj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KRrKLxAJYtX73SOj .icon-shape .label rect,#mermaid-svg-KRrKLxAJYtX73SOj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KRrKLxAJYtX73SOj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KRrKLxAJYtX73SOj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KRrKLxAJYtX73SOj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} vector 内存
连续地址
a\]\[b\]\[c\]\[d
list 内存
指针连接
a<->b<->c<->d
节点分散各处
#mermaid-svg-IujBjshuA55TJZLj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IujBjshuA55TJZLj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IujBjshuA55TJZLj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IujBjshuA55TJZLj .error-icon{fill:#552222;}#mermaid-svg-IujBjshuA55TJZLj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IujBjshuA55TJZLj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IujBjshuA55TJZLj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IujBjshuA55TJZLj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IujBjshuA55TJZLj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IujBjshuA55TJZLj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IujBjshuA55TJZLj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IujBjshuA55TJZLj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IujBjshuA55TJZLj .marker.cross{stroke:#333333;}#mermaid-svg-IujBjshuA55TJZLj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IujBjshuA55TJZLj p{margin:0;}#mermaid-svg-IujBjshuA55TJZLj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-IujBjshuA55TJZLj .cluster-label text{fill:#333;}#mermaid-svg-IujBjshuA55TJZLj .cluster-label span{color:#333;}#mermaid-svg-IujBjshuA55TJZLj .cluster-label span p{background-color:transparent;}#mermaid-svg-IujBjshuA55TJZLj .label text,#mermaid-svg-IujBjshuA55TJZLj span{fill:#333;color:#333;}#mermaid-svg-IujBjshuA55TJZLj .node rect,#mermaid-svg-IujBjshuA55TJZLj .node circle,#mermaid-svg-IujBjshuA55TJZLj .node ellipse,#mermaid-svg-IujBjshuA55TJZLj .node polygon,#mermaid-svg-IujBjshuA55TJZLj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IujBjshuA55TJZLj .rough-node .label text,#mermaid-svg-IujBjshuA55TJZLj .node .label text,#mermaid-svg-IujBjshuA55TJZLj .image-shape .label,#mermaid-svg-IujBjshuA55TJZLj .icon-shape .label{text-anchor:middle;}#mermaid-svg-IujBjshuA55TJZLj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IujBjshuA55TJZLj .rough-node .label,#mermaid-svg-IujBjshuA55TJZLj .node .label,#mermaid-svg-IujBjshuA55TJZLj .image-shape .label,#mermaid-svg-IujBjshuA55TJZLj .icon-shape .label{text-align:center;}#mermaid-svg-IujBjshuA55TJZLj .node.clickable{cursor:pointer;}#mermaid-svg-IujBjshuA55TJZLj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IujBjshuA55TJZLj .arrowheadPath{fill:#333333;}#mermaid-svg-IujBjshuA55TJZLj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IujBjshuA55TJZLj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IujBjshuA55TJZLj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IujBjshuA55TJZLj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IujBjshuA55TJZLj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IujBjshuA55TJZLj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IujBjshuA55TJZLj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IujBjshuA55TJZLj .cluster text{fill:#333;}#mermaid-svg-IujBjshuA55TJZLj .cluster span{color:#333;}#mermaid-svg-IujBjshuA55TJZLj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IujBjshuA55TJZLj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IujBjshuA55TJZLj rect.text{fill:none;stroke-width:0;}#mermaid-svg-IujBjshuA55TJZLj .icon-shape,#mermaid-svg-IujBjshuA55TJZLj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IujBjshuA55TJZLj .icon-shape p,#mermaid-svg-IujBjshuA55TJZLj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IujBjshuA55TJZLj .icon-shape .label rect,#mermaid-svg-IujBjshuA55TJZLj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IujBjshuA55TJZLj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IujBjshuA55TJZLj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IujBjshuA55TJZLj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} vector 插入中间元素
需挪动后续所有元素
O(N)
list 插入中间元素
只改相邻节点指针
O(1)
2.3 splice 的 O(1) 原理
splice 是 list 独有的高效操作。它把源链表的节点整个摘下 ,挂到目标链表上,只调整 prev 和 next 指针------不拷贝、不移动、不分配元素。
cpp
// splice 一个节点 L1.splice(pos, L2, it) 等价于(伪代码):
auto node = it; // 记下要转移的节点
it->prev->next = it->next; // 从 L2 中摘除
it->next->prev = it->prev;
node->prev = pos->prev; // 挂到 L1 的 pos 之前
node->next = pos;
pos->prev->next = node;
pos->prev = node;
整个过程仅 6 条指针操作,与链表长度无关,严格 O(1)。
#mermaid-svg-5P1LHgwlLV8wcq2z{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5P1LHgwlLV8wcq2z .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5P1LHgwlLV8wcq2z .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5P1LHgwlLV8wcq2z .error-icon{fill:#552222;}#mermaid-svg-5P1LHgwlLV8wcq2z .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5P1LHgwlLV8wcq2z .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5P1LHgwlLV8wcq2z .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5P1LHgwlLV8wcq2z .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5P1LHgwlLV8wcq2z .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5P1LHgwlLV8wcq2z .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5P1LHgwlLV8wcq2z .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5P1LHgwlLV8wcq2z .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5P1LHgwlLV8wcq2z .marker.cross{stroke:#333333;}#mermaid-svg-5P1LHgwlLV8wcq2z svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5P1LHgwlLV8wcq2z p{margin:0;}#mermaid-svg-5P1LHgwlLV8wcq2z .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5P1LHgwlLV8wcq2z .cluster-label text{fill:#333;}#mermaid-svg-5P1LHgwlLV8wcq2z .cluster-label span{color:#333;}#mermaid-svg-5P1LHgwlLV8wcq2z .cluster-label span p{background-color:transparent;}#mermaid-svg-5P1LHgwlLV8wcq2z .label text,#mermaid-svg-5P1LHgwlLV8wcq2z span{fill:#333;color:#333;}#mermaid-svg-5P1LHgwlLV8wcq2z .node rect,#mermaid-svg-5P1LHgwlLV8wcq2z .node circle,#mermaid-svg-5P1LHgwlLV8wcq2z .node ellipse,#mermaid-svg-5P1LHgwlLV8wcq2z .node polygon,#mermaid-svg-5P1LHgwlLV8wcq2z .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5P1LHgwlLV8wcq2z .rough-node .label text,#mermaid-svg-5P1LHgwlLV8wcq2z .node .label text,#mermaid-svg-5P1LHgwlLV8wcq2z .image-shape .label,#mermaid-svg-5P1LHgwlLV8wcq2z .icon-shape .label{text-anchor:middle;}#mermaid-svg-5P1LHgwlLV8wcq2z .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5P1LHgwlLV8wcq2z .rough-node .label,#mermaid-svg-5P1LHgwlLV8wcq2z .node .label,#mermaid-svg-5P1LHgwlLV8wcq2z .image-shape .label,#mermaid-svg-5P1LHgwlLV8wcq2z .icon-shape .label{text-align:center;}#mermaid-svg-5P1LHgwlLV8wcq2z .node.clickable{cursor:pointer;}#mermaid-svg-5P1LHgwlLV8wcq2z .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5P1LHgwlLV8wcq2z .arrowheadPath{fill:#333333;}#mermaid-svg-5P1LHgwlLV8wcq2z .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5P1LHgwlLV8wcq2z .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5P1LHgwlLV8wcq2z .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5P1LHgwlLV8wcq2z .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5P1LHgwlLV8wcq2z .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5P1LHgwlLV8wcq2z .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5P1LHgwlLV8wcq2z .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5P1LHgwlLV8wcq2z .cluster text{fill:#333;}#mermaid-svg-5P1LHgwlLV8wcq2z .cluster span{color:#333;}#mermaid-svg-5P1LHgwlLV8wcq2z div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5P1LHgwlLV8wcq2z .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5P1LHgwlLV8wcq2z rect.text{fill:none;stroke-width:0;}#mermaid-svg-5P1LHgwlLV8wcq2z .icon-shape,#mermaid-svg-5P1LHgwlLV8wcq2z .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5P1LHgwlLV8wcq2z .icon-shape p,#mermaid-svg-5P1LHgwlLV8wcq2z .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5P1LHgwlLV8wcq2z .icon-shape .label rect,#mermaid-svg-5P1LHgwlLV8wcq2z .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5P1LHgwlLV8wcq2z .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5P1LHgwlLV8wcq2z .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5P1LHgwlLV8wcq2z :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 原始链表 L2
摘除节点 B
将 B 插入 L1
完成 O(1) 转移
2.4 为什么 forward_list 没有 size()
std::forward_list 不提供 size() 方法。原因在于它的设计哲学是最小开销:
#mermaid-svg-D9wv3ACl8uiVk9T4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-D9wv3ACl8uiVk9T4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-D9wv3ACl8uiVk9T4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-D9wv3ACl8uiVk9T4 .error-icon{fill:#552222;}#mermaid-svg-D9wv3ACl8uiVk9T4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-D9wv3ACl8uiVk9T4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-D9wv3ACl8uiVk9T4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-D9wv3ACl8uiVk9T4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-D9wv3ACl8uiVk9T4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-D9wv3ACl8uiVk9T4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-D9wv3ACl8uiVk9T4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-D9wv3ACl8uiVk9T4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-D9wv3ACl8uiVk9T4 .marker.cross{stroke:#333333;}#mermaid-svg-D9wv3ACl8uiVk9T4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-D9wv3ACl8uiVk9T4 p{margin:0;}#mermaid-svg-D9wv3ACl8uiVk9T4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-D9wv3ACl8uiVk9T4 .cluster-label text{fill:#333;}#mermaid-svg-D9wv3ACl8uiVk9T4 .cluster-label span{color:#333;}#mermaid-svg-D9wv3ACl8uiVk9T4 .cluster-label span p{background-color:transparent;}#mermaid-svg-D9wv3ACl8uiVk9T4 .label text,#mermaid-svg-D9wv3ACl8uiVk9T4 span{fill:#333;color:#333;}#mermaid-svg-D9wv3ACl8uiVk9T4 .node rect,#mermaid-svg-D9wv3ACl8uiVk9T4 .node circle,#mermaid-svg-D9wv3ACl8uiVk9T4 .node ellipse,#mermaid-svg-D9wv3ACl8uiVk9T4 .node polygon,#mermaid-svg-D9wv3ACl8uiVk9T4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-D9wv3ACl8uiVk9T4 .rough-node .label text,#mermaid-svg-D9wv3ACl8uiVk9T4 .node .label text,#mermaid-svg-D9wv3ACl8uiVk9T4 .image-shape .label,#mermaid-svg-D9wv3ACl8uiVk9T4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-D9wv3ACl8uiVk9T4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-D9wv3ACl8uiVk9T4 .rough-node .label,#mermaid-svg-D9wv3ACl8uiVk9T4 .node .label,#mermaid-svg-D9wv3ACl8uiVk9T4 .image-shape .label,#mermaid-svg-D9wv3ACl8uiVk9T4 .icon-shape .label{text-align:center;}#mermaid-svg-D9wv3ACl8uiVk9T4 .node.clickable{cursor:pointer;}#mermaid-svg-D9wv3ACl8uiVk9T4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-D9wv3ACl8uiVk9T4 .arrowheadPath{fill:#333333;}#mermaid-svg-D9wv3ACl8uiVk9T4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-D9wv3ACl8uiVk9T4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-D9wv3ACl8uiVk9T4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-D9wv3ACl8uiVk9T4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-D9wv3ACl8uiVk9T4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-D9wv3ACl8uiVk9T4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-D9wv3ACl8uiVk9T4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-D9wv3ACl8uiVk9T4 .cluster text{fill:#333;}#mermaid-svg-D9wv3ACl8uiVk9T4 .cluster span{color:#333;}#mermaid-svg-D9wv3ACl8uiVk9T4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-D9wv3ACl8uiVk9T4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-D9wv3ACl8uiVk9T4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-D9wv3ACl8uiVk9T4 .icon-shape,#mermaid-svg-D9wv3ACl8uiVk9T4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-D9wv3ACl8uiVk9T4 .icon-shape p,#mermaid-svg-D9wv3ACl8uiVk9T4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-D9wv3ACl8uiVk9T4 .icon-shape .label rect,#mermaid-svg-D9wv3ACl8uiVk9T4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-D9wv3ACl8uiVk9T4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-D9wv3ACl8uiVk9T4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-D9wv3ACl8uiVk9T4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
维护 size 计数器?
splice_after 转移区间时
需 O(N) 遍历计数才能更新
破坏 splice O(1) 承诺
用户调用 distance()
自行 O(N) 遍历计算
forward_list 保持最小开销
- 如果维护 size 计数器,
insert_after/erase_after/splice_after时都需要同步更新 splice_after可能把任意长度的区间从一个 forward_list 移到另一个------要知道区间长度,要么每次 O(N) 数一遍,要么破坏 splice O(1) 的承诺- forward_list 的设计哲学就是最小开销------单向链表本身是为了节省那一个 prev 指针,再加一个计数器就违背了初衷
需要大小时只能自己用 std::distance(fl.begin(), fl.end()) 遍历计数,O(N)。
2.5 C++11 引入 forward_list 的动机
C++11 之前 STL 只有 list(双向链表)。引入 forward_list 的原因:
- 内存减半:每个节点少一个 prev 指针(8 字节),大量元素时差别显著
- 空间局部性略优:节点更小,缓存压力更小
- 与 C 风格单向链表对应:很多底层数据结构(如哈希表的链地址法、邻接表)天然是单向链表
- 语义更轻量:明确告诉读者"只需单向遍历",降低心智负担
典型使用场景:哈希表的桶内链、图的邻接表、内存池的空闲链表。
2.6 遍历性能对比
#mermaid-svg-CiM6EYuEA0sXk7bP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CiM6EYuEA0sXk7bP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CiM6EYuEA0sXk7bP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CiM6EYuEA0sXk7bP .error-icon{fill:#552222;}#mermaid-svg-CiM6EYuEA0sXk7bP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CiM6EYuEA0sXk7bP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CiM6EYuEA0sXk7bP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CiM6EYuEA0sXk7bP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CiM6EYuEA0sXk7bP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CiM6EYuEA0sXk7bP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CiM6EYuEA0sXk7bP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CiM6EYuEA0sXk7bP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CiM6EYuEA0sXk7bP .marker.cross{stroke:#333333;}#mermaid-svg-CiM6EYuEA0sXk7bP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CiM6EYuEA0sXk7bP p{margin:0;}#mermaid-svg-CiM6EYuEA0sXk7bP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CiM6EYuEA0sXk7bP .cluster-label text{fill:#333;}#mermaid-svg-CiM6EYuEA0sXk7bP .cluster-label span{color:#333;}#mermaid-svg-CiM6EYuEA0sXk7bP .cluster-label span p{background-color:transparent;}#mermaid-svg-CiM6EYuEA0sXk7bP .label text,#mermaid-svg-CiM6EYuEA0sXk7bP span{fill:#333;color:#333;}#mermaid-svg-CiM6EYuEA0sXk7bP .node rect,#mermaid-svg-CiM6EYuEA0sXk7bP .node circle,#mermaid-svg-CiM6EYuEA0sXk7bP .node ellipse,#mermaid-svg-CiM6EYuEA0sXk7bP .node polygon,#mermaid-svg-CiM6EYuEA0sXk7bP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CiM6EYuEA0sXk7bP .rough-node .label text,#mermaid-svg-CiM6EYuEA0sXk7bP .node .label text,#mermaid-svg-CiM6EYuEA0sXk7bP .image-shape .label,#mermaid-svg-CiM6EYuEA0sXk7bP .icon-shape .label{text-anchor:middle;}#mermaid-svg-CiM6EYuEA0sXk7bP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CiM6EYuEA0sXk7bP .rough-node .label,#mermaid-svg-CiM6EYuEA0sXk7bP .node .label,#mermaid-svg-CiM6EYuEA0sXk7bP .image-shape .label,#mermaid-svg-CiM6EYuEA0sXk7bP .icon-shape .label{text-align:center;}#mermaid-svg-CiM6EYuEA0sXk7bP .node.clickable{cursor:pointer;}#mermaid-svg-CiM6EYuEA0sXk7bP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CiM6EYuEA0sXk7bP .arrowheadPath{fill:#333333;}#mermaid-svg-CiM6EYuEA0sXk7bP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CiM6EYuEA0sXk7bP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CiM6EYuEA0sXk7bP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CiM6EYuEA0sXk7bP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CiM6EYuEA0sXk7bP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CiM6EYuEA0sXk7bP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CiM6EYuEA0sXk7bP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CiM6EYuEA0sXk7bP .cluster text{fill:#333;}#mermaid-svg-CiM6EYuEA0sXk7bP .cluster span{color:#333;}#mermaid-svg-CiM6EYuEA0sXk7bP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CiM6EYuEA0sXk7bP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CiM6EYuEA0sXk7bP rect.text{fill:none;stroke-width:0;}#mermaid-svg-CiM6EYuEA0sXk7bP .icon-shape,#mermaid-svg-CiM6EYuEA0sXk7bP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CiM6EYuEA0sXk7bP .icon-shape p,#mermaid-svg-CiM6EYuEA0sXk7bP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CiM6EYuEA0sXk7bP .icon-shape .label rect,#mermaid-svg-CiM6EYuEA0sXk7bP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CiM6EYuEA0sXk7bP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CiM6EYuEA0sXk7bP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CiM6EYuEA0sXk7bP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} vector 遍历: 地址连续
CPU 预取命中率高
一次 cache line
加载多个相邻元素
list 遍历: 节点地址分散
频繁 cache miss
每一步都可能
触发 L3 miss → ~100ns 延迟
list 遍历慢的根源在于内存碎片化 ------每个节点是单独 new 出来的,分布在堆的不同区域。即使逻辑上相邻,它们在物理内存上也往往不相邻。CPU 的预取器对 vector 的连续内存高效工作,但对 list 的随机跳转无能为力。
三、面试题 + 口语化答案
Q1:splice 为什么是 O(1)?
"splice 不拷贝元素,只改指针。把源链表的节点从前后节点中断开(摘链),再挂到目标链表的指定位置(接链),全程大概 6 条指针赋值,和链表长度没关系。"
Q2:list 的 sort 和 vector 的 sort 有什么区别?
"vector 的 sort 用的是快速排序(或 introsort),依赖于随机访问迭代器------要能 O(1) 跳到中间做 partition。list 的迭代器是双向的,不支持随机访问,所以不能用快排。list 自己的 sort 用的是归并排序------自底向上归并,不依赖随机访问,正好适合链表结构。时间复杂度都是 O(NlogN),但 list sort 的常数更大。"
Q3:为什么 list 的插入不会导致已有迭代器失效?
"因为 list 每个节点是独立分配的堆内存,插入新节点只是在前后节点之间接上新节点,已有节点的地址不变,指向它们的迭代器自然有效。这和 vector 完全不同------vector 插入可能触发扩容整块内存搬迁,所有迭代器全废了。list 只有被 erase 的那个节点的迭代器会失效,其他全部有效。"
Q4:forward_list 和 list 怎么选?
"主要看两个维度。第一,是否需要双向遍历------不需要就选 forward_list,节省一半指针内存。第二,是否需要 size()------需要快速获取元素个数时只能选 list(C++11 起保证 O(1))。另外,如果需要 splice 把区间从一个链表转移到另一个,list 的 splice 是 O(1),forward_list 虽然也有 splice_after,但不会自动更新计数器,反而容易出 bug。"
Q5:erase 的返回值在 C++11 前后有什么变化?
"C++11 之前,list 的 erase(iterator) 返回 void(gcc 实现)或者根本不定义返回值(标准未要求)。C++11 起,标准统一要求所有容器的 erase 返回被删除元素的下一个元素的迭代器。所以现在你能写 it = l.erase(it) 安全地一边删除一边遍历。《Effective STL》里提到的那种删除循环的写法,C++11 之后已经不需要了------直接返回下一个有效的迭代器。"
Q6:list 遍历为什么比 vector 慢?
"根本原因在于 cache locality。vector 的元素在内存里连续排列,CPU 加载一个元素时会把相邻几十字节一并装入缓存,遍历下一个元素时几乎 100% 缓存命中。list 的每个节点是独立 new 出来的,逻辑相邻的元素在物理内存上可能隔得很远------很可能每个节点访问都触发一次 cache miss。现代 CPU 的内存访问延迟大约 100ns(L3 miss 时),list 遍历的瓶颈不在 CPU 算力,在内存带宽和缓存命中率。"
Q7:list 的 size() 在 C++11 前后有什么变化?
"C++11 之前,list 的 size() 可以是 O(N) 的------gcc 的实现确实会遍历计数。C++11 起,标准强制要求 size() 必须是 O(1),所以所有主流实现都在 list 内部维护了一个计数器,插入加 1,删除减 1。但 forward_list 仍然没有 size()------因为它不是容器通用的接口,标准不要求。"
Q8:什么场景下应该用 list 代替 vector?
"频繁在中间或头部插入/删除、且不需要随机访问的场景。比如维护一个 LRU 链表、做任务调度队列、或者当一个大缓冲的中间部分需要不断调整。另外如果对迭代器稳定性有要求------不能因为插入而让已有指针或引用失效------也只能用 list。不过大多数时候,vector 的性能表现比大多数人直觉中好很多------因为 cache 比 O 记号重要,实测 vector 的批量尾部插入远快于 list。"
Q9:forward_list 的 before_begin() 有什么意义?
"因为 forward_list 是单向的,只有 next 指针,没办法 O(1) 在首元素之前插入或删除。before_begin() 返回一个特殊的哨兵迭代器------它指向一个'虚拟的头节点之前的位置',配合 insert_after 和 erase_after 就能在链表头部做操作了。这是 forward_list 设计的巧思:用一个虚拟哨兵简化了'第一个位置'的特殊性。"
Q10:为什么 list 的 remove 是 O(N),splice 是 O(1)?
"remove 需要遍历整个链表找到值为目标的所有节点,然后逐个摘除并释放内存。splice 不需要查找------它直接操作迭代器指向的具体节点,只需要改指针。换句话说,O(N) 来自查找,O(1) 来自纯指针操作。如果你知道要转移的节点的迭代器,就是 O(1);你不知道就需要先 find(O(N))再 splice(O(1))。"
一句话总结:list 和 forward_list 通过节点级分配和指针连接实现了任意位置 O(1) 插入/删除,代价是每个节点的额外指针开销(16 字节 / 8 字节)、不支持随机访问、以及遍历时因 cache miss 导致的性能劣化------splice 是它们最精华的操作,理解它也就理解了链表指针操作的精髓。