目录
list简介
list的几个特性
接口函数
1.默认成员函数
2.迭代器相关函数
3.容量相关的函数
4.成员访问相关的函数
5.modify系列
6.operation系列
7.重载在全局的函数
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的几个特性
接口函数
1.默认成员函数
构造函数
构造函数主要是由三种构成:1)n个val 2)默认构造函数(全缺省) 3)用迭代器传参
list<int> lt(10, 0);
list<int> lt2;
list<int> lt3(lt.begin(), lt.end());
拷贝构造
用于构造一个和原对象一样的“个体”
list<int> lt(10, 0);
list<int> lt2(lt);
赋值重载
起到赋值的作用
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()
迭代器的行为与指针相似,但不能说迭代器就等价于指针。迭代器可以用来遍历容器。
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和迭代器区间。
#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
#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
#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的空间和内容。
#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;
}
执行结果:
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
的指定位置之前。
#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
的指定位置。
#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)整个容器移动
#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函数
用来移除特定的元素
示例代码
#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函数
用来完成排序工作。默认排序是升序
#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必须是有序的。
示例代码:
#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<
来比较元素。
示例:
#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函数