【STL】list的使用

news2024/9/21 22:16:52

系列文章

学习C++途中自然绕不过STL,在这个系列文章之中

我们讲了string的使用和string的模拟实现,以及vector的使用、vector的模拟实现。

感兴趣的可以翻翻看。


目录

系列文章

前言

默认成员函数

构造函数

拷贝构造

赋值重载

迭代器

容量查询

数据访问

数据修改

assign

头插头删尾插尾删

指定位置插入删除

迭代器失效

交换

其他操作

splice

remove

合并

去重

逆置

排序


前言

☘️list 是 STL 中又一重要容器,而 list 其实就是对链表的封装。一提到链表,相信大家第一反应就是单链表,而单链表的缺点又是显而易见的:尾插过于麻烦、只能单向访问等等。但实际上list中封装的链表并非普通的单链表,而是之前讲过的带头双向循环链表。

☘️其能够优化除随机访问之外,单链表的大部分缺点,而且再次经过封装因此使用起来十分地方便。

☘️这次主要讲讲 list 该如何使用,更多实现的细节等到模拟实现的时候再讲。

默认成员函数

☘️使用list前记得要带头文件<list>,否则就无法使用list了。 

构造函数

☘️跟以前学习的容器一样,list 的构造函数还是老三样。

  • 创建空链表
  • 创建n个值为val的节点的链表
  • 以一个迭代器区间进行构造

 ☘️根据不同的写法初始化后的结果也有所不同。

int main()
{
	list<int> l1;                 //空表
	list<int> l2(5, 3);           //33333
	int arr[] = { 1,2,3,4,5,6 };
	list<int> l3(arr, arr + 6);   //123456
	return 0;
}

拷贝构造

☘️参数同样为 list 的自然就是拷贝构造了,该拷贝为深拷贝,节点内部的值都相同,但节点的地址不同。

☘️以一个已经初始化过的 list 来初始化一个新的 list,最后打印出来的结果都应是相同的。

int main()
{
	list<int> l1(5, 3);
	list<int> l2(l1); 
	//list<int> l2 = l1;   //也可以这样写
    for (auto i : l2)
	{
		cout << i << " ";
	}
	cout << endl;
	return 0;
}

 

赋值重载

☘️赋值重载则是用于两个已经存在的 list 之间进行赋值的操作,若其中一个还未初始化,那么实际上调用的是拷贝构造。

☘️通过这串代码,我们便可以清晰地看到赋值前后 list 的变化。

int main()
{
	list<int> l1(5, 3);
	list<int> l2(3, 2);
	cout << "原来的l1" << ": ";
	for (auto i : l1) cout << i << " ";
	cout << endl;
	l1 = l2;
	cout << "现在的l1" << ": ";
	for (auto i : l1) cout << i << " ";
	cout << endl;
	return 0;
}

 

迭代器

☘️虽然我们平时只是讲迭代器,但是根据其能力的不同,迭代器也被分作三类。

  • 单向迭代器:只能++   例如 unorder_map 的迭代器
  • 双向迭代器:可以++还能--    例如 list 的迭代器
  • 随机迭代器:++、--、+、-   例如 vector 的迭代器

☘️之所以是双向迭代器,得益于 list 的底层结构是一个带头双向循环链表,使得每一个节点都能访问自己的上下两个节点

☘️通过调用 begin 这个函数,我们能拿到指向 list 第一个有效数据的迭代器

☘️再调用 end 就能拿到标志 list 结尾的迭代器,便有了边界,就可以完成对 list 的遍历了。

      

 ☘️先构建一个 list,之后拿到 begin 的迭代器,直到 end 前打印当前节点的值。注意: 这个区间是左闭右开的。

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> l(arr, arr + 9);
	list<int>::iterator it = l.begin();
    //auto it = l.begin();   //还可以这样写
	while (it != l.end())    //[begin,end)
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	return 0;
}

☘️反向迭代器的使用也是类似,只不过调用的函数换成了 rbegin 和 rend 而已。

☘️值得注意的一点是,反向迭代器的迭代使用的也是 ++。但迭代区间一样是 [rbegin,rend)

   

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> l(arr, arr + 9);
	list<int>::reverse_iterator it = l.rbegin();  //类型名改变
	while (it != l.rend())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	return 0;
}

容量查询

☘️由于链表并没有扩容的概念,因此并没有 reserve 这个函数,而 resize 则是作为修改的函数,也不经常使用。

☘️最常用的莫过于上面这两个函数了,empty 用于判断链表是否为空,而 size 用于查看当前有效节点的数量

  

☘️我们可以写一段代码简单使用一下。

int main()
{
	list<int> l1(3, 6);
	list<int> l2;
	if (!l1.empty())    //l1不为空,打印大小为3
		cout << "l1 size: " << l1.size() << endl;
	if (!l2.empty())    //l2为空,不打印
		cout << "l2 size: " << l2.size() << endl;
	return 0;
}

 

数据访问

☘️在 list 中,我们可以使用 front 和 back 访问链表的首尾元素

  

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> l(arr, arr + 9);
	cout << "front is: " << l.front() << endl;
	cout << "back is: " << l.back() << endl;
	return 0;
}

 

数据修改

assign

☘️就像我们前面构造函数中的那样,可以讲内容转换为 n 个 val 或是一个迭代器区间。

☘️但是,以前的内容会被清除

int main()
{
	list<int> l(2, 3);    // 33
	l.assign(5, 6);       // 66666
	for (auto it : l) cout << it << " ";
	cout << endl;
	vector<int> v = { 2,3,5,4,2 };
	l.assign(v.begin(), v.end());   // 23542
	for (auto it : l) cout << it << " ";
	cout << endl;
	return 0;
}

 

头插头删尾插尾删

☘️通过 push_front push_back 可以做到头插和尾插,而 pop_frontpop_back 可以做到头删和尾删。

☘️只要记住 front 是前 back 是后push 代表插入 pop 代表删除,之后怎么排列组合都不会记混了。

int main()
{
	vector<int> v = { 2,3,5,4,2 };
	list<int> l(v.begin(), v.end());
	for (auto it : l) cout << it << " ";  //原链表
	cout << endl;
	l.push_front(10);
	l.push_back(20);
	for (auto it : l) cout << it << " ";  //插入后的链表
	cout << endl;
	l.pop_front();
	l.pop_back();
	for (auto it : l) cout << it << " ";  //删除后的链表
	cout << endl;
	return 0;
}

 

指定位置插入删除

☘️我们使用 insert 和 erase 可以做到指定位置插入和删除,但是我们发现 list 中并没有 find 这个函数。若是每次从 begin 获取迭代器还要++,那未免过于麻烦了。

  

☘️没关系,算法库里有一个find函数能够满足我们的使用需求,使用前记得带上头文件<algorithm>。

☘️这样我们就能任意地插入删除了。

int main()
{
	vector<int> v = { 2,3,5,4,2 };
	list<int> l(v.begin(), v.end());
	list<int>::iterator pos = find(l.begin(), l.end(), 3);
	l.insert(pos, 20);     //在3前插入20
	for (auto it : l) cout << it << " "; 
	cout << endl;
	pos = find(l.begin(), l.end(), 5); 
	l.erase(pos);          //删除5
	for (auto it : l) cout << it << " "; 
	cout << endl;
	return 0;
}

迭代器失效

☘️当我们使用 erase 进行删除后,此时指向删除位置的迭代器就失效了,再次使用就会令程序崩溃。

 

☘️因此若要多次删除,则需要在使用后利用 erase 的返回值更新迭代器,这样使用才不会出现错误。

int main()
{
	vector<int> v = { 2,3,5,4,6 };
	list<int> l(v.begin(), v.end());
	list<int>::iterator pos = find(l.begin(), l.end(), 3);
	for (int i = 0; i < 3; i++)
	{
		pos = l.erase(pos);   //利用erase的返回值更新迭代器
	}
	for (auto it : l) cout << it << " ";
	cout << endl;
	return 0;
}

  

交换

☘️跟 vector 和 string 里面的一样都是直接交换内部的指向,而不需要拷贝节点。

int main()
{
	vector<int> v1 = { 2,3,5,4,6 };
	vector<int> v2 = { 1,2,3,4,5 };
	list<int> l1(v1.begin(), v1.end());
	list<int> l2(v2.begin(), v2.end());
	for (auto it : l1) cout << it << " ";   //交换前
	cout << endl;
	for (auto it : l2) cout << it << " ";
	cout << endl;
	cout << endl;
	l1.swap(l2);
	for (auto it : l1) cout << it << " ";   //交换后
	cout << endl;
	for (auto it : l2) cout << it << " ";
	cout << endl;
	return 0;
}

其他操作

☘️除了上面那些基础了操作,库中还有一些其他函数,挑了几个讲讲。

splice

☘️这个函数可以帮助我们将一个链表的节点转移到另外一个链表中。

☘️函数结束后,l2 就变成了空链表,而其节点被转移到 l1 之中。 

int main()
{
	int arr[] = { 1,2,3,4,5,6 };
	int arr2[] = { 10,20,30 };
	list<int> l1(arr, arr + 6);
	list<int> l2(arr2, arr2 + 3);
	for (auto i : l1) cout << i << " ";  //转移前
	cout << endl;
	for (auto i : l2) cout << i << " ";
	cout << endl;
	cout << endl;
	list<int>::iterator it = l1.begin();
	++it;
	l1.splice(it, l2);
	for (auto i : l1) cout << i << " ";  //转移后
	cout << endl;
	for (auto i : l2) cout << i << " ";
	cout << endl;
	return 0;
}

 

remove

☘️remove 本质上就是 find + erase,可以方便我们的实际的使用。

int main()
{
	int arr[] = { 1,2,3,4,5,6 };
	list<int> l1(arr, arr + 6);
	for (auto i : l1) cout << i << " ";  //删除前
	cout << endl;
	l1.remove(5);
	for (auto i : l1) cout << i << " ";  //删除后
	cout << endl;
	return 0;
}

合并

☘️使用 merge,我们可以合并两个有序的链表,实际上的操作就类似于归并排序。

int main()
{
	int arr[] = { 1,2,3,4,5,6 };
	int arr2[] = { 10,20,30 };
	list<int> l1(arr, arr + 6);
	list<int> l2(arr2, arr2 + 3);
	for (auto i : l1) cout << i << " ";  //合并前
	cout << endl;
	for (auto i : l2) cout << i << " ";
	cout << endl;
	cout << endl;
	l1.merge(l2);
	for (auto i : l1) cout << i << " ";  //合并后
	cout << endl;
	for (auto i : l2) cout << i << " ";
	cout << endl;
	return 0;
}

  

去重

☘️使用这个函数可以去除掉链表中的重复元素,但前提是这个链表必须是有序的。

int main()
{
	vector<int> v = { 1,1,1,2,3,3,4,4,5 };
	list<int> l1(v.begin(),v.end());
	for (auto i : l1) cout << i << " ";  //去重前
	cout << endl;
	cout << endl;
	l1.unique();
	for (auto i : l1) cout << i << " ";  //去重后
	cout << endl;
	return 0;
}

  

逆置

☘️可以通过这个函数可以将链表逆置。

int main()
{
	vector<int> v = { 1,2,3,4,5,6 };
	list<int> l1(v.begin(), v.end());
	for (auto i : l1) cout << i << " ";  //逆置前
	cout << endl;
	cout << endl;
	l1.reverse();
	for (auto i : l1) cout << i << " ";  //逆置后
	cout << endl;
	return 0;
}

 

排序

☘️list 并不能使用算法库里面的 sort,因为它的迭代器并不是随机访问的迭代器,所以库中重载了一个 sort。

 

☘️但其实,这个函数的效率并不高,我们可以写个代码来测试一下。

int main()
{
	srand((size_t)time(NULL));
	int n = 1000000;
	vector<int> v;
	list<int> l;
	for (int i = 0; i < n; i++)
	{
		int val = rand() % 100;
		v.push_back(val);
		l.push_back(val);
	}

	int begin1 = clock();     //vector开始排序
	sort(v.begin(), v.end());
	int end1 = clock();       //vector结束排序

	int begin2 = clock();    //list开始排序
	l.sort();
	int end2 = clock();      //list结束排序

	cout << "vector: " << end1 - begin1 << endl;
	cout << "list: " << end2 - begin2 << endl;
	return 0;
}

☘️当 n 为1000000时,差距就非常的明显了。

☘️之后我们将一个 list 拷贝进 vector 后再进行排序,看看最后结果如何。

int main()
{
	srand((size_t)time(NULL));
	int n = 1000000;
	list<int> l1;
	list<int> l2;

	for (int i = 0; i < n; i++)
	{
		int val = rand() % 100;
		l1.push_back(val);
		l2.push_back(val);
	}

	int begin1 = clock();
	vector<int> v;
	for (auto it : l1) v.push_back(it); //把list拷贝进vector
	sort(v.begin(), v.end());           //再次排序
	int end1 = clock();

	int begin2 = clock();
	l2.sort();    
	int end2 = clock();

	cout << "l1: " << end1 - begin1 << endl;
	cout << "l2: " << end2 - begin2 << endl;
	return 0;
}

☘️可以看出,差距还是很大的,因此未来的使用中,除非数值较小为了图个方便可以直接使用,否则还不如拷贝到 vector 里再进行排序。


☘️好了,今天 list 的相关内容到这里就结束了,如果这篇文章对你有用的话还请留下你的三连加关注。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/564604.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

人人都能看懂的Spring源码解析,Spring声明式事务关于传播特性、事务挂起与恢复的处理

人人都能看懂的Spring源码解析&#xff0c;Spring声明式事务关于传播特性、事务挂起与恢复的处理 原理解析AbstractPlatformTransactionManager事务传播特性事务挂起与恢复通过DataSourceTransactionManager看事务挂起和恢复的具体实现 代码走读总结 往期文章&#xff1a; 人人…

LRU Cache

前言 哈喽&#xff0c;各位小伙伴大家好&#xff0c;本章内容为大家介绍计算机当中为了提高数据相互传输时的效率而引进的一种重要设计结构叫做LRU Cache,下面将为大家详细介绍什么是LRU Cache,以及它是如何是实现的&#xff0c;如何提升效率的。 1.什么是LRU Cache? LRU是L…

卷起来了?2023这三个项目直接让你原地起飞!

理论自学谁不会&#xff0c;理论知识跟实战项目实践相结合才是大问题&#xff1f; 还在发愁没有项目练手&#xff1f;还在发愁简历中的项目生搬硬凑&#xff1f;还在担心自己没实操过项目被面试官直接K.O? 这三个实战项目让你快人一步&#xff0c;总有一个适合你的&#xff…

数慧时空20年磨一剑:推出智能遥感云平台DIEY,自然资源多模态大模型“长城”,为地理信息产业提速

作者 | 伍杏玲 出品 | CSDN 据中国地理信息产业发展报告公布的数据&#xff0c;截至2020年末&#xff0c;行业从业单位13.8万家&#xff0c;从业人数336.6万&#xff0c;到2021年末&#xff0c;从业单位增加到16.4万家&#xff0c;从业人数增加到398万&#xff0c;产业规模越…

软件测试的未来?为什么越来越多的公司选择模糊测试

背景&#xff1a;近年来&#xff0c;随着信息技术的发展&#xff0c;各种新型自动化测试技术如雨后春笋般出现。其中&#xff0c;模糊测试&#xff08;fuzz testing&#xff09;技术开始受到行业关注&#xff0c;它尤其适用于发现未知的、隐蔽性较强的底层缺陷。这里&#xff0…

Eclipse MAT分析内存案例

前言 本文记录一次使用Eclipse MAT排查内存问题的案例&#xff0c;缘由是线上某服务OOM&#xff0c;排查得知jvm old区占满&#xff0c;但是gc了还是无法释放 实战 首先在线上服务器排查发现某应用占用了大量的内存&#xff0c;由一个ConcurrentHashMap对象造成的&#xff0…

【ArcGIS Pro二次开发】(31):ArcGIS Pro中的多线程

ArcGIS Pro与旧的ArcGIS桌面应用程序的显著不同之处在于&#xff0c;它采用多线程架构&#xff0c;可以有效的发挥多核CPU的优势。这使得二次开发工具的性能变得更好&#xff0c;但也对开发工作带来了更多的难点和挑战。 一、多线程需要注意的问题 一般情况下&#xff0c;为了…

尚硅谷MyBatis-Plus笔记001【简介、入门案例、基本CRUD】

视频地址&#xff1a;【尚硅谷】MyBatisPlus教程&#xff08;一套玩转mybatis-plus&#xff09;_哔哩哔哩_bilibili 尚硅谷MyBatis-Plus笔记01【简介、入门案例、基本CRUD】 尚硅谷MyBatis-Plus笔记02【】 尚硅谷MyBatis-Plus笔记03【】 尚硅谷MyBatis-Plus笔记04【】 尚硅谷…

非暴力沟通--日常沟通的技巧与实践

这篇文章是我在公司团队内部做的分享的演讲稿 开场白 大家好&#xff0c;我今天要分享的主题是非暴力沟通–日常沟通的技巧与实践。 介绍非暴力沟通这本书 分享这个主题的原因是我最近看了一本书&#xff0c;叫做《非暴力沟通》&#xff0c;这本书是美国一个叫做马歇尔卢森堡…

MFC CListCtrl 显示图片

MFC CListCtrl 显示图片 MFC CListCtrl 显示图片PreCreateWindow中设置风格没有起作用在OnCreate中设置CListCtrl的风格最合适在OnInitialUpdate中添加数据最合适需要设置CImageList&#xff0c;资源是我自己搞的一个图片资源ps:参考链接 MFC CListCtrl 显示图片 在使用MFC的C…

Codeforces Round 764 (Div. 3)

比赛链接 Codeforces Round 764 A. Plus One on the SubsetB. Make APC. Division by Two and PermutationD. Palindromes ColoringE. Masha-forgetful A. Plus One on the Subset Example input 3 6 3 4 2 4 1 2 3 1000 1002 998 2 12 11output 3 4 1题意&#xff1a; 你可…

怎么学习机械学习相关的技术?

学习机器学习相关技术的过程可以分为以下几个步骤&#xff1a; 掌握基本数学和统计知识&#xff1a; 机器学习建立在数学和统计学的基础上&#xff0c;了解线性代数、概率论、统计学等基本概念和方法对于理解机器学习算法至关重要。 学习编程和数据处理&#xff1a; 掌握一门…

kafka基础介绍

目录 前言&#xff1a; 一:kafka架构 1.kafka基础架构 2、kafka多副本架构 二、kafka基础概念 1、produce 2. Consumer 3、Broker ​ 4、Topic 5、Partition 6、Replicas 7、Offset 8、 AR 9、 ISR 10、OSR 11、HW 12、LEO 13、Lag 三、kafka特性 四、kafka…

总结加载Shellcode的各种方式

1.内联汇编加载 使用内联汇编只能加载32位程序的ShellCode&#xff0c;因为64位程序不支持写内联汇编 #pragma comment(linker, "/section:.data,RWE") //将data段的内存设置成可读可写可执行 #include <Windows.h>//ShellCode部分 unsigned char buf[] &qu…

FreeRTOS-任务通知详解

✅作者简介&#xff1a;嵌入式入坑者&#xff0c;与大家一起加油&#xff0c;希望文章能够帮助各位&#xff01;&#xff01;&#xff01;&#xff01; &#x1f4c3;个人主页&#xff1a;rivencode的个人主页 &#x1f525;系列专栏&#xff1a;玩转FreeRTOS &#x1f4ac;保持…

Spring Security入门

1. Spring Security 简介 Spring Security 是一个高度可定制的身份验证和访问控制框架&#xff0c;它基于 Spring 框架&#xff0c;并可与 Spring 全家桶无缝集成。该框架可以精确控制用户对应用程序的访问&#xff0c;控制用户的角色和权限等。 Spring Security 最早是由 Be…

C Primer Plus第三章编程练习答案

学完C语言之后&#xff0c;我就去阅读《C Primer Plus》这本经典的C语言书籍&#xff0c;对每一章的编程练习题都做了相关的解答&#xff0c;仅仅代表着我个人的解答思路&#xff0c;如有错误&#xff0c;请各位大佬帮忙点出&#xff01; 1.通过试验&#xff08;即编写带有此类…

第三节 HLMSEditor场景编辑器的编译

本节注意介绍下HLMSEditor场景编辑器的源码编译使用 一 安装依赖的资源 使用编译器为VS2019 X64&#xff0c;操作系统为WIN10&#xff0c;Ogre2.1&#xff0c;HLMSEditor 注意&#xff1a;为什么不用Ogre2.3?因为HLMSEditor版本为0.5.5&#xff0c;很久没有更新了&#xff0…

【Linux进阶之路】yum与vim操作

文章目录 前言一.yum——Linux的应用商店介绍基本使用① yum源②安装数据传输软件1.将Linux的文件传输到Windows平台上2.将Windows的文件传到Linux系统上 ③删除数据传输软件⑥查看安装包版本⑤练习安装与卸载小火车安装与卸载牛会说话 二.vim —— 一款优雅的编辑器①基本模式…

安卓基础巩固(一):工程结构、基本概念、常用布局、基本组件、动画

文章目录 安卓项目结构AndroidMainfest.xmlres资源目录简介 基本概念LayoutR类 Application与ActivityContextIntent数据传递可传递的数据类型intent.putExtra&#xff08;&#xff09;和使用Bundle的区别数据传递大小的限制 通过Intent 过滤器接收隐式 Intent&#xff1a; 单位…