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

前言

学习完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函数的返回值

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【QT】父子按钮同时响应点击事件

QPushButton如何响应点击事件 QPushButton::event(QEvent *e) 。可以看到在QPushButton中的event函数中并没有鼠标点击相关的操作&#xff0c;那么我们去QAbstractButton::event中寻找 damn it!。依然没有那我们去QWidget::event中寻找 damn it! 只有mousePressEvent mouseR…

c++学生管理系统

想要实现的功能 1&#xff0c;可以增加学生的信息&#xff0c;包括&#xff08;姓名&#xff0c;学号,c成绩&#xff0c;高数成绩&#xff0c;英语成绩&#xff09; 2&#xff0c;可以删除学生信息 3&#xff0c;修改学生信息 4&#xff0c;显示所有学生信息 5&#xff0c…

python中利用cartopy库绘制SST图像

1. Cartopy简介 Cartopy 是一个开源的 Python 库&#xff0c;用于绘制地图和地理数据分析。它结合了 matplotlib 的绘图功能和 shapely、pyproj 等库的地理空间数据处理能力&#xff0c;为用户提供了在地图上可视化数据的强大工具。 以下是 Cartopy 的一些主要特点和功能&#…

微信公众号开发(问题1):订阅号不能定时发私信/私信

微信公众号开发时&#xff0c;因为个人使用&#xff0c;申请的是订阅号&#xff0c;本来是想出了自动回复&#xff0c;再加一个定时消息的功能&#xff0c;尝试利用flask里的scheduler添加定时任务&#xff0c;但是执行之后不能收到消息&#xff0c;通过再次查看订阅号的功能&a…

stm32mp157为什么要把相同的tf-A trust烧录emmc的boot1和boot2?

在使用该处理器时&#xff0c;为什么要将相同的Trusted Firmware-A (TF-A)烧录到eMMC的Boot1和Boot2区域呢&#xff1f; 这么做的主要原因包括&#xff1a; 冗余性和可靠性&#xff1a; 将相同的TF-A烧录到两个不同的引导区域&#xff08;Boot1和Boot2&#xff09;可以增加系…

LeetCode 算法:找到字符串中所有字母异位词c++

原题链接&#x1f517;&#xff1a;找到字符串中所有字母异位词 难度&#xff1a;中等⭐️⭐️ 题目 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 异位词 指由相同字母重排列形成的字符…

Jenkins流水线pipeline--基于上一章的工作流程

1流水线部署 1.流水线文本名Jenkinsfile,将流水线放入gitlab远程仓库代码里面 2pipeline脚本 Jenkinsfile文件内容 pipeline {agent anyenvironment {key"value"}stages {stage("拉取git仓库代码") {steps {deleteDir()checkout scmGit(branches: [[nam…

人工智能与【肿瘤免疫微环境】结合,探索免疫治疗的新方向|24年6月·顶刊速递·06-02

罗小罗同学说 24-06-02&#xff5c;文献速递 今天分享的文章&#xff0c;主题是——人工智能&肿瘤免疫微环境。解释一下这张图&#xff0c;左列是文献标题&#xff0c;右侧是发表的年月&#xff0c;放心&#xff0c;都是顶刊&#xff0c;不然我也不会选的。 PS&#xff1a…

大数据测试/ETL开发,如何造测试数据

相信很多的小伙伴&#xff0c;有些是大数据测试岗位&#xff0c;有些是ETL开发&#xff0c;都面临着如何要造数据的情况。 1&#xff0c;造数背景 【大数据测试岗位】&#xff0c;比较出名的就是宁波银行&#xff0c;如果你在宁波银行做大数据开发&#xff0c;对着需求开发完…

Java——常见进制

在计算机领域有四种比较常见的进制&#xff0c;分别是二进制、八进制、十进制和十六进制。 一、二进制&#xff08;Binary&#xff09; 二进制&#xff08;Binary&#xff09;是一种基数为2的数值系统&#xff0c;仅使用两个符号&#xff1a;0和1。所以它的进位规则就是逢二进…

机械革命硬盘数据恢复:深度解析与实用指南

随着科技的飞速发展&#xff0c;数据存储与备份已成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;硬盘故障、误删除或格式化等意外情况时常发生&#xff0c;导致重要数据丢失&#xff0c;给用户带来极大的困扰。本文将以“机械革命硬盘数据恢复”为主题&#xff0…

【惯性传感器imu】—— WHEELTEC的惯导模块的imu的驱动安装配置和运行

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、IMU驱动安装1. 安装依赖2. 源码的下载3. 编译源码(1) 配置固定串口设备(2) 修改luanch文件(3) 编译 二、启动IMU1. 运行imu2. 查看imu数据 总结 前言 WHEE…

随记-点选验证码(二)

之前写过一篇文章 随记-点选验证码 &#xff0c;当时借助了 ddddocr 完成了ocr 识别&#xff0c;这篇文章算是对之前的补充。 本次更换了新的方案&#xff1a; 通过 ultralytics&#xff08;YOLO8&#xff09;训练自己的模型 吐槽一句&#xff1a;标注真是一件耗时的事情啊&am…

【Matplotlib作图-2.Deviation】50 Matplotlib Visualizations, Python实现,源码可复现

目录 02 Deviation 2.0 Prerequisite 2.1 发散型条形图(Diverging Bars) 2.2 发散型文本(Diverging Texts) 2.3 Diverging Dot Plot 2.4 Diverging Lollipop Chart with Markers 2.5 面积图(Area Chart) References 02 Deviation 2.0 Prerequisite Setup.py # !pip ins…

图书推荐:ChatGPT专业知识信息课程

《ChatGPT专业知识信息课程》&#xff08;ChatGPT-Expertise Informative Course&#xff09; 是一本由Dwayne Anderson撰写的电子书&#xff0c;提供了关于ChatGPT的丰富知识。该书涵盖了与ChatGPT相关的各种主题&#xff0c;如其与OpenAI的关系、ChatGPT与GPT-3之间的混淆、C…

【LeetCode热题100总结】239. 滑动窗口最大值

题目描述 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1&#xff1a; 输入&#xff1a;nums [1,3,-1,-3,5,3,6,7]…

STM32-- GPIO->EXTI->NVIC中断

一、NVIC简介 什么是 NVIC &#xff1f; NVIC 即嵌套向量中断控制器&#xff0c;全称 Nested vectored interrupt controller 。它 是内核的器件&#xff0c;所以它的更多描述可以看内核有关的资料。M3/M4/M7 内核都是支持 256 个中断&#xff0c;其中包含了 16 个系统中…

WHAT - 容器化系列(一)

这里写目录标题 一、什么是容器与虚拟机1.1 什么是容器1.2 容器的特点1.3 容器和虚拟机的区别虚拟机&#xff08;VM&#xff09;&#xff1a;基于硬件的资源隔离技术容器&#xff1a;基于操作系统的资源隔离技术对比总结应用场景 二、容器的实现原理1. Namespace&#xff08;命…

【Java】一文看懂Thread 线程池的 7 种创建方式、任务队列及自定义线程池(代码示例)

本文摘要&#xff1a;【Java】Thread 线程池的 7 种创建方式及自定义线程池&#xff08;代码示例版&#xff09; &#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专…

1. MySQL 数据库的基本操作

文章目录 【 1. SQL 的书写规则 】大小写规则常量的表示注释 【 2. RDBMS 术语 】Table 表Filed 域/字段Column 列Record 记录NULL 空值Constraint 约束数据的完整性范式 【 3. 数据库基本操作函数 】3.1 SHOW DATABASES 显示数据库3.2 CREATE DATABASE 创建数据库3.3 ALTER DA…