目录
list简介
Lists are sequence containers that allow constant time insert and erase operations anywhere within the sequence, and iteration in both directions.
在C语言中,我们已经学过了一些基础的带哨兵位的双向链表,但是链表的实现比较"呆板",因此C++中就出现了list。list是一个模板类,功能就类似双向链表。
list的几个特性
- list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
- list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向
其前一个元素和后一个元素。 - list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高
效。 - 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率
更好。 - 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list
的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间
开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这
可能是一个重要的因素)
下面我们就从list的几个接口函数来探索这些特点。
接口函数
1.默认成员函数

构造函数

构造函数主要是由三种构成:1)n个val 2)默认构造函数(全缺省) 3)用迭代器传参
cpp
list<int> lt(10, 0);
list<int> lt2;
list<int> lt3(lt.begin(), lt.end());
拷贝构造
用于构造一个和原对象一样的"个体"
cpp
list<int> lt(10, 0);
list<int> lt2(lt);
赋值重载
起到赋值的作用
cpp
int main()
{
list<int> lt(10, 0);
list<int> lt2(10, 1);
list<int> lt3(lt);
lt3 = lt2;
for (auto& ch : lt3)
{
cout << ch << " ";
}
cout << endl;
return 0;
}

2.迭代器相关函数
主要是begin()、end()、rbegin()、rend()

迭代器的行为与指针相似,但不能说迭代器就等价于指针。迭代器可以用来遍历容器。
cpp
int main()
{
list<int> lt(10, 0);
list<int> lt2(10, 1);
list<int> lt3(lt);
lt3 = lt2;
auto it = lt3.begin();
while (it != lt3.end())
{
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
list的迭代器是双向迭代器,支持++、--、*(解引用)操作
rbegin和rend则是反向迭代器,可以将数据反向遍历。cbegin系列则是const修饰的迭代器。
但是cbegin系列实际上begin系列已经完成了const迭代器的重载
3.容量相关的函数
这部分函数主要是与容量相关的。但是不同于string和vector,他不含有reserve、capacity函数。因为list是由一个个节点构成,所以不存在扩容的概念。当需要额外的空间时,只需要额外获取节点即可。

1)empty
用来检测是否是空container

2)size
用来监视容量

3)max_size
用来检测能允许的最大空间。但是实践中意义不大,毕竟也没有工程会傻到用完所有空间。

4.成员访问相关的函数

front()函数返回第一个有效元素。
back()返回最后一个有效元素。
5.modify系列
这个系列主要是与增删查改有关。

1)assign函数
清除原先的空间、内容,并且重新分配内容,并给予适配的空间大小。

可以使用迭代器传参,也可以使用n个val传参。
2)头插、头删、尾插、尾删系列
主要是应用push_back 和 pop_back组合、push_front和pop_front组合。
在插入时,只需要给出对应的数据。在删除时不需要传参。
3)insert
insert可以支持在任意位置插入数据,因此在传入参数时就需要传入迭代器用来指明对应的位置。

传入迭代器的位置之后,可以传入val、n个val和迭代器区间。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> my_list = {1, 2, 3, 4};
auto it = my_list.begin(); // 获取开始迭代器
++it; // 移动迭代器到第二个元素
my_list.insert(it, 'a'); // 在第二个元素之前插入'a'
for (const auto& elem : my_list) {
std::cout << elem << ' ';
}
std::cout << std::endl;
return 0;
}
// 输出: 1 a 2 3 4
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> my_list = {1, 2, 3, 4};
auto it = my_list.end(); // 获取结束迭代器
my_list.insert(it, 3, 'b'); // 在列表末尾插入3个'b'
for (const auto& elem : my_list) {
std::cout << elem << ' ';
}
std::cout << std::endl;
return 0;
}
// 输出: 1 2 3 4 b b b
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> my_list = {1, 2, 3, 4};
std::list<int> to_insert = {5, 6, 7};
auto it = my_list.begin(); // 获取开始迭代器
++it; // 移动迭代器到第二个元素
my_list.insert(it, to_insert.begin(), to_insert.end()); // 在第二个元素之前插入另一个列表的所有元素
for (const auto& elem : my_list) {
std::cout << elem << ' ';
}
std::cout << std::endl;
return 0;
}
// 输出: 1 5 6 7 2 3 4
4)erase函数
用来删除数据。可以删除某个数据,也可以删除某块区间的数据。

返回删除数据的下一个迭代器。
5)swap函数

用来交换两个list的空间和内容。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> list1 = {1, 2, 3, 4};
std::list<int> list2 = {5, 6, 7, 8};
// 输出交换前的列表
std::cout << "Before swap:" << std::endl;
std::cout << "list1: ";
for (const auto& elem : list1) {
std::cout << elem << ' ';
}
std::cout << std::endl;
std::cout << "list2: ";
for (const auto& elem : list2) {
std::cout << elem << ' ';
}
std::cout << std::endl;
// 交换两个列表的内容和空间
list1.swap(list2);
// 输出交换后的列表
std::cout << "After swap:" << std::endl;
std::cout << "list1: ";
for (const auto& elem : list1) {
std::cout << elem << ' ';
}
std::cout << std::endl;
std::cout << "list2: ";
for (const auto& elem : list2) {
std::cout << elem << ' ';
}
std::cout << std::endl;
return 0;
}
执行结果:
cpp
Before swap:
list1: 1 2 3 4
list2: 5 6 7 8
After swap:
list1: 5 6 7 8
list2: 1 2 3 4
6)resize
用来调整size的大小。
当n<size时,会减小size到n;
当n>size时,会增大到n,并且将后续的内容初始化为val。

7)clear
用来清除空间和内容。list是带哨兵位的双向链表,clear()函数并不会清除掉哨兵位。

6.operation系列

1)splice函数
这是splice函数的介绍。介绍中写道将一个list中的元素转移到另一个链表中。注意:该转移不是单纯数据的转移,而是体现在节点的转移。


可以挪动整个list,可以挪动list的某个元素,也可以挪动某一段区间。
Transfers elements from x into the container, inserting them at position.
1.单元素移动
将单个元素从另一个 list
移动到当前 list
的指定位置之前。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> list1 = {1, 2, 3, 4};
std::list<int> list2 = {5, 6, 7, 8};
// 将 list2 中的第一个元素(5)移动到 list1 的第三个位置之前
auto it = list1.begin(); // 获取 list1 的开始迭代器
std::advance(it, 2); // 将迭代器向前移动两位,指向第三个位置
list1.splice(it, list2, list2.begin()); // 移动操作
// 输出结果
for (int elem : list1) std::cout << elem << ' ';
std::cout << std::endl;
for (int elem : list2) std::cout << elem << ' ';
std::cout << std::endl;
return 0;
}
list2.begin()
是源迭代器,在 splice
操作后它将失效,因为它指向的元素已经被移动到 list1
中。it
是目标迭代器,它在操作后仍然有效,并且指向移动后的元素 3。
输出结果:
1 2 5 3 4
6 7 8
2)范围移动
将另一个 list
中的一个范围的所有元素移动到当前 list
的指定位置。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> list1 = {1, 2, 3, 4};
std::list<int> list2 = {5, 6, 7, 8};
// 将 list2 中的前两个元素移动到 list1 的末尾
list1.splice(list1.end(), list2, list2.begin(), ++std::list<int>::iterator(list2.begin()));
// 输出结果
for (int elem : list1) std::cout << elem << ' ';
std::cout << std::endl;
for (int elem : list2) std::cout << elem << ' ';
std::cout << std::endl;
return 0;
}
代码中,迭代器iterator是一个对象,因此采用了匿名对象传参的方式。
输出结果:
1 2 3 4 5 6
7 8
3)整个容器移动
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> list1 = {1, 2, 3, 4};
std::list<int> list2 = {5, 6, 7, 8};
// 将整个 list2 移动到 list1 的末尾
list1.splice(list1.end(), list2);
// 输出结果
for (int elem : list1) std::cout << elem << ' ';
std::cout << std::endl;
// list2 现在为空
for (int elem : list2) std::cout << elem << ' ';
std::cout << std::endl;
return 0;
}
在这个例子中,list2
的所有迭代器在 splice
操作后都将失效,因为整个 list2
的内容都被移动到了 list1
中。目标迭代器 list1.end()
在操作后仍然有效,并且指向新插入元素之后的元素。
输出:
1 2 3 4 5 6 7 8
总结:
在 splice
操作后,如果是从源 list
移动单个元素或一个范围 ,则源 list
中被移动元素之前的迭代器仍然有效 ,而被移动元素及其之后 的迭代器失效 。目标 list
的迭代器在操作后仍然有效 。如果整个 源 list
被移动,则源 list
的所有迭代器失效。
2)remove函数
用来移除特定的元素

示例代码
cpp
#include <iostream>
#include <list>
int main() {
// 创建一个list
std::list<int> lst = {1, 2, 3, 4, 2, 5, 2};
// 打印原始list
std::cout << "原始list: ";
for (int i : lst) {
std::cout << i << " ";
}
std::cout << std::endl;
// 移除所有值为2的元素
lst.remove(2);
// 打印修改后的list
std::cout << "修改后的list: ";
for (int i : lst) {
std::cout << i << " ";
}
std::cout << std::endl;
return 0;
}
原始list: 1 2 3 4 2 5 2
修改后的list: 1 3 4 5
使用remove时,不需要进行sort(排序)
3)sort函数
用来完成排序工作。默认排序是升序
cpp
#include <iostream>
#include <list>
#include <algorithm> // 用于sort函数
int main() {
// 创建一个list
std::list<int> lst = {4, 1, 3, 5, 2};
// 打印原始list
std::cout << "原始list: ";
for (int i : lst) {
std::cout << i << " ";
}
std::cout << std::endl;
// 使用sort函数对list进行排序
lst.sort();
// 打印排序后的list
std::cout << "排序后的list: ";
for (int i : lst) {
std::cout << i << " ";
}
std::cout << std::endl;
return 0;
}
输出:
原始list: 4 1 3 5 2
排序后的list: 1 2 3 4 5
需要注意的是,sort在list有内置函数接口,在算法库中也有函数接口。
list内置:

算法库:

对于list对象,默认的迭代器是bidirectional迭代器(双向迭代器)
对于算法库中的sort,需要传入random迭代器(随机迭代器)
就迭代器而言,在功能上有const迭代器、非const迭代器、正向、反向迭代器;在性质上有随机迭代器、双向迭代器、单向迭代器。
就迭代器的权限而言:随机>双向>单项
随机支持:++ / -- / + / - 操作 (vector、string 、deque)
双向支持:++ / -- (list 、红黑树(map和set))
单向支持: ++ (单链表、哈希表)
因此随机迭代器可以用算法库(algorithm)函数中的sort,也可以用list中的sort
但是双向迭代器没法将权限上升到算法库中的sort,只能使用bidirectional专属的sort
sort内部也是消耗比较大的接口函数,因此sort函数尽量少用。
4)unique函数
用来完成去重工作。但是也有前提,即list必须是有序的。
示例代码:
cpp
#include <iostream>
#include <list>
int main() {
// 创建一个list
std::list<int> lst = {1, 2, 2, 3, 4, 4, 4, 5, 5, 6};
// 打印原始list
std::cout << "原始list: ";
for (int i : lst) {
std::cout << i << " ";
}
std::cout << std::endl;
// 使用unique函数移除连续的重复元素
lst.unique();
// 打印修改后的list
std::cout << "修改后的list: ";
for (int i : lst) {
std::cout << i << " ";
}
std::cout << std::endl;
return 0;
}
输出:
原始list: 1 2 2 3 4 4 4 5 5 6
修改后的list: 1 2 3 4 5 6
5)merge函数
merge是合并的意思,用来合并两个list对象

在C++标准模板库(STL)中,list
容器的merge
成员函数用于将两个已排序的list
合并成一个新的已排序的list
。这个函数特别适用于list
,因为它可以高效地执行合并操作,而不需要额外的存储空间,因为它直接在现有的节点之间重新链接
以下是对merge
函数的几个关键点的说明:
-
两个
list
都必须已排序 :在调用merge
之前,两个list
必须根据相同的排序准则进行排序。 -
合并操作 :
merge
函数会将第二个list
(参数x
)中的所有元素合并到调用merge
的list
中。合并后的list
仍然保持已排序状态。 -
效率 :由于
list
的双向链表特性,合并操作是非常高效的。它不需要像数组或vector
那样进行大量的元素移动。 -
第二个
list
的状态 :在合并操作后,参数x
指定的list
变为空。 -
比较函数 :如果不提供比较函数,则使用默认的
operator<
来比较元素。
示例:
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> lst1 = {1, 3, 5, 7};
std::list<int> lst2 = {2, 4, 6, 8};
// 确保两个list都已排序
lst1.sort();
lst2.sort();
// 将lst2合并到lst1
lst1.merge(lst2);
// lst2现在应该是空的
std::cout << "lst2的大小: " << lst2.size() << std::endl;
// 打印合并后的lst1
std::cout << "合并后的lst1: ";
for (int i : lst1) {
std::cout << i << " ";
}
std::cout << std::endl;
return 0;
}
在这个例子中,我们首先创建了两个已排序的list
,lst1
和lst2
。然后我们使用merge
函数将lst2
合并到lst1
中。合并后,lst2
变为空,而lst1
包含了两个原始list
的所有元素,并且仍然保持排序顺序。
输出:
lst2的大小: 0
合并后的lst1: 1 2 3 4 5 6 7 8
如果是乱序,采用merge函数之后,导致合并后的list
也是乱序的。
std::list<int> lst1 = {5, 3, 1, 7};
std::list<int> lst2 = {8, 6, 4, 2};
如果我们直接合并这两个list
:合并后的lst1
将包含以下元素,且顺序是它们在原始list
中的顺序:
输出:
5, 3, 1, 7, 8, 6, 4, 2
为了得到一个排序后的list
,你需要在调用merge
函数之前分别对两个list
进行排序
6)reverse
用来逆置list

7.重载在全局的函数

第一部分是关系运算符的重载
第二部分是swap函数
