C++笔记-list

list即是我们之前学的链表,这篇主要还是讲解list的底层实现,前面会讲一些list区别于前面string和vector的一些接口以及它们的注意事项。

一.list的基本使用

和之前的string,vector一样,有很多之前见过的一些接口,经过前面两篇的讲解,很多接口不用讲解相信大家都会使用,主要就是Operations中的一些接口之前没有讲过,前面主要就是讲解这些接口。

这里主要讲splice,remove,unique和sort:

1.1splice:
splice的作用是将一个列表中的内容拷贝给另一个链表。

可以看出splice有三种传参的方式,每种方式都会产生不一样的结果:
1.1.1第一种传参

第一种传参中的两个参数:第一个参数就是从目标位置开始拷贝数据过去,第二个参数代表某个链表。

第一种传参方式就是把第二个参数中链表的所有数据都拷贝到第一个参数之前,结果如第二张图所示,lt1的所有数据都被拷贝到lt2的begin位置之前。

1.1.2第二种传参

第二种传参比第一种多了一个参数,意思就是把具体某个位置的值拷贝到目标位置之前,就比如上图所示的将lt1中的第一个数据拷贝过去。

1.1.3第三种传参方式

第三种传参就是把某个范围的数据拷贝过去,就比如上图中将lt1中的4到6拷贝过去。

这里可能会有人疑惑:为什么第三个参数直接写lt1.begin()+3呢?这样写不是更简洁吗?

这个问题我下面讲sort时会讲,这里先跳过。

splice还有一种玩法就是可以将自己的数据拷贝到某个位置之前,就比如将最后一个数据拷贝到第一个位置:

这样就将6放到了第一个位置,这种用法是要比前面的用法更为普遍的。

1.2remove

list中remove作用是将链表中所有和你要删除的值相同的数据一并删除。

如上图所示,这里就把所有的2都给删除了,remove的基本用法就是如此。

1.3unique

unique的作用就是删除一个有序链表中重复的数据。

通过unique就可以把重复的数据给删掉。

注意:一定要是有序的,如果不是有序的就无法做到删除重复的数据。

此时链表数据不是有序的,unique就没有发挥作用。

1.4sort

sort的作用相信就不用多说大家都知道,但是在这里我们要思考两个问题:

1.string和vector都没有实现sort接口,为什么list要单独实现?

2.list实现的sort效率怎么样?

先来解释第一个问题:
这个问题的答案也能解释上面splice出现的疑点,这就涉及到迭代器的分类。

迭代器分为三类(移动迭代方式):

1.单向迭代器,如:forword_list/unordered_map...   ++

2.双向迭代器,如:list/map...   ++/--

3.随机迭代器,如:string/vector/deque.. ++/--/+/-

后面就是它们能够进行的移动迭代方式,而语法库中的sort参数是随机迭代器,list都是双向迭代器,所以不能使用语法库中的sort,而上面的splice出现的疑点也是因为双向迭代器不能+/-,所以不能那样使用。

中间蓝色的单词的意思就是双向迭代器。

这是string和vector的随机迭代器。

这是语法库中的sort的参数要求,上去就要求随机迭代器,所以list才需要自己实现。

而迭代器的分类和之前讲的权限放大和缩小的概念有些类似,就是如果函数参数要求是双向迭代器,此时我们传随机迭代器也是可以的,相当于权限的缩小,同理单向迭代器也是如此。

双向迭代器和随机迭代器就是特殊的单向迭代器。

再来说第二个问题:
我就直接说结论,list自己实现的sort效率不高,比起语法库中的sort效率低了很多。

不过在数量较少时,效率差不多,但是在数量较大时效率就会低很多,举个例子:

在数量较大时,把list中的数据拷贝到vector,调用语法库中的sort,再把数据传回list的效率都比直接调用list本身的sort高。

二.list的底层实现

2.1list框架的实现

链表我们之前在数据结构阶段都了解过,链表要把整个链表和单个结点区分开,所以这里也是同理。

这里定义哨兵位,相信大家都知道底层list其实就是双向带头循环链表。

可能有人会疑惑:为什么单个结点要用struct而不用class?

其实我们在实际应用中,想让别人使用的用class,不想别人访问的用struct,我们只想让别人用链表整体,而不能直接访问我一个结点中的变量。

在单个结点类中运用初始化列表进行初始化,这样在list类中初始化哨兵位时就不需要传数据,直接创捷一个哨兵位,并让prev和next都指向自己即可,和之前讲链表是一样的。

2.2push_back尾插

思路和之前讲链表是一样的,创建一个节点,并更新哨兵位,原尾节点和新建结点的next和prev即可。

2.3iterator迭代器的实现

之前在string中讲过迭代器并不就一定是指针,在list中印证了。

为什么在list中要单独实现iterator迭代器呢?

因为在之前的string和vector中,它们两个底层都是数组,它们的原生指针就能完成解引用和++等操作,而链表我们知道解引用是访问一个结点,而一个结点不止有数据,还有prev和next,而迭代器我们要求解引用就是直接拿出其中的数据。

包括++等操作,链表底层并不是数组,每个结点的地址并不是连续的,所以通过++等操作是不能找到相邻的结点的。

基于以上两点,所以在list终究要我们自己实现iterator迭代器,而自定义类型的迭代器其实执行的还是指针的作用,所以成员变量依旧还是单个结点的地址。

其中里面所实现的各种比较我就不过多赘述了,之前都讲过,这里主要讲解一下为什么多加了一个类型Ref。

因为不仅有iterator,还有const_iterator,两者的区别无非就是解引用的值能不能修改的问题,但是如果我们没有多加Ref这个类型,那我们是不是还要写一个函数重载?

但是从之前学过的函数重载中我们知道,是不能以返回值不同而进行函数重载的,程序会报错,

那要解决这个问题就只能在单独写一个const_iterator类,内容和iterator一样,除了解引用符号的重载这一个不同而已。

这样写代码就太冗余了,有很多重复代码,但是我们加一个类型Ref就可以解决这个问题,根据传过去的参数不同将其分为iterator和const_iterator:

可能有人会疑惑:iterator类不需要实现析构函数吗?

我们所实现的iterator只是用于解引用和遍历链表而已,不能使用后直接把这个节点给销毁了,所以不能实现析构函数,相应的也不需要实现拷贝构造函数。

上面的结点类也是一样,析构操作只需要在list类中实现即可。

另外其中的!=,==等符号的重载是因为现在iterator是自定义类型,不再是原生指针,所以如果要比较自定义类型,就要重载相应的符号。

2.3begin和end

实现了迭代器iterator,接下来就要实现begin和end,也是分为普通和const版本,注意返回值是需要调用iterator类并进行传参的,头结点是哨兵位的下个结点,end返回就是哨兵位。

2.4insert

insert实现逻辑和之前一样,找到目标结点的prev和next,并更新三个结点的next和prev即可。

2.5erase

 

相信现在看到这个返回值大家就知道erase会出现什么问题了,没错,还是迭代器失效的问题,所以还是要在erase后更新指针。

需要注意的地方有两个:

1.目标结点不能是哨兵位

2.删除当前结点后,要更新指针的位置,指向下个结点

2.6push_front,pop_back和pop_front

这三个包括之前实现的push_back就都可以通过复用insert和erase来实现,要注意的就是尾删不能直接传end,要让end--才是尾结点的位置。

2.7clear

clear的作用就是销毁除哨兵位之外的所有结点,实现起来也较为容易,遍历整个链表挨个销毁即可。

2.7拷贝构造函数

实现拷贝构造函数也是为了深拷贝做准备,一样也要创建哨兵位,不过这和构造函数的代码一样,所以其实可以将这段代码封装成一个函数也可以,这里因为就几行代码所以我就没整。

下面就是遍历目标链表,把相应的节点push_back进去即可。

2.8=符号重载

以及是需要用到std标准库中的swap函数进行操作,最后=符号重载复用即可。

我们再看最后一种情况:

当传进去的参数类型是自定义类型时,我们要访问其中的数据要用到->符号,但是vector是可以直接用的,因为它用的是原生指针,而list不能使用,因为是自定义类型迭代器,所以在这里我们要自己实现->符号。

和上面的解引用重载一样,这里我们引进一种类型Ptr,代表要输出的数据的地址,所以也分为普通和const版本,作用也就和vector原生指针作用是一样的。

此时就不再会报错,但是可能有人会觉得很奇怪:不应该是两个箭头吗?

第一个箭头返回的是指针,应该还有一个箭头才能指向数据啊。

这个想法没错,出现这现象的原因是为了可读性高,编译器把另一个箭头给省略了。

以上就是list的内容。

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

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

相关文章

unityTEngine学习记录2

上一篇了解了下载项目与外部调用的接口,接下来就继续学习根据这个框架来加载场景首先打开te官网,进入教程。 了解框架目录以及功能 首先要了解的就是这个框架的文件结构目录,知道他都是干啥的,在官网的目录结构中介绍了其中重要…

逻辑过期怎么设计

设计“逻辑过期”通常用于缓存、令牌管理、数据有效性验证等场景,其核心是通过业务逻辑判断数据是否过期(而非单纯依赖物理时间)。以下是设计逻辑过期的关键思路和实现方案: 1. 核心思想 物理过期:基于固定的时间&…

DAY 47 leetcode 232--栈与队列.用栈实现队列

题号232 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作&#xff08;push、pop、peek、empty&#xff09;&#xff1a; class MyQueue {Stack<Integer> stackIn;Stack<Integer> stackOut;/** Initialize your data structure here. */pu…

逻辑回归 (Logistic Regression)

文章目录 逻辑回归 (Logistic Regression)问题的引出Sigmoid function逻辑回归的解释决策边界 (Decision boundary)逻辑回归的代价函数机器学习中代价函数的设计1. 代价函数的来源&#xff08;1&#xff09;从概率模型推导而来&#xff08;统计学习视角&#xff09;&#xff08…

关于C语言的模拟物理模型

声明&#xff1a;本文全部代码效果基于C语言easyx图形界面库。 引言 关于很多游戏和模型的开发&#xff0c;都需要模拟真实的物理模型 比如&#xff1a;基本矢量运动模型&#xff08;位移&#xff0c;速度&#xff0c;加速度&#xff09;&#xff0c;重力模型&#xff0c;碰撞…

C++编译与链接:从源码到可执行文件的魔法之旅(Visual Studio实践)

文章目录 **C++编译与链接:从源码到可执行文件的魔法之旅(Visual Studio实践)****一、C++编译器的工作流程****二、Visual Studio环境配置实战****三、示例项目:Hello World全流程解析****四、高级技巧与工具链****五、总结与参考资料**C++编译与链接:从源码到可执行文件的…

现代C++的范式演进与工程实践深度解析(本文序号不知道怎么整的,有点问题)

引言:C++的复兴时代 在经历了"已死语言"的质疑后,现代C++正迎来前所未有的复兴。据2024年TIOBE指数显示,C++以8.33%的占比稳居第三,较2020年上升2.1个百分点。这种复兴并非偶然——随着C++20标准的全面落地和C++23特性的逐步实现,这门已有40年历史的语言正在系…

通过gird布局实现div的响应式分布排列

目标&#xff1a;实现对于固定宽度的div盒子在页面中自适应排布&#xff0c;并且最后一行的div盒子可以与前面的盒子对齐。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" con…

WSL2-Ubuntu22.04安装URSim5.21.3

WSL2-Ubuntu22.04安装URSim5.21.3 准备安装启动 准备 名称版本WSL2Ubuntu22.04URSim5.21.3VcXsrvNaN WSL2安装与可视化请见这篇:WSL2-Ubuntu22.04-配置。 安装 我们是wsl2-ubuntu22.04&#xff0c;所以安装Linux版本的URSim&#xff0c;下载之前需要注册一下&#xff0c;即…

产品研发项目管理6大痛点

在产品研发项目管理实践中&#xff0c;企业普遍面临六大系统性挑战&#x1f937;‍♀️&#xff0c;直接影响研发效能与战略目标达成&#x1f514;&#xff0c;具体表现为&#xff1a; ① 产品需求管理不完善&#xff1a;需求与市场脱节&#xff0c;需求不明确、需求变更频繁…

计算机网络基础概论

计算机网络基础概论 目录 一、网络基本概念 1.1. 网络 1.2 互联网 1.3 ip地址 1.3.1 作用 1.3.2 分类 1.4 MAC地址 1.4.1 MAC地址与 IP 地址的关系 1.5 网络协议 二、网络分层模型 2.1 物理层 2.2 数据链路层 2.3 网络层 2.4 传输层 2.5 会话层 2.6 表示层 2.7…

Windows下导入文件中的环境变量

在Windows批处理脚本&#xff08;.bat&#xff09;中&#xff0c;通过文件获取并设置环境变量通常涉及逐行读取文件内容并动态赋值给变量。以下是具体实现方法及示例&#xff1a; 一、从文件读取变量并设置到环境变量 假设有一个配置文件&#xff08;如env_config.txt&#xf…

WebSocket 实现数据实时推送原理

WebSocket 实现数据实时推送的核心机制在于其全双工通信能力和持久的连接特性。以下是其工作原理的详细步骤&#xff1a; 1. 握手阶段&#xff08;HTTP 升级协议&#xff09; 客户端发起请求&#xff1a;通过发送一个带有特殊头部的 HTTP 请求&#xff0c;请求协议升级。 GET …

Linux操作系统学习之---进程状态

目录 明确进程的概念: Linux下的进程状态: 虚拟终端的概念: 见一见现象: 用途之一 : 结合指令来监控进程的状态: 和进程强相关的系统调用函数接口: getpid()和getppid(): fork(): fork函数创建子进程的分流逻辑: 进程之间具有独立性: 进程中存在的写时拷贝: 见一见进程状态…

何小鹏在得意的笑

"小鹏汽车率先迈出了造车新势力出海一大步" 作者 | 魏强 编辑 | 卢旭成 4月15日&#xff0c;小鹏汽车在香港举行小鹏全球热爱之夜和2025首款全球旗舰小鹏X9上市发布会。 当小鹏汽车创始人何小鹏把香车X9交付给香港首批车主的时候&#xff0c;脸上露出经典的笑脸。…

@Autowird 注解与存在多个相同类型对象的解方案

现有一个 Student 类&#xff0c;里面有两个属性&#xff0c;分别为 name 和 id&#xff1b;有一个 StuService 类&#xff0c;里面有两个方法&#xff0c;返回值均为类型为 Student 的对象&#xff1b;还有一个 StuController 类&#xff0c;里面有一个 Student 类型的属性&am…

黑马商城项目(三)微服务

一、单体架构 测试高并发软件 二、微服务 三、SpringCloud 四、微服务拆分 黑马商城模块&#xff1a; 服务拆分原则&#xff1a; 拆分服务&#xff1a; 独立project&#xff1a; maven聚合&#xff1a; 拆分案例&#xff1a; 远程调用&#xff1a; package com.hmall.cart.…

PyTorch:学习 CIFAR-10 分类

&#x1f50d; 开始你的图像分类之旅&#xff1a;一步一步学习 CIFAR-10 分类 图像分类是计算机视觉中最基础的任务之一&#xff0c;如果你是初学者&#xff0c;那么以 CIFAR-10 为训练场是一个不错的选择。本文一步一步带你从零开始&#xff0c;学习如何用深度学习模型实现图…

3.学习笔记--Spring-AOP总结(p39)-Spring事务简介(P40)-Spring事务角色(P41)-Spring事务属性(P42)

1.AOP总结&#xff1a;面向切面编程&#xff0c;在不惊动原始基础上为方法进行功能增强。 2.AOP核心概念&#xff1a; &#xff08;1&#xff09;代理&#xff1a;SpringAOP的核心是采用代理模式 &#xff08;2&#xff09;连接点&#xff1a;在SpringAOP中&#xff0c;理解为任…

数据库-day06

一、实验名称和性质 分类查询 验证 综合 设计 二、实验目的 1&#xff0e;掌握数据查询的Group by &#xff1b; 2&#xff0e; 掌握聚集函数的使用方法。 三、实验的软硬件环境要求 硬件环境要求&#xff1a; PC机(单机) 使用的软件名称、版本号以及模块&#xff1a; …