C++STL---list知识汇总

news2025/1/15 20:55:09

前言

学习完list,我们会对STL中的迭代器有进一步的认识。list底层有很多经典的东西,尤其是他的迭代器。而list的结构是一个带头双向循环链表。

list没有reserve和resize,因为它底层不是连续的空间,它是用时随时申请,不用时随时释放。他也不支持随机访问,所以没有operator[ ]。而他有头插,尾插,头删,尾删,以及任意位置的插入删除。

严格来说,C++中list实际有两个:

第一个forward_list是单链表,它是C++11新增加的,它的使用场景很少。它不支持尾插,尾删,因为单链表尾插尾删的效率很低。并且它对任意位置做插入删除操作是在当前位置之后,因为当前位置之前得找前一个,也是一个O(n)的实现。唯一的优势也就是每个结点少一个指针。

第二个list是我们要学习的带头双向循环链表。

list的介绍

1.列表是序列容器,允许在序列中的任何位置进行恒定时间O(1)的插入和擦除操作,以及双向迭代。

2.列表容器底层是双链表;双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向前一个元素和后一个元素。

3.list与forward_list非常相似:主要区别在于forward_listobject是单个链表,因此它们只能向前迭代,以换取更小、更高效。

4.与其他基本标准序列容器(array,vector和deque)相比,list在任意位置插入,删除结点的效率更高。

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

list的使用

1.list的构造
constructor接口说明
list()构造空的list对象
list(size_t n,const value_type& val =value_type())构造list对象中包含n个值为val的元素
list(const list& x)拷贝构造
lsit(Inputiterator first,Inputiterator last)用迭代区间构造list对象

这里我们只要记住用迭代区间构造list对象时,也可以用其他容器的迭代器就行,其他没什么好说的。

#include<iostream>
#include<vector>
#include<list>
using namespace std;
int main()
{
    vector<int> v = { 1, 2, 3, 4, 5 };
	list<int> lt1(v.begin(), v.end());//用vector的迭代器构造list对象	
    return 0;
}

2.list迭代器的使用
iterator接口说明
begin && end返回第一个元素的迭代器 && 返回最后一个元素下一个位置的迭代器
rbegin + rend返回第一个元素的 reserve_iterator,即 end 位置 && 返回最后一个元素下一个位置的 reverse_iterator,即 begin 位置

我们需要记住两点:

1.在遍历的时候vector和string我们可以使用小于或不等于做判断条件,但是list只能使用不等于。因为list内不是连续的空间。

2.因为list不支持operator[ ],所以我们遍历list的时候不能使用[ ]的方式遍历了

		vector<int> v = { 1, 2, 3, 4};
		list<int> lt(v.begin(), v.end());
        list<int>::iterator it1 = lt.begin();
		while(it1 != lt.end())//这里只能使用!=,不能使用<
		{
			cout << *it1 << " ";
			++it1;	
		}
		cout << endl
3.list容量大小的函数(empty && size)
empty判断list对象是否为空
size返回list中有效结点的个数
4.list结点接收的函数(front && back)
front返回list第一个结点的数据的引用
back返回list最后一个结点的数据的引用
5.list修改函数
push_back尾插一个值为val的结点
push_front头插一个值为val的结点
pop_back尾删一个结点
pop_front头删一个结点
insert在pos位置中插入值为val的结点
erase删除pos位置的结点
swap交换两个list对象中的元素
clear清空list对象中的有效元素

下面我们在一段代码中用例子来解释我们需要注意的地方:

#include<iostream>
#include<algorithm>
#include<vector>
#include<list>
#include<functional>
using namespace std;

namespace std
{
	void test1()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);

		lt.pop_front();
		lt.pop_front();
		lt.pop_front();
		lt.pop_front();
		//lt.pop_front();  //注意在头删、尾删时要保证list里还有数据,否则这里会报断言错误

		for (const auto& e : lt)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	void test2()
	{
		list<int> lt;
		lt.push_back(10);
		lt.push_back(20);
		lt.push_back(30);
		lt.push_back(40);
		
		list<int>::iterator pos = find(lt.begin(), lt.end(), 30);//list里也没有提供find函数,所以这里使用的是algorithm里的
		if (pos != lt.end())
		{
			lt.insert(pos, 3);
		}
		for (const auto& e : lt)
		{
			cout << e << " ";
		}
		cout << endl;

		lt.clear();//clear不会把头节点清除,这里还可以继续插入数据
		lt.push_back(1);
		lt.push_back(2);
		for (const auto& e : lt)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	void test3()
	{
		list<int> lt1;
		lt1.push_back(1);
		lt1.push_back(2);

		list<int> lt2;
		lt2.push_back(2);
		lt2.push_back(1);

		list<int> lt3;
		lt3.push_back(1);
		lt3.push_back(2);
		lt3.push_back(3);
		lt3.push_back(4);

		//对于swap,建议使用容器里的,而不建议使用算法里的。它们效果一样,但是效率不一样
		lt1.swap(lt2);        //这是std::list::swap,专门为了list设计的,效率会更高
		//swap(lt1, lt2);     //这是std::swap,这是算法中的,通用的,底层发生的是深拷贝,效率低
		for (const auto& e : lt1)
		{
			cout << e << " ";
		}
		cout << endl;
		for (const auto& e : lt2)
		{
			cout << e << " ";
		}
		cout << endl;

		//所有的排序都满足,>是降序,<是升序,这里默认是升序
		//这个也是一个类模板,它是一个仿函数,所在头<functional>,后面我们会实现,sort所在头<algorithm>
		greater<int> g;
		lt3.sort(g);
		lt3.sort(greater<int>());//同上,可以直接写成匿名对象
		for (const auto& e : lt3)
		{
			cout << e << " ";
		}
		cout << endl;
		//sort(lt3.begin(), it3.end());     //error
		//vector可以使用算法提供的sort()函数,但是list不行,因为本质sort()会用两个迭代器相减,而list的迭代器不支持减!!

		//unique的功能是去重,是algorithm提供的,去重的前提是排序,升序降序都行,如果不排序它只能去重相邻的数据
		lt3.unique();
		for (const auto& e : lt3)
		{
			cout << e << " ";
		}
		cout << endl;

		//erase需要先find,而remove可以直接删除,有就全删,没有就不删,由algorithm提供
		lt3.remove(2);
		for (const auto& e : lt3)
		{
			cout << e << " ";
		}
		cout << endl;

		//reverse的功能是逆置,对于带头双向循环链表的逆置比单链表简单,由algorithm提供
		lt3.reverse();
		for (const auto& e : lt3)
		{
			cout << e << " ";
		}
		cout << endl;

		//merge的功能是合并
		//splice的功能是转移,它转移的是节点不是数据,很特殊的场景下才会使用到,我们以后在了解LRU可能还会再接触到
	}
}
int main()
{
	//std::test1();
	//std::test2();
	std::test3();
	return 0;
}

注意:

1.C++98不论什么容器都建议使用各自容器里的 swap,而不建议使用算法里的 swap。

因为无论什么容器使用算法中的swap()时都会涉及到深拷贝问题,并且需要深拷贝三次,代价极大。

而容器内的swap会根据各个容器的特性,进行交换。例如,两个list对象交换的时候,只需要交换两个对象的头指针指向就行;两个vector对象交换的时候,只需要交换两个vector对象中_start、_finish、_endofstorage三个指针的指向即可,不用做深拷贝,也就提高了效率。

2.关于迭代器的补充

从使用功能的角度分类:正向,反向,const,非const

从容器底层结构分类:单向,双向,随机

如单链表,哈希表迭代器就是单向,特点是能++,不能--;双向循环链表,map迭代器就是双向,特点是能++,也能--;string,vector,deque迭代器就是随机迭代器,特征是不仅能++,--,还能+,-,一般随机迭代器底层都是一个连续的空间。

这里我们可以通过算法里函数参数的命名来推断出他的含义:

我们看,例如sort函数内参数命名为RandomAccessIterator,也就是随机迭代器;而reverse函数内参数命名为BidirectionalIterator,也就是双向迭代器。

而我们使用的时候,要注意,比如:reserve函数的参数是双向迭代器,而string能不能使用呢?答案是可以的,因为string是随机迭代器,它满足双向迭代器的所有功能。但是例如:list能使用sort函数吗?答案是不能的,因为list迭代器是双向的,不满足随即迭代器的功能。也就是说功能多的是可以执行参数迭代器功能少的函数的,这实际上就是一种继承关系。

而我们这里就要明白一个道理:容器是用来存储数据的,而根据封装的要求,他的成员变量一般是私有的,而封装的本质就是通过合法的渠道去进行操作,那我可以提供成员函数来供使用者合法的操作,但是这样的话也不太好,因为底层结构差异大了以后,每种容器的操作起来的差别也会很大。例如,string,vector底层是数组,我们通过数组的方式进行操作,list底层是链表我们通过链表的方式进行操作,而其他复杂的数据结构,操作的方式会更不一样。所以迭代器的本质就是不破坏容器的封装性,不暴露容器底层实现细节的情况下,提供统一的方式去操作容器中存储的数据。只要我们会其中一种容器的迭代器,我们用其他容器的迭代器也没有问题。所以迭代器被称为”容器和算法之间的胶合剂“。

6.list迭代器失效问题

在具体介绍list迭代器之前,我们可以先将迭代器暂时理解为指针,迭代器失效就是迭代器所指向的结点无效,即该结点被删除了。list底层结构为带头双向循环链表,所以对list进行insert操作时不会导致list迭代器失效,只有在erase的时候才会失效,并且失效的只有被删除的结点,其他迭代器不会收到影响。

也就是说:

  • vector insert,pos会失效,因为它的物理空间是一个连续的数组,首先它可能会扩容,就会导致野指针问题;就算不扩容,挪动了数据,pos的意义也改变了,所以pos也失效了
  • vector erase,pos会失效,因为此时pos的意义已经改变了,为了解决这个问题,我们可以使用pos重新接收erase函数的返回值,其指向删除元素的下一个元素
  • list insert,pos不会失效,因为list底层是一个链表,每个结点都是独立的,insert后的数据属于新增的结点,而pos还是指向原来的位置
  • erase pos,pos会失效,因为pos指向的结点已经被释放了,为了解决这个问题,和vector erase的解决方法一样,我们也可以使用pos重新接收erase函数的返回值

以上就是本章的所有内容,谢谢大家!!!

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

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

相关文章

18.使用__asm实现调用hp减伤害

上一个内容&#xff1a;17.调用游戏本身的hp减伤害函数实现秒杀游戏角色 17.调用游戏本身的hp减伤害函数实现秒杀游戏角色 以它的代码为基础进行修改 首先禁用安全检查 然后再把优化关闭 编译代码时使用Release方式&#xff0c;debug方式会加一些代码&#xff0c;如果这些代码…

【机器学习】深度探索:从基础概念到深度学习关键技术的全面解析——梯度下降、激活函数、正则化与批量归一化

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、机器学习的基本概念与原理二、深度学习与机器学习的关系2.1 概念层次的关系2.2 技术特点差异2.3 机器学习示例&#xff1a;线性回归&#xff08;使用Python和scikit-learn库&#xff09;2.4 深度学习示例&#xff1a;简…

【网络编程开发】4.socket套接字及TCP的实现框架 5.TCP多进程并发

4.socket套接字及TCP的实现框架 Socket套接字 Socket套接字是网络编程中用于实现不同计算机之间通信的一个基本构建块。 在现代计算机网络中&#xff0c;Socket套接字扮演着至关重要的角色。它们为应用程序提供了一种方式&#xff0c;通过这种方式&#xff0c;程序能够通过网…

【技巧】系统语音是英文 影刀如何设置中文-作者:【小可耐教你学影刀RPA】

写在前面 嘿哈&#xff01; 有些跨境或香港的小伙伴&#xff0c;可能需要使用英文操作界面的影刀 该功能目前还没有现成的可视化按钮&#x1f518; 但其实这个效果可以实现&#xff5e; 1、效果图 2、实现原理 %影刀安装目录%\ShadowBot-版本号\ShadowBot.Shell.dll.confi…

十四、返回Insert操作自增索引值

分为两部分&#xff0c;解析初始化和使用 拿含有selectkey标签的insert语句解析来说 解析部分 1.解析时看有没有selectkey标签&#xff0c;有的话先解析selectkey的内容&#xff0c;包括对其SQL的解析并封装成一个MappedStatement和创建KeyGenerator放入configuration中 2.解…

Android WebView上传文件/自定义弹窗技术,附件的解决方案

安卓内核开发 其实是Android的webview默认是不支持<input type"file"/>文件上传的。现在的前端页面需要处理的是&#xff1a; 权限 文件路径AndroidManifest.xml <uses-permission android:name"android.permission.WRITE_EXTERNAL_STORAGE"/&g…

NumPy应用(一)

NumPy学习篇1 NumPy是一个强大的Python库&#xff0c;它提供了高效的多维数组对象和各种用于数组操作的函数。以下是NumPy学习大纲&#xff0c;详细介绍了NumPy的核心功能和概念。 1. NumPy 简介 NumPy是一个用于处理多维数组的Python库&#xff0c;它提供了一个强大的数组对…

测试记录3:WLS2运行Linux界面

1.WLS1转到WLS2 &#xff08;1&#xff09;根据自己的平台&#xff0c;下载WLS2安装包 x64: https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi arm64: https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_arm64.msi &#xff08;2&…

高清矩阵是什么?

在数学中&#xff0c;矩阵是一个按照长方阵列排列的复数或实数集合&#xff0c;最早来自于方程组的系数及常数所构成的方阵。如图为m行n列的矩阵&#xff1a; 由此延伸可以想到矩阵图片是把一个三维空间分切成多个行和列的区域进行图像捕获&#xff0c;将捕获图像再进行拼合成为…

Spring系统学习 - Spring入门

什么是Spring&#xff1f; Spring翻译过来就是春天的意思&#xff0c;字面意思&#xff0c;冠以Spring的意思就是想表示使用这个框架&#xff0c;代表程序员的春天来了&#xff0c;实际上就是让开发更加简单方便&#xff0c;实际上Spring确实做到了。 官网地址&#xff1a;ht…

k8s 1.28.x 配置nfs

1.安装nfs&#xff0c;在每个节点上安装 yum install -y nfs-utils 2.创建共享目录(主节点上操作) mkdir -p /opt/nfs/k8s 3.编写NFS的共享配置 /opt/nfs/k8s *(rw,no_root_squash) #*代表对所有IP都开放此目录&#xff0c;rw是读写 4.启动nfs systemctl enable nfs-ser…

基于STC12C5A60S2系列1T 8051单片机实现串口调试助手软件与单片机相互发送数据的RS485通信功能

基于STC12C5A60S2系列1T 8051单片机实现串口调试助手软件与单片机相互发送数据的RS485通信功能 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机串口通信介绍STC12C5A60S2系列1T 8051单片机串口通信的结构基于STC12C5A60S2系列1T 8051单片机串口通信的特殊功…

Wireshark Lua插件入门

摘要 开发中经常通过抓包分析协议&#xff0c;对于常见的协议如 DNS wireshark 支持自动解析&#xff0c;便于人类的理解&#xff0c;对于一些私有协议&#xff0c;wireshark 提供了插件的方式自定义解析逻辑。 1 动手 废话少说&#xff0c;直接上手。 第一步当然是装上wiresh…

Java基础27,28(多线程,ThreadMethod ,线程安全问题,线程状态,线程池)

目录 一、多线程 1. 概述 2. 进程与线程 2.1 程序 2.2 进程 2.3 线程 2.4 进程与线程的区别 3. 线程基本概念 4.并发与并行 5. 线程的创建方式 方式一&#xff1a;继承Thread类 方式二&#xff1a;实现Runable接口 方式三&#xff1a;实现Callable接口 方式四&…

【wiki知识库】05.分类管理模块--后端SpringBoot模块

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 目录 一、&#x1f525;今日目标 二、☀SpringBoot代码修改 1.使用逆向工程生成Category表结构 2. 新增CategoryQueryParam 3.新增CategorySaveParam 4.新增CategotyQueryVo 三、&#x1f916;新增分类管理的相关接口…

数字化营销有哪些模式?企业采用数字营销方式有什么意义?

在当今快速发展的商业环境中&#xff0c;营销已经远远超越了传统的推广和销售概念&#xff0c;演变成一种复杂而全面的组织职能。随着信息技术的飞速发展&#xff0c;数字化营销应运而生&#xff0c;为企业与消费者之间的互动带来了革命性的改变。数字化营销不仅为企业提供了全…

为何PHP使用率 大幅度下降!需求量几乎为零!

用PHP的人越来越少的主要原因包括&#xff1a;市场竞争加剧、新技术的出现、性能和安全问题、以及开发者社区的变化。市场竞争加剧是其中一个突出的因素。随着Python、Node.js等现代编程语言的崛起&#xff0c;它们提供了更好的性能、更简洁的语法和更丰富的框架&#xff0c;逐…

【Text2SQL 论文】DBCopilot:将 NL 查询扩展到大规模数据库

论文&#xff1a;DBCopilot: Scaling Natural Language Querying to Massive Databases ⭐⭐⭐⭐ Code: DBCopilot | GitHub 一、论文速读 论文认为目前的 Text2SQL 研究大多只关注具有少量 table 的单个数据库上的查询&#xff0c;但在面对大规模数据库和数据仓库的查询时时却…

PPT文件损坏且无法读取怎样修复?文档损坏修复方法推荐

PPT文件已经成为工作汇报、商务演示、学术交流以及教学培训中最常用到的文件&#xff0c;随着文件数量的增多和存储设备的频繁使用&#xff0c;我们有时会遇到PPT文件损坏无法打开的情况&#xff0c;这无疑给工作和学习带来了极大的困扰。 PPT文件损坏的原因可能多种多样&#…

技术回眸一笑

回忆一下一年前的出差日记吧&#xff0c;那个时候真的是一点经验没有&#xff0c;干硬件又干软件&#xff0c;只能一步一步慢慢摸索&#xff0c;努力过后慢慢成长起来的吧。那个时候甚至开学了都没有正常报道&#xff0c;但是也收获了不少东西&#xff0c;并且也将作为我后来继…