目录
[1. list的基本使用](#1. list的基本使用)
[1.1 list构造函数](#1.1 list构造函数)
[1.2 list迭代器](#1.2 list迭代器)
[1.3 list capacity](#1.3 list capacity)
[1.4 list元素访问](#1.4 list元素访问)
[1.5 list 修改操作](#1.5 list 修改操作)
[2. list失效迭代器问题](#2. list失效迭代器问题)
[3. list使用算法库函数问题](#3. list使用算法库函数问题)
前言
list(链表)在C++中非常重要的容器,它本质是一个带头双向循环链表。本文的主要内容是介绍list的基本使用以及对list迭代器问题。
1. list的基本使用
学习一种新的容器,首先我们需要先了解它的接口;
在C++的STL标准库中,list是一个带头双向循环链表;
list和vector不同,它不支持[ ]下标随机访问;
list是一个双向链表,它的元素在内存中不是连续存储的,因此并不需要像vector一样预留容量或者调整容量
1.1 list构造函数
C++98中默认有4中构造函数:
有四种构造方法:
- 默认无参构造
cpp
list<int> lt1;//无参构造
- n个val构造
cpp
list<int> lt2(10,0); // 10个0初始化
- 迭代器区间构造
cpp
vector<int> v{1,2,3,4,5};
list<int> lt3(v.begin(),v.end());
- 拷贝构造
cpp
list<int> lt4(10,0);
list<int> lt5(lt4);
除此之外还有比较常用的初始值设定项列表构造;
- initializer list构造(初始值设定项列表构造)
cpp
list<int> lt = {1, 2, 3, 4, 5};
1.2 list迭代器
list迭代器本质并不是指针,为了方便理解,可暂时将迭代器理解成一个指针,该指针指向list中的某个节点;
这4个迭代器接口大家都很熟悉,但需要注意的是list中它们的指向;
- list迭代器遍历
cpp
list<int> lt = { 1, 2, 3, 4, 5 };
list<int>::iterator it = lt.begin();
while (lt.end() != it)
{
cout << *it << ' ';
it++;
}
- 范围for遍历
cpp
for (auto e : lt)
{
cout << e << ' ';
}
范围for循环确实是本质上是迭代器遍历,在执行时会被替换成迭代器;
注意:
在使用list迭代器时,我们往往使用的是it!=l.end(),而不是it<l.end(),
因为list空间并不连续,每个节点的地址都不同(无法确认大小关系),在string和vector中,空间是连续的,可以使用;
1.3 list capacity
使用操作较为简单,使用list对象即可调用:
cpp
lt.empty();
lt.size();
1.4 list元素访问
使用操作较为简单,使用list对象即可调用:
cpp
lt.front();
lt.back();
1.5 list 修改操作
这里我们可以参考cplusplus上边的接口;
这里我就介绍一些比较常用的,其他的接口使用可以去查阅;
头插、头删、尾插、尾删我就不再详细介绍;注意这些操作只能插入删除一个元素!
assign
接口功能是新元素替换列表中的内容,它的功能我们完全可以使用list的构造函数替代,这里不再详细介绍;
insert
- 使用单个值插入
cpp
list<int> mylist = {1, 2, 3, 4, 5};
auto it = mylist.begin();
it++; // 将迭代器it移动到第二个位置
mylist.insert(it, 10); // 在it位置插入值为10的元素
- 使用相同值填充插入
cpp
list<int> mylist = {1, 2, 3, 4, 5};
auto it = mylist.begin();
it++; // 将迭代器it移动到第二个位置
mylist.insert(it, 3, 10); // 在it位置插入3个值为10的元素
- 迭代器区间插入
cpp
list<int> mylist = {1, 2, 3, 4, 5};
auto it = mylist.begin();
it++; // 将迭代器it移动到第二个位置
vector<int> vec = {7, 8, 9};
mylist.insert(it, vec.begin(), vec.end()); // 在it位置插入vector中的元素
erase
- 删除单个元素
cpp
list<int> mylist = {1, 2, 3, 4, 5};
auto it = mylist.begin();
it++; // 将迭代器it移动到第二个位置
mylist.erase(it); // 删除it位置的元素
- 删除范围内的元素:
cpp
list<int> mylist = {1, 2, 3, 4, 5};
auto it1 = mylist.begin();
auto it2 = mylist.begin();
advance(it1, 1); // 将迭代器it1移动到第二个位置
advance(it2, 3); // 将迭代器it2移动到第四个位置
mylist.erase(it1, it2); // 删除从it1到it2范围内的元素
注意list迭代器不支持使用+或-的操作来进行移动 ;所以这里使用advance
函数来移动迭代器;
swap
swap
函数交换两个list
的内容:
cpp
list<int> list1 = {1, 2, 3};
list<int> list2 = {4, 5, 6};
list1.swap(list2); // 交换list1和list2的内容
resize
resize
函数调整list
的大小:
cpp
list<int> mylist = {1, 2, 3, 4, 5};
mylist.resize(3); // 将mylist的大小调整为3,删除多余的元素
mylist.resize(5, 100); // 将mylist的大小调整为5,多出的元素用100填充
list没有reserve接口
clear
clear
函数清空list
的内容:
cpp
list<int> mylist = {1, 2, 3, 4, 5};
mylist.clear(); // 清空mylist的内容,mylist现在为空
2. list失效迭代器问题
前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,list的底层结构为带头结点的双向循环链表,在list中进行插入时是不会导致list的迭代
器失效;
list迭代器失效只会在erase删除操作时发生;
删除节点后,节点的数据就会被释放,再使用迭代器访问这个节点,此时节点已经失效;
前边我们也提到过,可以通过返回值的方法来避免迭代器的非法访问;
节点被删除后,自动返回下一个位置的迭代器;
3. list使用算法库函数问题
如果要对链表进行排序呢?
其实list自己封装的有sort接口,list链表的sort函数使用的是归并排序算法;那它为什么不用算法库里的sort;
原因很简单,其实就是迭代器不兼容;
算法库里的sort它的参数是RandomAccessIterator,也就是随机访问迭代器;
这里就要补充一点知识,从不同容器的迭代器出发,容器不同迭代器实现也不同,那么迭代器的特性也不同;
根据迭代器的特性大致可以先分为三类:
- 单向迭代器(Forward Iterator)
- 双向迭代器(Bidirectional Iterator)
- 随机访问迭代器(Random Access Iterator)
单向迭代器: 迭代器仅++操作,单链表
双向迭代器:迭代器支持++、--操作,双向链表
随机访问迭代器:迭代器支持++/--/+/-操作,vector、string
怎么分辨是否可以使用算法接口?
单向迭代支持++操作,满足所有迭代器的操作,三种类型的迭代器类型接口函数都可以使用;
双向迭代器支持++/--操作,满足随机访问迭代器的操作,那它就可以使用参数类型为双向迭代器类型的接口函数,参数类型为随机访问迭代器类型的接口;
它就有向上兼容的特性;
总结:
- 单向迭代器可以使用三种迭代器类型的接口;
- 双向迭代器可以使用:双向迭代器、随机迭代器类型的接口;
- 随机迭代器仅可以使用随机迭代器类型的接口;
虽然list自己实现的有sort接口,但还是不建议使用,它的效率非常的低;
它甚至还没有这样操作的速度快:
将list数据拷贝到vector中排序,然后将排序好的数据拷贝回list;
总结
本文主要介绍的是list的一些基本使用,以及list迭代器问题和list使用算法库函数的问题。list的接口函数不多,但是在使用算法库函数时需要注意迭代器的类型;以上便是本期的全部内容,希望对你有所帮助,感谢阅读!