【c++进阶[五]】list相关接口介绍及list和vector的对比

💓博主CSDN主页::Am心若依旧💓

⏩专栏分类c++从入门到精通

🚚代码仓库:青酒余成🚚

🌹关注我🫵带你学习更多c++

🔝🔝

1.前言

本章重点

本章重点讲解list的接口函数的熟悉,并且讲解list迭代器失效的特性和迭代器的功能分类以及算法库函数中谁能用谁不能用,最后比较一下list和vector的优势和劣势

2.list的介绍

对上诉大致理解如下:

1.list是一种可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。

2.list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立结点当中,在结点中通过指针指向其前一个元素和后一个元素。

3.list与forward_list非常相似,最主要的不同在于forward_list是单链表,只能进行单方向迭代。

与其他容器相比,list通常在任意位置进行插入、删除元素的执行效率更高。

4.list和forward_list最大的缺陷是不支持在任意位置的随机访问,其次,list还需要一些额外的空间,以保存每个结点之间的关联信息(对于存储的类型较小元素来说这可能是一个重要的因素)。

更加具体的学习可以参考如下链接:list - C++ Reference (cplusplus.com)

3.list的使用

构造函数

在这里主要介绍四种构造函数

1.是无参构造,一眼就能看懂。

2.构造n个值为val的双向链表。

3.迭代器区间构造。

4.用一个初始值构造,也称之为拷贝构造函数。

上述四个构造的方法都是以int为类型,也可以使用其他的类型。具体视情况而

cpp 复制代码
list<int> l1;//无参构造
list<int> l2(10,5);//用10个5初始化链表

vector<int> vv{1,2,3,4,5,6};
list<int> l3(vv.begin(),vv.end());//用迭代器区间初始化

//拷贝构造函数
list<char> l4('a');//用一个字符来初始化

迭代器

list底层实现迭代器的不是指针,但是可以把他当成指针来理解。list中的迭代器在底层重新封装了*,->,++,--,!=,==这几个函数。这样我们就能够像vector那样的把迭代器当成指针来使用了

这四个函数在前面讲解string和vector的时候都讲过,就不过多的叙述了。

但是一定要搞清楚这几个函数分别指向什么位置,下面用一张图帮助大家理解

begin()和rend()指向的位置相同

end()和rbegin()指向的位置相同

迭代器来遍历链表

cpp 复制代码
vector<int> vv{1,3,5,7,9,11,13,15};
list<int> l(vv.begin(),vv.end());

auto it = l.begin();
while(it!=l.end())
{
	cout << *it<< " ";
	it++;
}

值得注意的是:这里用的是!=而没有使用等于,而在vector中使用的是<。这是为什么呢?

这是因为在list中元素都是不连续的,如果用<的话非常有可能是错误的。而在vector中由于空间都是连续的,地址前面的一定是比后面的小的。

同时还要注意的是:这里没有使用[]来进行读取元素的值,而是使用了解引用,那是因为list不能支持随机访问,只支持解引用来读取元素。

与容量相关的函数

这四个都是常见的函数了。resize在list中使用的不多,就不拿出来介绍了,有想法的可以参考上面提供的网站自己阅读。

增删查改相关函数

这几个函数一看就知道什么意思了,就不多叙述

insert插入重点讲解

使用insert函数时可以使用在某个位置插入某个值,也可以在某个位置插入n个值,也可以在某个位置前面插入一段区间大小的值

cpp 复制代码
#include <iostream>
#include <algorithm>
#include <vector>
#include <list>
using namespace std;

int main()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	list<int>::iterator pos = find(lt.begin(), lt.end(), 2);
	lt.insert(pos, 9); //在2的位置插入9
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl; //1 9 2 3
	pos = find(lt.begin(), lt.end(), 3);
	lt.insert(pos, 2, 8); //在3的位置插入2个8
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl; //1 9 2 8 8 3
	vector<int> v(2, 7);
	pos = find(lt.begin(), lt.end(), 1);
	lt.insert(pos, v.begin(), v.end()); //在1的位置插入2个7
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl; //7 7 1 9 2 8 8 3
	return 0;
}

注: find函数是头文件"algorithm"当中的一个函数,该函数在指定迭代器区间寻找指定值的位置,并返回该位置的迭代器

erase函数

list当中的erase函数支持两种删除方式:

  1. 删除指定迭代器位置的元素。
  2. 删除指定迭代器区间(左闭右开)的所有元素。
cpp 复制代码
#include <iostream>
#include <algorithm>
#include <vector>
#include <list>
using namespace std;

int main()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	list<int>::iterator pos = find(lt.begin(), lt.end(), 2);
	lt.erase(pos); //删除2
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl; //1 3 4 5
	pos = find(lt.begin(), lt.end(), 4);
	lt.erase(pos, lt.end()); //删除4及其之后的元素
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl; //1 3
	return 0;
}

clear函数

清空双向链表中除了头结点以外的所有节点

cpp 复制代码
vector<int> vv{1,5,10,15,20,100,120};
list<int> ll(vv.begin(),vv.end());
auto pos = find(ll.begin(),ll.end(),20);
ll.insert(pos,250);
ll.erase(ll.begin()+2);
ll.clear();

clear之后就会把所有的值全部清空。(除了头结点之外,头结点可以把他理解为哨兵卫,里面的值无效)。

4.关于迭代器的问题

由于list的底层是双向带头循环链表,所以插入操作时,pos指向的节点的位置始终都是一个位置,它们只改变链接关系,并不连续,所以插入操作,并不会导致list迭代器失效。

list迭代器失效只在erase删除时才会发生

erase删除的位置在空间上是唯一的,这个位置的数据被删除后,只是改变了数据的链接关系,然而此位置已经不在原先的链表中了,再次使用会出错!

例如你删除了最后一个元素位置的值,你再对最后一个元素的迭代器位置进行解引用,那么就会报错。这就是迭代器失效的问题

因此在设计删除erase函数的时候,每当删除一个位置的时候,他都会给你返回一个有效元素的位置。

举个例子,你要把所有的偶数全部删除。按照如下的方法写就不会出现问题了。

cpp 复制代码
list<int> ll{1,2,3,4,5,6,7,8};
auto it=ll.begin();
while(it!=ll.end())
{
	if((*it)%2==0)
	{
		it=erase(it);
	}
	else
	{
		it++;
	}
}

5.各种迭代器的介绍

迭代器从功能可以大体分为三种:

  1. 正向迭代器: forward_iterator
  2. 双向迭代器: bidirectional_iterator
  3. 随机迭代器: random_iterator

他们分别支持++,++、--,++和--以及+和-

常见的容器以及它们的迭代器类型:

结论:

  1. 若函数模板给的随机迭代器
    则只能传有随机迭代器的容器

  2. 若函数模板给的双向迭代器
    则可以传有随机或者双向迭代器的容器

  3. 若函数模板给的单向迭代器
    则三种迭代器都可以传进来!

可以看出迭代器的优先级是:单向,双向,随机。分别依次向前兼容

6.vector与list的对比

好啦,list的相关接口就介绍到这里了,本篇文章主要介绍的是博主认为比较重要的接口,其他接口可以去下面网站自行阅读。

list::insert - C++ Reference (cplusplus.com)

下期预告:list的模拟实现

相关推荐
EterNity_TiMe_3 分钟前
【论文复现】(CLIP)文本也能和图像配对
python·学习·算法·性能优化·数据分析·clip
长弓聊编程5 分钟前
Linux系统使用valgrind分析C++程序内存资源使用情况
linux·c++
陌小呆^O^9 分钟前
Cmakelist.txt之win-c-udp-client
c语言·开发语言·udp
cherub.12 分钟前
深入解析信号量:定义与环形队列生产消费模型剖析
linux·c++
机器学习之心14 分钟前
一区北方苍鹰算法优化+创新改进Transformer!NGO-Transformer-LSTM多变量回归预测
算法·lstm·transformer·北方苍鹰算法优化·多变量回归预测·ngo-transformer
I_Am_Me_24 分钟前
【JavaEE进阶】 JavaScript
开发语言·javascript·ecmascript
yyt_cdeyyds25 分钟前
FIFO和LRU算法实现操作系统中主存管理
算法
暮色_年华26 分钟前
Modern Effective C++item 9:优先考虑别名声明而非typedef
c++
重生之我是数学王子34 分钟前
QT基础 编码问题 定时器 事件 绘图事件 keyPressEvent QT5.12.3环境 C++实现
开发语言·c++·qt
Ai 编码助手36 分钟前
使用php和Xunsearch提升音乐网站的歌曲搜索效果
开发语言·php