一. list的介绍及使用
1.1 list的介绍

文档介绍
https://cplusplus.com/reference/list/list/?kw=list

**list 定义:**list 是带头双向循环链表,每个节点存数据 + 前后两个指针,支持前后双向遍历。
list 的优点:list 在任意位置插入和删除元素非常快,时间复杂度是 O(1),因为它只需要改一下前后节点的指针就行,不用像 vector 那样搬动后面的所有元素。而且 list 没有扩容的概念,每次插入新节点就是单独开一块内存,不存在 vector 那种扩容时全部搬移的问题,所以迭代器也不会因为插入而失效,只有被删除的那个节点的迭代器会失效。头尾插入删除也很
快,直接操作头节点和尾节点就行。
list 的缺点:list 最大的问题是不支持随机访问 。想取第 6 个元素,必须从头开始一步一步往后走,时间复杂度是 O(N),不能像 vector 那样直接用下标
v[5]取出来。所以遍历 list 只能用迭代器,不能写list[3]。另一个问题是内存开销大。每个节点除了存数据,还要存两个指针(指向前一个和后一个节点),如果数据本身很小(比如只存个int),指针占的空间反而比数据还大,浪费内存。而 vector 数据是连续存放的,没有这个额外开销。和 forward_list 对比:forward_list 是单向链表,每个节点只存一个指针(指向下一个节点),比 list 省了一个指针的内存,但只能单向遍历,不支持尾插尾删。不过 forward_list 实际开发中用的非常少,因为单链表尾删效率太低,不实用。
list的使用:如果程序需要频繁在中间位置插入和删除元素,就选 list。如果频繁随机访问,或者元素数量固定,就选 vector。两者各有各的用场,看具体场景选。
1.2list的使用
list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口。

1.2.1 list的构造

1. 默认构造
cpp
list<int> lt1; // 构造一个空的 list,里面啥也没有
不传任何参数,造一个空链表。
2. 填充构造
cpp
list<int> lt2(5, 10); // 构造一个 list,里面有 5 个 10
// lt2 = [10, 10, 10, 10, 10]
第一个参数是元素个数,第二个参数是每个元素的值。不传第二个参数的话,值取类型的默认值(比如 int 就是 0)。
3. 拷贝构造
cpp
list<int> lt3(lt2); // 用 lt2 拷贝一份新的 list
拿已有的 list 复制一份出来,内容和原 list 一样,互不影响。
4. 迭代器区间构造
cpp
vector<int> v = {1, 2, 3, 4, 5};
list<int> lt4(v.begin(), v.end()); // 用 vector 的区间构造 list
// lt4 = [1, 2, 3, 4, 5]
只要是迭代器区间,不管来自 vector、list 还是其他容器,都能拿来构造 list。这也体现了 STL 的迭代器接口统一的设计思想,不同容器之间可以互相拷贝数据。
1.2.2 list iterator的使用
此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。


list 的迭代器分两种:正向迭代器和反向迭代器。每种又分普通版和 const 版。
常迭代器版本
cpp
const_iterator cbegin() const; // 只能读不能写
const_iterator cend() const;
const_reverse_iterator crbegin() const;
const_reverse_iterator crend() const;


begin() / end()
begin():指向第一个元素
end():指向最后一个元素的下一个位置(哨兵位)
cpp
list<int> lt = {1, 2, 3, 4, 5};
list<int>::iterator it = lt.begin();
while (it != lt.end()) {
cout << *it << " "; // 输出:1 2 3 4 5
++it;
}


rbegin() / rend()
rbegin():反向迭代器,指向最后一个元素(相当于正向的end()位置)
rend():反向迭代器,指向第一个元素的前一个位置(相当于正向的begin()位置)
cpp
list<int>::reverse_iterator rit = lt.rbegin();
while (rit != lt.rend()) {
cout << *rit << " "; // 输出:5 4 3 2 1
++rit; // 注意:反向迭代器++是往前移动
}

功能: 反转 list 中元素的顺序。调用
reverse()后,list 里的元素会完全倒过来排列。
cpp
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
// lt = 1, 2, 3, 4, 5
lt.reverse();
// lt = 5, 4, 3, 2, 1
底层原理
list 是双向链表,反转不需要重新开空间搬数据,只需要把每个节点的
_prev和_next指针交换一下,然后把头节点和尾节点的指向改一下就行了。时间复杂度 O(N),需要遍历所有节点交换指针。
const 迭代器
当对象是 const 类型时,只能调用 const 版本的 begin() 和 end(),返回的是 const_iterator,只能读不能写
cpp
void PrintList(const list<int>& l)
{
list<int>::const_iterator it = l.begin();
while (it != l.end())
{
cout << *it << " ";
// *it = 10; // 这句会编译报错,const迭代器不能修改值
++it;
}
}
void TestReverse()
{
int arr[] = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20};
list<int> lt(arr, arr + sizeof(arr)/sizeof(arr[0]));
PrintList(lt); // const迭代器打印:2 4 6 8 10 12 14 16 18 20
lt.reverse();
PrintList(lt); // const迭代器反向打印:20 18 16 14 12 10 8 6 4 2
}
遍历演示:
cpp
void PrintList(const list<int>& l)
{
list<int>::const_iterator it = l.begin();
while (it != l.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
void TestListTraverse()
{
// 用数组初始化 list
int arr[] = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20};
list<int> lt(arr, arr + sizeof(arr) / sizeof(arr[0]));
// 正向遍历(普通迭代器)
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
// 反向遍历
list<int>::reverse_iterator rit = lt.rbegin();
while (rit != lt.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
// 范围 for 遍历
for (int x : lt)
{
cout << x << " ";
}
cout << endl;
}
begin() 第一个元素;
end() 最后一个元素后面;
rbegin() 最后一个元素;
rend() 第一个元素前面。
总结一下这个方向:
| 迭代器类型 | 获取方式 | 能否修改值 |
|---|---|---|
iterator |
l.begin() |
可读可写 |
const_iterator |
l.cbegin() 或 const 对象 |
只读 |
reverse_iterator |
l.rbegin() |
可读可写 |
const_reverse_iterator |
l.crbegin() |
只读 |
【注意】
begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
1.2.3 list capacity(容量操作)

https://cplusplus.com/reference/list/list/size/
https://cplusplus.com/reference/list/list/empty/


empty()
检测 list 是否为空,里面没有元素就返回 true,有元素就返回 false。
cpp
list<int> lt;
if (lt.empty()) {
cout << "链表为空" << endl; //不报错直接执行这句话
}
lt.push_back(10);
if (!lt.empty()) {
cout << "链表不为空,有 " << lt.size() << " 个元素" << endl;
}
size()
返回 list 中有效节点的个数,也就是当前存了多少个元素。
cpp
list<int> lt;
cout << lt.size() << endl; // 0
lt.push_back(10);
lt.push_back(20);
lt.push_back(30);
cout << lt.size() << endl; // 3
1.2.4 list element access(访问操作)
https://cplusplus.com/reference/list/list/front/
https://cplusplus.com/reference/list/list/back/



front()
返回第一个元素的引用,可以直接读取或修改。
cpp
list<int> lt;
lt.push_back(10);
lt.push_back(20);
lt.push_back(30);
cout << lt.front() << endl; // 10
lt.front() = 100; // 修改第一个元素
cout << lt.front() << endl; // 100
back()
返回最后一个元素的引用,可以直接读取或修改。
cpp
list<int> lt;
lt.push_back(10);
lt.push_back(20);
lt.push_back(30);
cout << lt.back() << endl; // 30
lt.back() = 300; // 修改最后一个元素
cout << lt.back() << endl; // 300
const 版本
当 list 是 const 对象时,返回的是 const 引用,只能读不能改。
cpp
void PrintFirstAndLast(const list<int>& lt)
{
cout << lt.front() << endl; // 只能读
cout << lt.back() << endl; // 只能读
// lt.front() = 100; // 编译报错,const对象不能修改
}
1.2.5 list modifiers(修改操作)
说明:list 可以在任意位置 进行 O(1) 的插入 和删除操作。

1.头尾插入删除操作
cpp
void TestPushPop()
{
list<int> lt; // 创建空链表
lt.push_back(10); // 尾部插入10 : 10
lt.push_back(20); // 尾部插入20 :10, 20
lt.push_back(30); // 尾部插入30 :10, 20, 30
PrintList(lt); // 输出:10 20 30
lt.push_front(5); // 头部插入5 : 5, 10, 20, 30
lt.push_front(0); // 头部插入0 : 0, 5, 10, 20, 30
PrintList(lt); // 输出:0 5 10 20 30
lt.pop_back(); // 删除尾部元素 :0, 5, 10, 20
lt.pop_front(); // 删除头部元素 :5, 10, 20
PrintList(lt); // 输出:5 10 20
}
push_back和pop_back是在链表尾部操作,push_front和pop_front是在链表头部操作。list 是双向链表,头尾操作都是 O(1),效率高。
2.任意位置插入删除
cpp
void TestInsertErase()
{
list<int> lt;
for (int i = 1; i <= 5; ++i)
lt.push_back(i); // 循环尾插 1 2 3 4 5
PrintList(lt); // 输出:1 2 3 4 5
auto it = lt.begin(); // it 指向第一个元素 1
++it; // it 向后走一步,指向第二个元素 2
lt.insert(it, 10); // 在 it 前面插入 10
PrintList(lt); // 输出:1 10 2 3 4 5
lt.erase(it); // 删除 it 指向的元素2
PrintList(lt); // 输出:1 10 3 4 5
lt.erase(lt.begin(), lt.end()); // 删除整个链表中所有元素
PrintList(lt); // 输出:为空
}
insert在指定位置前面插入元素,erase删除指定位置的元素。list 的 insert 和 erase 都是 O(1) 复杂度,因为只改指针,不用搬动数据 。erase(lt.begin(), lt.end())是区间删除,把整个链表清空。
3.交换和清空
cpp
void TestSwapClear()
{
list<int> lt1 = {1, 2, 3}; // 创建链表 lt1 = 1, 2, 3
list<int> lt2 = {9, 8, 7, 6}; // 创建链表 lt2 = 9, 8, 7, 6
PrintList(lt1); // 输出:1 2 3
PrintList(lt2); // 输出:9 8 7 6
lt1.swap(lt2); // 交换两个链表的内容
PrintList(lt1); // 输出:9 8 7 6
PrintList(lt2); // 输出:1 2 3
lt1.clear(); // 清空 lt1 所有元素
cout << lt1.size() << endl; // 输出:0
}
swap交换两个 list 的内容,只交换头节点指针,时间复杂度 O(1),效率很高。clear清空链表所有节点,逐个释放节点内存,时间复杂度 O(N)。size()返回当前元素个数,O(1) 复杂度。
问题补充:
为什么 C++98 建议用容器自己的 swap,而不是算法里的 swap?
算法里的 swap 干了啥
cpp
C++98 的 std::swap:
template <class T>
void swap(T& a, T& b)
{
T c(a); // 拷贝构造
a = b; // 赋值
b = c; // 赋值
}
交换两个对象,需要经历 1 次拷贝构造 + 2 次赋值。
vector 用算法 swap 干了啥
假设有两个 vector,各存了1万个数据:
cpp
vector<int> v1(10000, 1);
vector<int> v2(10000, 2);
swap(v1, v2); // 算法里的 swap
执行过程:
T c(a):把 v1 的 1万个数据深拷贝一份,开新空间
a = b:把 v2 的 1万个数据深拷贝给 v1,v1 原来的 1万个数据释放
b = c:把临时对象 c 的 1万个数据深拷贝给 v2,v2 原来的 1万个数据释放整整深拷贝了 3 次,开辟释放了 3 次内存,1万个元素被搬来搬去 3 趟,代价巨大。
C++98 里算法版 swap 需要深拷贝 3 次,代价很大,而容器自己的 swap 只交换几个指针,几乎零代价,所以 C++98 建议用容器自己的 swap。C++11 引入移动语义后算法版 swap 优化了很多,但容器自己的 swap 仍然是最优选择,因为永远是 O(1) 的指针交换。
1.2.6 list的迭代器失效
前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无
效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在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())
{
// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给
其赋值
l.erase(it);
++it;
}
}
// 修正
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())
{
l.erase(it++); // it = l.erase(it);
}
}
vector(连续数组)
- insert
会失效
原因分两种情况:
扩容:底层重新分配内存,所有迭代器全部作废
不扩容:插入点之后的元素整体后移,原来那个
pos指向的位置内容已经变了,即使迭代器本身没坏,语义也失效了
- erase
会失效删除点之后的元素整体前移,
pos指向的内容被覆盖,意义不再成立
list(双向链表)
- insert
不会失效底层是独立节点,插入只是新增一个节点,原来
pos指向的那个节点没有任何变化,仍然有效
- erase
会失效
pos指向的那个节点被释放了,继续使用就是野指针,必须重新获取有效迭代器
【知识补充】
a.容器迭代器的分类
从使用功能角度分:正向迭代器、反向迭代器,以及各自的 const 版本(只读)。
从容器底层结构角度分,迭代器分为三大类:
1. 单向迭代器(Unidirectional Iterator)
典型容器:
forward_list(单向链表)、unordered_set/unordered_map(哈希表)特征:只能
++(向前移动),不支持--(后退)含义:只能从前往后单向遍历
2. 双向迭代器(Bidirectional Iterator)
典型容器:
list(双向链表)、set/map(红黑树)特征:既能
++,也能--含义:可以前后双向遍历
3. 随机访问迭代器(Random Access Iterator)
典型容器:
string、vector、deque、array特征:支持
++、--、+、-、[]下标访问,甚至支持<、>等比较运算含义:可以直接跳转到任意位置,底层通常是连续内存(
deque略有不同,但接口支持随机访问)注意:C++ 标准库迭代器类别还有更细的层级(如输入迭代器、输出迭代器),但在日常使用中,上述三类最常用。
迭代器的本质
**迭代器是一种抽象接口,它在不暴露容器底层实现细节的前提下,提供统一的方式来访问或修改容器中存储的数据。**简单说:你不需要知道容器是数组、链表还是树,只要拿到迭代器,就能按统一方式遍历和操作。
容器、迭代器、算法之间的关系
容器 负责存储数据;算法 (如
sort、find、copy)负责处理数据;迭代器是两者之间的粘合剂。
三者关系可以用一句话概括:
算法通过迭代器间接访问容器,迭代器屏蔽了容器之间的底层差异,使得同一套算法可以适用于不同容器。举个例子就是:
std::find可以同时用于vector、list、set,靠的就是迭代器统一了访问方式。
迭代器的五种分类(标准库源码中的继承体系)
C++ 标准库在
stl_iterator.h中定义了五个迭代器标签(tag),它们之间存在继承关系:
input_iterator_tag------ 只读迭代器(输入迭代器),只能读取元素,单次遍历;output_iterator_tag------ 只写迭代器(输出迭代器),只能写入元素,单次遍历;forward_iterator_tag------ 单向迭代器 ,继承自input_iterator_tag,可多次遍历,支持++bidirectional_iterator_tag------ 双向迭代器 ,继承自forward_iterator_tag,支持++和--random_access_iterator_tag------ 随机访问迭代器 ,继承自bidirectional_iterator_tag,支持++、--、+、-、[]、比较运算
b.迭代器两种实现方式的核心逻辑
迭代器的实现方式取决于容器的底层数据结构。不同容器内部内存布局不同,遍历/迭代的细节也就不同,因此迭代器的实现分为两类:
方式一:原生指针天然就是迭代器
适用容器:底层空间物理地址连续的容器,如 string、vector、array
原因:
连续空间意味着元素在内存中一个挨着一个排列;
对原生指针执行
++,天然移动到下一个元素的位置;执行
--,天然回到上一个元素的位置;执行
*解引用,天然得到该位置存储的数据.
得到的结论:
此时原生指针本身就是迭代器。并不是原生指针有多厉害,而是因为它指向的空间物理地址连续,指针运算天然契合了遍历需求。
所以 vector<int>::iterator 实际上就是 int*(或类似原生指针的简单包装)。
方式二:封装类来实现迭代器
适用容器:底层空间物理地址不连续的容器,如
list、set、map
原因:
list的节点散落在内存各处,节点内包含data(数据)、prev(前驱指针)、next(后继指针),对原生指针执行++,只会跳到内存中的下一个地址,那里面可能是垃圾数据,而不是链表的下一个节点对原生指针执行*解引用,得到的是整个结构体,而不是单纯的数据。
解决方案:
用一个类封装原生指针,重载相关运算符,控制对象使用这些运算符时的行为。这个类的对象用起来像指针一样,但底层逻辑由我们自己定义。
必须重载的运算符(目的是让对象用起来像指针):
operator*()------ 解引用,获取当前节点存储的数据
operator->()------ 成员访问,用于访问节点内的成员(如it->data)
operator++()和operator++(int)------ 前置/后置自增,移动到下一个节点
operator==()和operator!=()------ 比较两个迭代器是否相等
按需重载的运算符(取决于容器是否支持双向遍历):
operator--()和operator--(int)------list是双向链表,需要重载;forward_list是单向链表,不需要
重要的区分点
方式一中的原生指针就是迭代器;方式二中的类对象像指针但不是原生指针。
两者在使用形式上完全一致(++、--、*、-> 都支持),但底层实现完全不同:
方式一靠的是内存连续,指针运算自然生效
方式二靠的是运算符重载,手动控制遍历逻辑
1.2.7容器操作
1.sort
https://cplusplus.com/reference/list/list/sort/

sort()稳定排序:值相同的元素在排序后仍保持原来的先后次序。
只移动节点,不操作元素本身:排序过程只调整链表节点的指针链接,不会对元素对象进行构造、销毁或拷贝。
链表排序效率一般:由于链表不支持随机访问,排序需要较多指针操作,整体效率不如数组或
vector,但在需要频繁增删元素的场景中仍有其适用价值。默认按升序排列(使用
<);若要降序,需传入比较函数对象,例如greater<int>()。
写法一:使用函数对象(具名对象)
cpp
include <list>
#include <functional>
using namespace std;
void test()
{
int arr[] = {10, 2, 5};
list<int> lt(arr, arr + sizeof(arr) / sizeof(int));
greater<int> gt;
lt.sort(gt); // 降序:10 5 2
}
写法二:使用匿名对象
cpp
#include <list>
#include <functional>
using namespace std;
void test()
{
int arr[] = {10, 2, 5};
list<int> lt(arr, arr + sizeof(arr) / sizeof(int));
lt.sort(greater<int>()); // 降序:10 5 2
}
2.unique
https://cplusplus.com/reference/list/list/unique/

函数作用
移除容器中连续相等的元素,只保留每组连续相等元素中的第一个。
注意:它不是全局去重,只针对相邻元素。
cpp
list<int> lt = {1, 1, 2, 2, 3, 1, 1};
lt.unique();
// 结果:1, 2, 3, 1
// 相同元素不相邻时不会被删除
3.remove
https://cplusplus.com/reference/list/list/remove/

函数作用
移除容器中所有值与 val 相等的元素,而不是只删除第一个。
cpp
#include <list>
using namespace std;
void test()
{
int arr[] = {5, 8, 2, 2, 9};
list<int> lt(arr, arr + sizeof(arr) / sizeof(int));
lt.remove(2); // 删除所有值为 2 的元素
// 结果:5 8 9
}
注意:
remove()不会对容器进行排序,只遍历删除匹配值;如果容器中有多个相同值,全部会被移除;
4. reverse
https://cplusplus.com/reference/list/list/reverse/

函数作用
反转容器中元素的顺序,将第一个变为最后一个,最后一个变为第一个。
cpp
#include <list>
using namespace std;
void test()
{
int arr[] = {2, 4, 6, 8, 10};
list<int> lt(arr, arr + sizeof(arr) / sizeof(int));
lt.reverse(); // 反转顺序
// 结果:10 8 6 4 2
}
5. merge
cplusplus.com/reference/list/list/merge/

函数作用
将两个已排序的链表合并为一个有序链表,合并后目标链表仍保持有序。
注意前提 :合并前两个
list容器都必须是已排序的。
cpp
#include <list>
using namespace std;
void test()
{
int arr1[] = {1, 3, 5};
int arr2[] = {2, 4, 6};
list<int> lt1(arr1, arr1 + sizeof(arr1) / sizeof(int));
list<int> lt2(arr2, arr2 + sizeof(arr2) / sizeof(int));
// lt1 和 lt2 都已排序
lt1.merge(lt2); // 将 lt2 合并到 lt1 中
// lt1: 1 2 3 4 5 6
// lt2:空
}
6.emplace
https://cplusplus.com/reference/list/list/emplace
函数作用
在指定位置直接构造元素,而不是先创建对象再拷贝或移动插入。
cpp
#include <list>
#include <iostream>
using namespace std;
struct Student
{
int id;
string name;
Student(int i, string n) : id(i), name(n) {}
};
void test()
{
list<Student> lt;
// 在链表末尾直接构造 Student 对象
lt.emplace(lt.end(), 0001, "孙悟空");
// 相当于:lt.insert(lt.end(), Student(0001, "孙悟空")); 但少了临时对象
}
7.emplace_back
https://cplusplus.com/reference/list/list/emplace_back/

函数作用
在链表末尾直接构造元素,而不是先创建对象再拷贝或移动插入。
cpp
#include <list>
#include <iostream>
#include <string>
using namespace std;
struct Student
{
int id;
string name;
Student(int i, string n) : id(i), name(n) {}
};
void test()
{
list<Student> lt;
// 在链表末尾直接构造 Student 对象
lt.emplace_back(0001, "孙悟空");
lt.emplace_back(0002, "猪八戒");
// 相当于:lt.push_back(Student(0001, "孙悟空")); 但少了临时对象
}
对比:push_back() 是先造好再搬进去;emplace_back() 是直接在屋里造,不用搬。
lt.push_back(Student(0001, "孙悟空")),那个 Student(0001, "孙悟空") 就是临时对象,用完就销毁了。而 lt.emplace_back(0001, "孙悟空") 直接在链表里造对象,没有临时对象产生
8.emplace_front
https://cplusplus.com/reference/list/list/emplace_front/

函数作用
在链表开头直接构造元素 ,有效增加容器大小(
size加 1),类比push_front,后者需要拷贝或移动一个已存在的对象。
cpp
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<pair<int, char>> mylist;
// 在链表开头直接构造 pair 对象
mylist.emplace_front(10, 'a');
mylist.emplace_front(20, 'b');
mylist.emplace_front(30, 'c');
// 输出:mylist contains: (30,c) (20,b) (10,a)
// 注意:最先插入的在最后,因为每次都在开头插入
for (auto& x : mylist)
cout << " (" << x.first << "," << x.second << ")";
cout << endl;
return 0;
}
二.List的底层结构
2.1List底层结构

head->next指向第一个数据节点 (d1);
head->prev指向最后一个数据节点 (d3);空链表时,
head->next和head->prev都指向head自身。
2.2STL_list 源码
cpp
// stl_list.h(SGI STL)
template <class T>
struct __list_node {
typedef void* void_pointer;
void_pointer next;
void_pointer prev;
T data;
};
template<class T, class Ref, class Ptr>
struct __list_iterator {
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
typedef __list_iterator<T, Ref, Ptr> self;
typedef Ptr pointer;
typedef Ref reference;
typedef __list_node<T>* link_type;
link_type node; // 核心指针
reference operator*() const { return node->data; }
pointer operator->() const { return &(operator*()); }
self& operator++() { node = (link_type)(node->next); return *this; }
self operator++(int) { self tmp = *this; ++*this; return tmp; }
self& operator--() { node = (link_type)(node->prev); return *this; }
self operator--(int) { self tmp = *this; --*this; return tmp; }
bool operator==(const self& x) const { return node == x.node; }
bool operator!=(const self& x) const { return node != x.node; }
};
template <class T, class Alloc = alloc>
class list {
protected:
typedef __list_node<T> list_node;
typedef list_node* link_type;
link_type node; // 哨兵节点指针
public:
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
// 构造函数
list() { empty_initialize(); }
protected:
void empty_initialize() {
node = get_node();
node->next = node;
node->prev = node;
}
// ... 其他构造、析构、插入、删除等实现
};
三.List的模拟实现
3.1list 的节点结构
cpp
namespace bit
{
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)
{}
};
3.2 list 的迭代器
写法一:用一个模板
cpp
// 写法一:一个类模板,通过 Ref 和 Ptr 参数区分普通/const 迭代器
// 优点:代码复用,维护成本低,是 STL 源码中的标准做法
// 模板参数说明:
// T : 元素类型
// Ref : 引用类型,实例化时传入 T& 或 const T&
// Ptr : 指针类型,实例化时传入 T* 或 const T*
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)
{}
// 必须重载的运算符(模拟指针行为)
// 1. 解引用 operator*()
// 功能:返回当前节点数据的引用
// 返回类型:Ref 可能是 T&(可修改)或 const T&(只读)
Ref operator*()
{
return _node->_data;
}
// 2. 成员访问 operator->()
// 功能:返回当前节点数据的指针,支持 it->member 写法
// 返回类型:Ptr 可能是 T*(可修改)或 const T*(只读)
Ptr operator->()
{
return &_node->_data;
}
// 3. 前置自增 operator++()
// 功能:迭代器指向下一个节点,返回自增后的自身
// 典型用法:++it
Self& operator++()
{
_node = _node->_next; // 指针后移到下一个节点
return *this; // 返回自身引用
}
// 4. 前置自减 operator--()
// 功能:迭代器指向前一个节点,返回自减后的自身
// 典型用法:--it
Self& operator--()
{
_node = _node->_prev; // 指针前移到上一个节点
return *this; // 返回自身引用
}
// 5. 后置自增 operator++(int)
// 功能:返回自增前的旧迭代器,然后让迭代器指向下一个节点
// 典型用法:it++ (int 参数是占位符,用于区分前置/后置)
Self operator++(int)
{
Self tmp(*this); // 保存当前状态的副本(旧值)
_node = _node->_next; // 指针后移
return tmp; // 返回旧值
}
// 6. 后置自减 operator--(int)
// 功能:返回自减前的旧迭代器,然后让迭代器指向前一个节点
// 典型用法:it--
Self operator--(int)
{
Self tmp(*this); // 保存当前状态的副本(旧值)
_node = _node->_prev; // 指针前移
return tmp; // 返回旧值
}
// 7. 不等运算符 operator!=()
// 功能:比较两个迭代器是否不相等(即是否指向不同节点)
bool operator!=(const Self& s) const
{
return _node != s._node; // 比较节点指针
}
// 8. 相等运算符 operator==()
// 功能:比较两个迭代器是否相等(即是否指向同一个节点)
bool operator==(const Self& s) const
{
return _node == s._node; // 比较节点指针
}
};
// 在 list 类中的使用方式
/*
template <class T>
class list {
public:
// 通过传不同模板参数,同一个类模板实例化成两种迭代器
typedef list_iterator<T, T&, T*> iterator; // 普通迭代器:可修改元素
typedef list_iterator<T, const T&, const T*> const_iterator; // const 迭代器:只读
iterator begin() { return iterator(_head->_next); }
const_iterator begin() const { return const_iterator(_head->_next); }
iterator end() { return iterator(_head); }
const_iterator end() const { return const_iterator(_head); }
};
*/
写法二:写两个类(直观但冗余)
cpp
// 写法二:分别写普通迭代器和 const 迭代器两个独立的类
// 缺点:两份代码几乎完全重复,维护成本高
// 普通迭代器(可修改元素)
template<class T>
struct list_iterator
{
// 类型别名
typedef list_node<T> Node;
typedef list_iterator<T> Self;
// 成员变量
Node* _node; // 封装节点指针
// 构造函数
list_iterator(Node* node)
: _node(node)
{}
//重载运算符
// 1. 解引用:返回 T&,允许修改数据
T& operator*()
{
return _node->_data;
}
// 2. 成员访问:返回 T*,允许修改数据
T* operator->()
{
return &_node->_data;
}
// 3. 前置++:返回自增后的自身
Self& operator++()
{
_node = _node->_next;
return *this;
}
// 4. 前置--:返回自减后的自身
Self& operator--()
{
_node = _node->_prev;
return *this;
}
// 5. 后置++:返回旧值,再自增
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
// 6. 后置--:返回旧值,再自减
Self operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
// 7. 不等运算符
bool operator!=(const Self& s) const
{
return _node != s._node;
}
// 8. 相等运算符
bool operator==(const Self& s) const
{
return _node == s._node;
}
};
// const 迭代器(只读,不可修改元素)
template<class T>
struct list_const_iterator
{
//类型别名
typedef list_node<T> Node;
typedef list_const_iterator<T> Self;
// 成员变量
Node* _node; // 封装节点指针
// 构造函数
list_const_iterator(Node* node)
: _node(node)
{}
// 重载运算符
// 1. 解引用:返回 const T&,只读不可修改
const T& operator*()
{
return _node->_data;
}
// 2. 成员访问:返回 const T*,只读不可修改
const T* operator->()
{
return &_node->_data;
}
// 3. 前置++:返回自增后的自身
Self& operator++()
{
_node = _node->_next;
return *this;
}
// 4. 前置--:返回自减后的自身
Self& operator--()
{
_node = _node->_prev;
return *this;
}
// 5. 后置++:返回旧值,再自增
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
// 6. 后置--:返回旧值,再自减
Self operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
// 7. 不等运算符
bool operator!=(const Self& s) const
{
return _node != s._node;
}
// 8. 相等运算符
bool operator==(const Self& s) const
{
return _node == s._node;
}
};
// 在 list 类中的使用方式
/*
template <class T>
class list {
public:
typedef list_iterator<T> iterator;
typedef list_const_iterator<T> const_iterator;
// ...
};
*/
迭代器的两种实现方式
迭代器说白了就是让遍历容器的方式统一,具体怎么实现看容器本身怎么存数据:
直接用原生指针:像 vector、string 这种一段连续的空间,原生指针自己就能往前走、往后走,拿来就能当迭代器用。
把指针包一层:像 list 这种节点散落在各处的,指针不能直接 ++ 就到下一个节点,得写个类把节点指针包起来,然后重载各种运算符,让这个类的对象用起来跟指针一模一样。
想让这个类像指针,必须重载这些运算符
既然要模仿指针,指针能干啥,这个类就得能干啥:
operator*():解引用,拿到当前指向的数据。
operator->():取成员,支持it->xxx这种写法。
operator++()和operator++(int):往前走一步,前置后置都得有。
operator==()和operator!=():判断两个迭代器是否指向同一个位置,遍历结束要靠它。有些运算符看情况重载
operator--()和operator--(int):只有能往回走的容器才需要,比如 list 是双向链表,可以往前,就需要;forward_list 是单向链表,只能往前,就不需要。如果是 vector 这种支持随机访问的,还得额外重载
operator+=、operator[]这些。普通迭代器和 const 迭代器怎么实现
推荐做法 :写一个类模板,多加两个模板参数(引用类型和指针类型)。普通迭代器传
T&和T*,const 迭代器传const T&和const T*,一套代码两套用法,省事好维护。不推荐做法:普通迭代器写一个类,const 迭代器再单独写一个类,代码基本一样,改个 bug 得改两份,自己给自己找麻烦。
1.为什么 list 的 const 迭代器实现起来和 vector 不一样
vector 和 string 的迭代器就是原生指针,想让它只读,直接在前面加个 const 就行,比如 const int* 和 int* 的区别。
但 list 的迭代器是一个类,不是指针。想让这个类变成只读的,关键就是让用户通过它拿到的数据不能被修改。也就是说,operator*() 返回的是常引用,operator->() 返回的是常指针,用户就只能看不能改了。
二、那怎么实现两种迭代器呢?
法一:写两个独立的类
一个普通迭代器类,一个 const 迭代器类。后者把前者的返回值全部改成 const 版本。
这么做当然能跑,但问题是两个类的代码几乎一样,就返回值那里差个 const。以后要加功能或者修 bug,得同时改两份,自己给自己找事做。
法二:用一个模板,靠参数控制返回值类型
既然两个类只有返回值类型不同,那就把返回值类型当成模板参数传进去。
迭代器模板多搞两个参数:一个管引用类型,一个管指针类型。普通迭代器传
T&和T*,const 迭代器传const T&和const T*。一套代码,传不同的参数就生成不同的迭代器,省心省力。
3.为什么不能靠函数重载解决这个问题
有人可能会想,在一个类里写两个
operator*(),一个返回T&,一个返回const T&,编译器不就能区分了吗?不能。C++ 规定函数重载只看参数列表,不看返回值。两个函数参数一样、返回值不同,编译器根本分不清该调哪个,直接报错。
4.SGI 源码的做法总结
SGI 全称 Silicon Graphics International (硅谷图形国际公司)。
在 C++ 领域提到 SGI,通常指 SGI STL------该公司实现的经典标准模板库版本,由 Stepanov 和 Lee 等人主导开发,代码质量高、风格清晰,是很多编译器标准库的参考蓝本。
**SGI 就是用了法二,**给迭代器模板加了Ref和Ptr两个参数。Ref决定operator*()的返回类型,Ptr决定operator->()的返回类型。
在 list 里面定义两种迭代器的时候,传不同的参数就完事了:
普通迭代器:
Ref = T&,Ptr = T*const 迭代器:
Ref = const T&,Ptr = const T*
cpp
// 迭代器模板:Ref 和 Ptr 控制返回值类型
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 可能是 T& 或 const T&
Ref operator*() {
return _node->_data;
}
// Ptr 可能是 T* 或 const T*
Ptr operator->() {
return &_node->_data;
}
Self& operator++() {
_node = _node->_next;
return *this;
}
// 其他运算符...
};
// list 类中定义两种迭代器
template<class T>
class list {
public:
// 普通迭代器:传 T& 和 T*
typedef list_iterator<T, T&, T*> iterator;
// const 迭代器:传 const T& 和 const T*
typedef list_iterator<T, const T&, const T*> const_iterator;
iterator begin() {
return iterator(_head->_next);
}
const_iterator begin() const {
return const_iterator(_head->_next);
}
iterator end() {
return iterator(_head);
}
const_iterator end() const {
return const_iterator(_head);
}
};
1.遍历 list 的两种场景
用迭代器遍历 list 的时候,不管里面存的是什么类型,方式都一样:从
begin()走到end(),逐个访问。
存的是内置类型(int、double 等) :*it 直接拿到节点里存的那个值,打印或者赋值都行。
cpp
int arr[] = {1, 2, 3, 4, 5};
list<int> lt(arr, arr + sizeof(arr) / sizeof(int));
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " "; // *it 拿到节点里存的数据
++it;
}
// 输出:1 2 3 4 5
存的是自定义类型或结构体 :
*it拿到的是整个对象,想访问里面的成员就得用->。比如节点里存的是TreeNode,想拿_val就写it->_val。
cpp
#include <iostream>
#include <list>
using namespace std;
// ---------- 自定义类型:AA 结构体 ----------
struct AA
{
int _a1;
int _a2;
// 构造函数
AA(int a1 = 0, int a2 = 0)
: _a1(a1)
, _a2(a2)
{}
};
// ---------- 打印容器内容的函数模板 ----------
template<typename Container>
void print_container(const Container& con)
{
for (auto e : con)
{
cout << e << " ";
}
cout << endl;
}
// ---------- 测试函数 ----------
void test_list1()
{
// ========== 第一部分:list 存内置类型 ==========
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
// 用迭代器遍历并修改数据
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
*it += 10; // 通过解引用修改数据
cout << *it << " "; // 打印修改后的值
++it;
}
cout << endl;
// 用范围 for 遍历(只读)
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
// 用打印函数遍历
print_container(lt);
//第二部分:list 存自定义类型 AA
list<AA> lta;
lta.push_back(AA(1, 2)); // 插入 AA(1, 2)
lta.push_back(AA(3, 4)); // 插入 AA(3, 4)
lta.push_back(AA(5, 6)); // 插入 AA(5, 6)
lta.push_back(AA(7, 8)); // 插入 AA(7, 8)
// 用迭代器遍历自定义类型
list<AA>::iterator ita = lta.begin();
while (ita != lta.end())
{
//方式1:先解引用再 . 访问
cout << (*ita)._a1 << ":" << (*ita)._a2 << endl;
// 方式2:用 -> 直接访问(推荐)
// 底层:ita.operator->()->_a1
// 编译器特殊处理,本来应该是两个 ->,为了可读性省略了一个
cout << ita->_a1 << ":" << ita->_a2 << endl;
// 方式3:完整展开形式(了解底层)
cout << ita.operator->()->_a1 << ":" << ita.operator->()->_a2 << endl;
++ita;
}
cout << endl;
}
关于
it->_a1这个写法,底层实际上是it.operator->()->_a1,也就是迭代器重载的->返回了节点数据的地址,然后这个地址再去访问成员。编译器为了可读性把两个->缩成了一个,写起来才那么简洁。
2.迭代器需不需要自己写拷贝构造、赋值重载、析构?
不需要。
迭代器里虽然包了一个节点指针,但这个指针指向的节点是链表管的,不是迭代器管的。链表创建节点、释放节点都是 list 自己做的事,迭代器只负责指着某个节点,不负责节点的生和死。编译器默认生成的拷贝构造和析构就够用了,不用自己动手
3.vector 迭代器和 list 迭代器有什么区别?
物理上没啥区别,都是 4 个字节(32 位下),里面存的就是一个指针。
cpp
cpp
// vector 迭代器 - 原生指针
typedef T* iterator; // 大小:4 字节(32位)
// list 迭代器 - 封装的类
template<class T, class Ref, class Ptr>
struct list_iterator {
Node* _node; // 大小:4 字节(32位)
// ... 运算符重载
};
区别在于逻辑上:
vector 迭代器 就是个原生指针,++ 就是指针自己往后走一步,因为 vector 内存是连续的,往前走一步就到下一个元素了。
cpp
// vector 迭代器的 ++
int* it = ...;
++it; // 指针直接 +1,跳到下一个元素
list 迭代器 是个类,++ 调的是 operator++() 函数,函数里面把指针从当前节点移到下一个节点。因为 list 内存不连续,不能直接往后走,得靠 _next 指针跳过去。
cpp
// list 迭代器的 ++
list_iterator it = ...;
++it; // 调用 it.operator++(),内部执行 _node = _node->_next
物理上看都是存了一个指针,但执行
++的时候,一个是直接加,一个是调用函数跳转,逻辑完全不一样。
3.3list 类框架
cpp
namespace bit
{
template<class T>
class list
{
typedef list_node<T> Node; // 节点类型别名
public:
// 迭代器类型定义(嵌套类型)
typedef list_iterator<T, T&, T*> iterator; // 普通迭代器
typedef list_iterator<T, const T&, const T*> const_iterator; // const 迭代器
// 迭代器接口
iterator begin() { return _head->_next; }
iterator end() { return _head; }
const_iterator begin() const { return _head->_next; }
const_iterator end() const { return _head; }
private:
Node* _head; // 哨兵节点指针
size_t _size; // 有效元素个数
};
}
3.4list 的底层结构
list 的底层是带头双向循环链表。
命名空间与类结构
cpp
namespace hjq
{
// 带头双向循环链表
template<class T> // T: 节点中数据的类型
class list
{
public:
// 节点类型别名
typedef __list_node<T> Node;
// 迭代器类型定义
// iterator是内嵌类型,在list类域里面定义的类型(类中类)
// 所有容器迭代器都叫iterator,用起来统一规范
// 普通迭代器:传 T, T&, T*
typedef __list_iterator<T, T&, T*> iterator;
// const迭代器:传 T, const T&, const T*
typedef __list_iterator<T, const T&, const T*> const_iterator;
// 迭代器接口
iterator begin(); // 返回指向第一个有效节点的迭代器
iterator end(); // 返回指向头节点(哨兵)的迭代器
const_iterator begin() const; // const版本
const_iterator end() const; // const版本
// 默认成员函数
list(); // 默认构造函数
template<class InputIterator>
list(InputIterator first, InputIterator last); // 迭代器区间构造
list(const list<T>& lt); // 拷贝构造(深拷贝)
list<T>& operator=(list<T> lt); // 赋值重载(现代写法)
~list(); // 析构函数
//容量操作
size_t size() const; // 有效元素个数
bool empty() const; // 判空
// 修改操作
iterator insert(iterator pos, const T& data); // 在pos前插入
iterator erase(iterator pos); // 删除pos位置元素
void push_back(const T& data); // 尾插
void push_front(const T& data); // 头插
void pop_back(); // 尾删
void pop_front(); // 头删
void clear(); // 清空所有有效元素
private:
Node* _head; // 哨兵节点指针(不存数据,指向自己表示空链表)
};
}
辅助结构(需补充定定义库里面没有的)
cpp
// 节点结构
template<class T>
struct __list_node
{
__list_node<T>* _next; // 指向下一个节点
__list_node<T>* _prev; // 指向前一个节点
T _data; // 存储的数据
__list_node(const T& data = T())
: _next(nullptr)
, _prev(nullptr)
, _data(data)
{}
};
// 迭代器结构
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 = nullptr);
Ref operator*() const;
Ptr operator->() const;
Self& operator++();
Self operator++(int);
Self& operator--();
Self operator--(int);
bool operator==(const Self& s) const;
bool operator!=(const Self& s) const;
};
1.迭代器类型定义的代码
cpp
namespace hjq
{
template<class T>
class list
{
public:
// 普通迭代器:传 T, T&, T*
typedef __list_iterator<T, T&, T*> iterator;
// const迭代器:传 T, const T&, const T*
typedef __list_iterator<T, const T&, const T*> const_iterator;
};
}
2.为什么要 typedef 成 iterator 和 const_iterator
__list_iterator<T, T&, T*>和__list_iterator<T, const T&, const T*>是定义在 list 类内部的嵌套类型,用 typedef 重命名为iterator和const_iterator,目的很明确:统一规范迭代器的名称。所有容器的迭代器都叫
iterator,不管是 vector、list 还是 map,使用者拿到容器就知道用::iterator来声明迭代器,不用记各种乱七八糟的名字。如果每个容器都各起各的名字,比如 vector 叫vec_iter,list 叫list_iter,那用起来成本就太高了。
3.迭代器类型和容器类型的关系
比如:
list<Book> lt;(Book自定义的结构体),编译器会这样处理:第一步:list 类模板根据
Book实例化模板参数
T被替换成Book类里面所有的
T都变成Book第二步:迭代器类型自动推导
iterator的定义是__list_iterator<T, T&, T*>,T是Book,所以普通迭代器就变成__list_iterator<Book, Book&, Book*>
const_iterator的定义是__list_iterator<T, const T&, const T*>,T是Book,所以 const 迭代器就变成__list_iterator<Book, const Book&, const Book*>第三步:两个迭代器模板各自实例化
普通迭代器的
Ref是Book&,operator*()返回Book&,可以修改书籍数据const 迭代器的
Ref是const Book&,operator*()返回const Book&,只能读取不能修改
为什么说迭代器和容器是共生的?
其中
T、T&、T*都是类型参数。当 list 的类型确定了,T、T&、T*的类型自动确定,那么iterator的类型同时也自动确定了。这看起来就像
iterator和list是共生的一样------有什么样的容器,就有什么样的迭代器。list<Book>配的就是操作Book数据的迭代器,list<int>配的就是操作int数据的迭代器,就像什么样的锁配什么样的钥匙,锁定了,钥匙也跟着确定了。
代码如下看效果:
cpp
struct Book
{
string _name;
double _price;
};
list<Book> books;
list<Book>::iterator it = books.begin();
list<Book>::const_iterator cit = books.begin();
it->_price = 99.9; // 可以修改,因为 operator*() 返回 Book&
cit->_price = 99.9; // 编译报错,因为 operator*() 返回 const Book&
cout << it->_name; // 可以读取
cout << cit->_name; // 可以读取
3.4list 的一些成员函数
(1)默认成员函数详解
a. 构造函数
1. 默认构造函数:创建空链表
cpp
// 初始化哨兵节点,让 _next 和 _prev 都指向自己(空链表状态)
void empty_init()
{
_head = new Node; // 创建哨兵节点(不存数据)
_head->_next = _head; // 自己指向自己
_head->_prev = _head;
_size = 0;
}
list() { empty_init(); }
2. 初始值列表构造函数(C++11)
cpp
list(initializer_list<T> il)
{
empty_init(); // 先初始化空链表
for (auto& e : il) // 遍历初始值列表,逐个尾插
{
push_back(e);
}
}
b. 拷贝构造函数(深拷贝)
cpp
// lt2(lt1) -- 用 lt1 拷贝构造 lt2
list(const list<T>& lt)
{
empty_init(); // 先初始化空链表
for (auto& e : lt) // 遍历 lt 中的每个元素
{
push_back(e); // 逐个尾插到新链表
}
}
大致过程:
创建空链表(只生成哨兵节点)
遍历源链表,每个元素调用
push_back插入新节点每个新节点独立分配内存,深拷贝数据
c. 赋值运算符重载(传统写法)
cpp
// 传统写法:lt1 = lt3
list<T>& operator=(const list<T>& lt)
{
// 检查自赋值
if (this != <)
{
clear(); // 清空当前所有有效节点
for (auto& e : lt) // 遍历源链表
{
push_back(e); // 逐个尾插
}
}
return *this;
}
缺点 :先
clear()再插入,期间链表为空,如果插入过程中抛出异常,链表状态被破坏,异常安全性较差。
d. 赋值运算符重载(现代写法)
cpp
// 现代写法:lt1 = lt3
list<T>& operator=(list<T> lt) // 传值(拷贝构造一份临时对象)
{
swap(lt); // 交换当前对象和临时对象
return *this; // 临时对象出函数作用域自动销毁
}
// swap 交换函数
void swap(list<T>& lt)
{
std::swap(_head, lt._head); // 交换哨兵节点指针
std::swap(_size, lt._size); // 交换大小
}
大致过程:
传值调用,实参拷贝构造出临时对象
lt(复用拷贝构造);交换当前对象和临时对象的内部资源(
_head和_size);函数返回,临时对象销毁,带走原来的旧资源。
优点 :异常安全,代码简洁,复用拷贝构造,这是 C++ 推荐的现代写法。
e. 析构函数
cpp
~list()
{
clear(); // 清空所有有效节点
delete _head; // 释放哨兵节点
_head = nullptr; // 防止野指针
}
大致流程:
clear()遍历释放所有有效数据节点;
delete _head释放哨兵节点;
_head置空,防止后续误用。
(2)容量操作
cpp
// 返回有效元素个数
size_t size() const
{
return _size;
}
// 判空:有效元素是否为 0
bool empty() const
{
return _size == 0;
}
(3)修改操作
1. insert:在 pos 位置之前插入新节点
cpp
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node; // 当前节点
Node* prev = cur->_prev; // 前一个节点
Node* newnode = new Node(x); // 创建新节点
// prev <-> newnode <-> cur
newnode->_next = cur;
cur->_prev = newnode;
newnode->_prev = prev;
prev->_next = newnode;
++_size; // 维护大小
return newnode; // 返回指向新节点的迭代器
}
在
pos位置之前 插入新节点,新节点的值为x插入后新节点成为
pos的前返回指向新节点的迭代器
时间复杂度 O(1)
2. erase:删除 pos 位置的节点
cpp
iterator erase(iterator pos)
{
assert(pos != end()); // 不能删除哨兵节点
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
// prev <-> next
prev->_next = next;
next->_prev = prev;
delete pos._node; // 释放被删除节点
--_size; // 维护大小
return next; // 返回指向下一个节点的迭代器
}
删除
pos位置的节点
pos不能是end()(哨兵节点不能删)返回指向被删除节点下一个节点的迭代器
时间复杂度 O(1)
push_back:尾插
cpp
void push_back(const T& x)
{
insert(end(), x); // 在哨兵节点之前插入(即最后一个有效节点之后)
}
end()指向哨兵节点,在哨兵节点之前插入,就是在链表尾部插入新节点。
push_front:头插
cpp
void push_front(const T& x)
{
insert(begin(), x); // 在第一个有效节点之前插入
}
begin()指向第一个有效节点,在它之前插入,新节点就成为新的第一个有效节点。
pop_back:尾删
cpp
void pop_back()
{
erase(--end()); // 删除最后一个有效节点
}
end()指向哨兵节点,--end()向前移动一步,指向最后一个有效节点,然后删除它。
(6)pop_front:头删
cpp
void pop_front()
{
erase(begin()); // 删除第一个有效节点
}
begin()指向第一个有效节点,直接删除它。删除后原来的第二个节点成为新的第一个节点。
(7)clear:清空所有有效元素
cpp
void clear()
{
auto it = begin();
while (it != end())
{
it = erase(it); // 逐个删除,erase 返回下一个位置
}
}
clear()用于清空链表中的所有有效元素,删除所有数据节点,让链表回到空链表状态(只剩哨兵节点)。
(4)类模板成员函数的实例化
1.类模板的实例化过程
当编译器遇到
list<int> lt;这样的代码时,会先根据int这个具体类型,从类模板list<T>中生成一个具体的list<int>类,然后再用这个类创建出lt对象。
2.成员函数都是函数模板
类模板里的每个成员函数,本质上都是一个函数模板。在模板参数
T确定之前,它们只是半成品,不是真正的函数。
3.按需实例化
类模板的成员函数不是一口气全部生成,而是按需实例化------程序里用到哪个成员函数,编译器才去生成哪个函数的代码。程序里没调用的成员函数,编译器就不会去生成它的机器码。
4.不调用就不检查
一个成员函数如果从来没被实例化过,编译器就不会去检查它内部的语法是否正确。即使里面有拼写错误、类型不匹配等问题,只要没被调用,程序
结合代码理解
cpp
list<int> lt; // 实例化 list<int> 类,成员函数还没生成
lt.push_back(1); // 调用 push_back,此时才实例化 push_back
lt.push_back(2); // 调用 push_back,已有实例化版本,直接使用
lt.push_front(3); // 调用 push_front,此时才实例化 push_front
// pop_back 没有被调用,所以不会被实例化
cpp
// 函数模板的按需实例化
// T* const ptr1
// const T* ptr2
template<class Container>
void print_container(const Container& con)
{
// const iterator -> 迭代器本身不能修改
// const_iterator -> 指向内容不能修改
typename Container::const_iterator it = con.begin();
//auto it = con.begin();
while (it != con.end())
{
//*it += 10;
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : con)
{
cout << e << " ";
}
cout << endl;
}
print_container 本身是一个函数模板,不是真正的函数。只有当你用具体的容器类型去调用它时,编译器才会根据那个类型生成对应的函数版本。
print_container函数模板,当用list<int>去调用它的时候,编译器会把Container推导成list<int>,然后生成一份专门处理list<int>的代码。如果用vector<double>去调用,编译器又会生成另一份专门处理vector<double>的代码。也就是说,同一个函数模板,传什么类型就按需生成什么版本的函数,不是提前把所有可能的版本都准备好。
四.list与vector的对比
vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

五.总结代码
如下分为两个部分:
List.h
cpp
#pragma once
#include<assert.h>
namespace bit
{
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)
{
}
};
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--()
{
_node = _node->_prev;
return *this;
}
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
Self& operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
};
/*template<class T>
struct list_const_iterator
{
typedef list_node<T> Node;
typedef list_const_iterator<T> Self;
Node* _node;
list_const_iterator(Node* node)
:_node(node)
{}
const T& operator*()
{
return _node->_data;
}
const T* operator->()
{
return &_node->_data;
}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
Self& operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
};*/
template<class T>
class list
{
typedef list_node<T> Node;
public:
/*typedef list_iterator<T> iterator;
typedef list_const_iterator<T> const_iterator;*/
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
iterator begin()
{
/* iterator it(_head->_next);
return it;*/
//return iterator(_head->_next);
return _head->_next;
}
iterator end()
{
return _head;
}
const_iterator begin() const
{
return _head->_next;
}
const_iterator end() const
{
return _head;
}
void empty_init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
list()
{
empty_init();
}
list(initializer_list<T> il)
{
empty_init();
for (auto& e : il)
{
push_back(e);
}
}
// lt2(lt1)
list(const list<T>& lt)
{
empty_init();
for (auto& e : lt)
{
push_back(e);
}
}
// lt1 = lt3
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
auto it = begin();
while (it != end())
{
it = erase(it);
}
}
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
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;*/
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
// prev newnode cur
newnode->_next = cur;
cur->_prev = newnode;
newnode->_prev = prev;
prev->_next = newnode;
++_size;
return newnode;
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
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;
}
size_t size() const
{
return _size;
}
bool empty() const
{
return _size == 0;
}
private:
Node* _head;
size_t _size;
};
struct AA
{
int _a1 = 1;
int _a2 = 1;
};
// 按需实例化
// T* const ptr1
// const T* ptr2
template<class Container>
void print_container(const Container& con)
{
// const iterator -> 迭代器本身不能修改
// const_iterator -> 指向内容不能修改
typename Container::const_iterator it = con.begin();
//auto it = con.begin();
while (it != con.end())
{
//*it += 10;
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : con)
{
cout << e << " ";
}
cout << endl;
}
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
*it += 10;
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
print_container(lt);
list<AA> lta;
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
list<AA>::iterator ita = lta.begin();
while (ita != lta.end())
{
//cout << (*ita)._a1 << ":" << (*ita)._a2 << endl;
// 特殊处理,本来应该是两个->才合理,为了可读性,省略了一个->
cout << ita->_a1 << ":" << ita->_a2 << endl;
cout << ita.operator->()->_a1 << ":" << ita.operator->()->_a2 << endl;
++ita;
}
cout << endl;
}
void test_list2()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
// insert以后迭代器不失效
list<int>::iterator it = lt.begin();
lt.insert(it, 10);
*it += 100;
print_container(lt);
// erase以后迭代器失效
// 删除所有的偶数
it = lt.begin();
while (it != lt.end())
{
if (*it % 2 == 0)
{
it = lt.erase(it);
}
else
{
++it;
}
}
print_container(lt);
}
void test_list3()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
list<int> lt2(lt1);
print_container(lt1);
print_container(lt2);
list<int> lt3;
lt3.push_back(10);
lt3.push_back(20);
lt3.push_back(30);
lt3.push_back(40);
lt1 = lt3;
print_container(lt1);
print_container(lt3);
}
void func(const list<int>& lt)
{
print_container(lt);
}
void test_list4()
{
// 直接构造
list<int> lt0({ 1,2,3,4,5,6 });
// 隐式类型转换
list<int> lt1 = { 1,2,3,4,5,6,7,8 };
const list<int>& lt3 = { 1,2,3,4,5,6,7,8 };
func(lt0);
func({ 1,2,3,4,5,6 });
print_container(lt1);
//auto il = { 10, 20, 30 };
/* initializer_list<int> il = { 10, 20, 30 };
cout << typeid(il).name() << endl;
cout << sizeof(il) << endl;*/
}
}
Listtest.cpp
cpp
#include<iostream>
#include<algorithm>
#include<list>
#include<vector>
using namespace std;
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
/*it = lt.begin();
lt.erase(it + 3);*/
// 不支持,要求随机迭代器
//sort(lt.begin(), lt.end());
string s("dadawdfadsa");
cout << s << endl;
sort(s.begin(), s.end());
cout << s << endl;
}
struct A
{
public:
A(int a1 = 1, int a2 = 1)
:_a1(a1)
, _a2(a2)
{
cout << "A(int a1 = 1, int a2 = 1)" << endl;
}
A(const A& aa)
:_a1(aa._a1)
, _a2(aa._a2)
{
cout << "A(const A& aa)" << endl;
}
int _a1;
int _a2;
};
void test_list2()
{
/*list<int> lt;
lt.push_back(1);
lt.emplace_back(2);
lt.emplace_back(3);
lt.emplace_back(4);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;*/
list<A> lt;
A aa1(1, 1);
lt.push_back(aa1);
lt.push_back(A(2, 2));
//lt.push_back(3, 3);
lt.emplace_back(aa1);
lt.emplace_back(A(2, 2));
cout << endl;
// 支持直接传构造A对象的参数emplace_back
lt.emplace_back(3, 3);
}
void test_list3()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
auto it = lt.begin();
int k = 3;
while (k--)
{
++it;
}
lt.insert(it, 30);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
int x = 0;
cin >> x;
it = find(lt.begin(), lt.end(), x);
if (it != lt.end())
{
lt.erase(it);
}
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
void test_list4()
{
list<int> lt;
lt.push_back(1);
lt.push_back(20);
lt.push_back(3);
lt.push_back(5);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
// 升序
// lt.sort();
// 降序 - 仿函数
// less<int> ls;
// greater<int> gt;
// lt.sort(gt);
lt.sort(greater<int>());
// lt.reverse();
//reverse(lt.begin(), lt.end());
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
std::list<double> first, second;
first.push_back(3.1);
first.push_back(2.2);
first.push_back(2.9);
second.push_back(3.7);
second.push_back(7.1);
second.push_back(1.4);
first.sort();
second.sort();
first.merge(second);
}
void test_list5()
{
list<int> lt;
lt.push_back(1);
lt.push_back(20);
lt.push_back(3);
lt.push_back(5);
lt.push_back(5);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
lt.sort();
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.unique();
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
void test_list6()
{
// 一个链表节点转移给另一个链表
std::list<int> mylist1, mylist2;
std::list<int>::iterator it;
// set some initial values:
for (int i = 1; i <= 4; ++i)
mylist1.push_back(i); // mylist1: 1 2 3 4
for (int i = 1; i <= 3; ++i)
mylist2.push_back(i * 10); // mylist2: 10 20 30
it = mylist1.begin();
++it; // points to 2
mylist1.splice(it, mylist2); // mylist1: 1 10 20 30 2 3 4
// mylist2 (empty)
// "it" still points to 2 (the 5th element
// 调整当前链表节点的顺序
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
int x = 0;
cin >> x;
it = find(lt.begin(), lt.end(), x);
if (it != lt.end())
{
//lt.splice(lt.begin(), lt, it);
lt.splice(lt.begin(), lt, it, lt.end());
}
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
void test_op1()
{
srand(time(0));
const int N = 1000000;
list<int> lt1;
vector<int> v;
for (int i = 0; i < N; ++i)
{
auto e = rand() + i;
lt1.push_back(e);
v.push_back(e);
}
int begin1 = clock();
// 排序
sort(v.begin(), v.end());
int end1 = clock();
int begin2 = clock();
lt1.sort();
int end2 = clock();
printf("vector sort:%d\n", end1 - begin1);
printf("list sort:%d\n", end2 - begin2);
}
void test_op2()
{
srand(time(0));
const int N = 1000000;
list<int> lt1;
list<int> lt2;
for (int i = 0; i < N; ++i)
{
auto e = rand() + i;
lt1.push_back(e);
lt2.push_back(e);
}
int begin1 = clock();
// 拷贝vector
vector<int> v(lt2.begin(), lt2.end());
// 排序
sort(v.begin(), v.end());
// 拷贝回lt2
lt2.assign(v.begin(), v.end());
int end1 = clock();
int begin2 = clock();
lt1.sort();
int end2 = clock();
printf("list copy vector sort copy list sort:%d\n", end1 - begin1);
printf("list sort:%d\n", end2 - begin2);
}
#include"List.h"
// 10:35
int main()
{
//test_list6();
//test_op2();
bit::test_list4();
return 0;
}