Learning C++ No.29 【右值引用实战】

news2024/11/24 9:16:14

引言:

北京时间:2023/6/7/9:39,上午有课,且今天是周三,承接之前博客,今天我又去帮我舍友签到早八,但愿这次不会被发现吧!嘻嘻嘻!并且刚刚发文有关对C++11相关知识,由于所剩时间不多,这里我们就简单的为下篇博客,当然也就是该篇博客打一打铺垫,哦!对了,今天是高考哦!对于2022年的今天,哈哈哈,不多做赘述,往事不堪回首,把握今朝最重要,当然承接上篇博客,该篇博客最重要的知识就是有关右值引用的知识,当然不是右值引用的语法,最为重要的是对右值引用真实编码场景的分析和模仿,当然还有为什么要使用右值引用等相关知识,这里不多做细讲,反正右值引用非常重要就对头啦!

在这里插入图片描述

什么是右值引用

简简单单,想要学习什么是右值引用,最好的方法,还是如我们以前所说,要么通过图示来学习,要么通过对比或者模仿的方式来学习,当然,此时我们选择对比的方式,把问题归结到什么是左值引用,所以什么是左值引用?使用左值引用的目的又是什么呢?想要搞懂这些问题,如下所述:

什么是左值
当然,在我们想要搞懂什么是左值引用之前,首先肯定要明白什么是左值,谈到左值,大家可能会十分的陌生,但是,还是同理,如果使用另一个名称去和它进行对比,那么理解左值不过是砍瓜切菜那么简单,如下图所示:就是一些列的左值
在这里插入图片描述
当然,如上图所示,变量 p/a/b 表示的就是左值 ,此时对左值我们就有了一个最简单的认识,左值就是在赋值符号左边的变量,当然,此时只是简单认识,具体认识和理解需要等我们将右值的定义给理解一下,明白了什么是左值之后,此时理所当然,就应该学习什么是左值引用,当然,本质上左值引用的概念在我们之前C++入门学习类和对象时,在学习引用这个知识点的时候,我们就已经充分了解过了,所以左值引用本质就是我们之前学习的引用,当然字面意思理解,也就是对左值取别名,以下就是一系列对左值取别名代码,也就是我们之前学习的有关引用的语法使用,如下:
在这里插入图片描述

正式学习右值引用

在上述知识的铺垫下,此时我们知道了什么是左值,什么是左值引用,左值引用的本质就是我们之前在类和对象中学习的有关引用的知识,所以此时明白,左值引用在C++编码中是至关重要的,在合适的位置使用,它就可以最简单粗暴的方式提高代码效率,如:在返回值处使用引用,在函数参数中使用引用,在模板参数中使用引用等方面,引用都可以很好的减少数据频繁的拷贝,从而提高代码效率,所以同理,先不管什么是左值引用,什么是右值引用,使用引用的本质目的,都只是为了提高代码效率而已;正式了解右值引用相关知识,明白上述知识,知道左值引用的学习是在吃回锅肉,但右值引用相关知识,我们却实实在在的是第一次了解,同理,第一点,明白什么是右值,如下图所示:
在这里插入图片描述
明白,右值也是一个数据的表达式,如:字面常量,表达式返回值,函数返回值等…,总而言之,右值是一个可以生成临时对象的表达式或者是一个不可以被修改的值 ,具体如何辨别右值和左值,等我们将右值引用讲解完,再做进一步区分,明白什么是右值之后,顺理成章,此时学习右值引用相关知识,虽然本质上理解,右值引用就是对右值取别名,但和左值引用在语法使用方面还是有一定区别,如下图所示:
在这里插入图片描述
如上图所示,此时右值引用和左值引用在语法使用上并不相同,使用左值引用我们只需要在类型之后,对象之前添加一个取地址符号就行,而右值引用,我们则需要添加两个取地址符号,首先这就是右值引用和左值引用最大的一个区别,并不是说对右值使用引用符号(&)那么就是右值引用,对左值使用,则就是左值引用,当然这样设计最大的好处就是能让我们很快的分清,那个变量是左值引用变量,那个变量是右值引用变量,从而区分具体代码的编写和使用,明白了这些之后,下述就进行对什么是左值,什么是右值进行特别区分

综上所述: 左值是在内存中有实际地址,能随时随地获取到其地址并且允许直接赋值和修改的变量和对象,而右值则是一个可以产生临时对象的表达式,在内存中没有实际的地址,不能直接获取到地址,也不支持赋值和修改,总的来说,通过一个值是否支持取地址操作,我们就可以判断一个值是左值,还是右值

左值引用和右值引用分析

当我们了解了什么是右值和什么是右值引用之后,此时想必大家都疑问,平时在编写代码的时候,好像也没注意,反正无论是左值还是右值,我们都可以使用左值引用表示,那么这是个什么情况呢?此时想要彻底明白如何在编码时,区分左值引用接收左值,还是左值引用接收右值,亦或者是右值引用接收左值,还是右值引用接收右值,如下分析所示:

1.左值引用是否可以给左值取别名
当然由于这个是吃回锅饭,这里的答案肯定是可以,因为平时我们用引用用的最多的就是左值引用给左值取别名,如下图所示:
在这里插入图片描述
2.左值引用是否可以给右值取别名
相信刚接触这个问题,你肯定是犯迷糊,所以到底行还是不行呢?让我们通过代码说话,如下图所示:
在这里插入图片描述
很显然,编译器不允许我们编写这样的代码,那么原因是什么呢?其实本质就是我们之前在学习引用时,谈到的有关权限放大问题,因为如上代码中,无论是a+b,还是常量10,它们本质都具有常属性,如果此时支持这种写法,让ref1和ref2成为了这些具有常属性数据的别名,那么就会导致修改ref1或者修改ref2,就可以将具有常属性的值进行修改,但,在C++语法中规定,具有常属性的值是一定不允许被修改的,所以导致冲突,编译器不允许该写法,所以左值引用不允许给右值取别名 ,但,同理,如果加上const,那么左值引用就允许给右值取别名,如下代码所示:
在这里插入图片描述
所以总的来说,左值引用并不允许给右值取别名,但是const左值引用允许给右值取别名

3.右值引用是否可以给右值取别名
这个位置闭着眼睛我都知道可以,不然右值怎么取引用,哈哈哈,你说有没有道理,具体不多说,如下代码所示:
在这里插入图片描述
当然值得一提的是,此时不仅仅是x+y表示的是一个右值,连x和y进行运算完之后得到的值本质也是一个右值,因为运算完之后得到的是一个临时变量,虽然该临时变量在内存中有地址,但是由于它是由x+y这个右值进行初始化或者说我们并拿不到这个地址,所以此时该临时变量还是一个右值(关键

4.右值引用是否可以给左值取别名
哈哈哈,简简单单,类比左值引用给右值引用取别名,此时我首先明白,取肯定是可以取,但是肯定没有那么简单,哈哈哈!同理,本质还是左值和右值的区别,右值是一个没有名称,不可寻址的对象,而左值是一个实实在在的地址,如果此时右值可以成为左值的别名,那么就会导致右值可以被取地址,但是右值和左值最大的区分就是右值不允许被取地址,那么此时就会导致冲突,所以编译器肯定是不支持右值引用给左值取别名的,这里不多做演示;同理,此时编译器肯定有一定的解决方法,从而让右值引用可以给左值取别名,那就是使用move接口,在我看来,move接口的本质就是进行资源的转换,也就是对使用move接口的左值进行资源转移,从而让我们不能再直接获取到对应左值的地址,当然也就是将左值间接转换成了一个右值(重点),具体使用如下图所示:
在这里插入图片描述

右值引用真实使用场景

搞定了上述什么是右值引用和右值引用的具体使用方式,此时我们就正式走进代码,看看右值引用具体在那些方面可以大放异彩,可以让我们的代码在无形中效率得到提高,首先同理左值引用,右值引用也可以作为输出型参数使用,如下代码所示:
在这里插入图片描述
此时发现,由于函数参数一个是左值引用,一个是右值引用,所以此时两个函数可以构成函数重载,并且,我们不仅可以使用左值传参,也可以直接使用右值进行传参,所以右值引用在编码过程中带来的第一个好处就是让我们可以直接使用右值进行传参,当然在没有右值引用之前,我们使用const左值也能完成,但是两者的区别非常大,具体等我们将右值引用具体的使用场景分析完毕,我们再进行详细讲解,下述我们就再来看看右值引用在其它方面有什么显著作用,如下:

1.移动拷贝

文章讲解到这里,才真正算右值引用登堂入室的一个环节,当然也就是该篇博客的重点,具体下述内容,我们都将讲述有关右值引用在完美转发、移动拷贝和移动赋值方面的知识,当然这些知识也就是我们使用右值引用实现C++高效代码的关键,所以此时我们就正式来看看什么是移动拷贝,如下代码所示:

在这里插入图片描述
上图就是有关使用右值引用实现移动拷贝的经典代码,并且从代码中,我们就可以看出,移动拷贝和深拷贝本质的区别就是是否需要再次开空间,那么此时我相信大家肯定有一个疑问,那就是为什么左值必须要执行深拷贝,而右值却可以不需要执行深拷贝,而是执行移动拷贝呢?并且移动拷贝具体是什么?接下来就让我来为大家解惑,如下:

什么是移动拷贝
首先想要搞懂移动拷贝,那么最重要的一点就是搞懂,为什么可以进行移动拷贝,想要搞懂为什么可以进行移动拷贝,本质还是需要明白与右值相关的知识,此时我们就知道,当一个右值是自定义类型,那么该右值也被称为将亡值,当一个右值是内置类型,该右值也被称为纯右值,为什么自定义类型的右值被称为将亡值呢?原因就在于该右值是一个临时变量,我们无法通过名称获取该变量的地址,并且它的所有资源都是临时性的,简单理解也就是即将被析构的资源,明白了这点之后,一切都迎刃而解,编译器本着效率优先原则,它就允许我们在使用右值的时候,通过右值引用符号为标示(&&),进行移动拷贝,因为如果当一个值为右值,且为将亡值,本来就需要被析构的临时变量,让它去执行拷贝构造,构造出一块新空间,并且在构造完新空间之后,它自己又被释放掉,那就等于是在脱裤子放屁,多此一举 ,虽然不是不行,但是本着效率优先原则,编译器就支持了移动拷贝的语法(前提被拷贝对象是一个将亡值,也就是自定义类型的右值)
所以此时我们就明白了什么是移动拷贝,移动拷贝就是将被拷贝对象中已分配的内存和内存中存储的数据等资源转移给目标对象,如下图所示:
在这里插入图片描述
所以从上图可以看出,如果我们将右值(将亡值)识别成执行移动拷贝,那么该程序就可以减少一次深拷贝,一次析构,从而提高代码执行效率,优化资源管理方式

总: 之所以左值不可以执行移动拷贝,而右值可以执行移动拷贝,就是因为左值是一个有对应名称、地址、并且我们可以直接获取到对应地址的变量或者对象,所以该变量或者对象可以在程序中的任何函数中被使用,如果我们将它进行资源转移,那么就会导致使用该变量或者对象的代码块出现错误或者异常(本质就是两个指针指向了同一块空间,析构时导致该空间析构了两次,导致其中一个指针出现野指针),所以左值坚决不允许使用移动拷贝, 而右值可以使用移动拷贝的原因,还是同理,且右值通常表示的就是将要被销毁或者不再使用的临时对象,我们不能获取到对应的地址,所以可以直接进行移动拷贝,当然也就是资源转移

注意: 明白了上述总括的知识,和上述有关右值引用对左值取别名的知识,此时我们知道,move虽然可以让右值引用对左值取别名,但本质上还是将对应的左值对象变成了一个右值,进而才支持右值引用对左值取别名的操作,所以明白,使用move将一个左值变成右值,是存在风险的,一不小心就会出现问题,在使用move时一定要保证该左值在别的代码块中没有被使用,否则就不允许其调用move接口,反正move接口需要谨慎使用

移动拷贝具体使用场景
想要看看移动拷贝具体的使用场景,首先我们要回顾一下有关使用左值引用的知识,比如:使用左值引用的前提是被返回对象出了函数作用域不会随着栈帧的销毁而销毁,明白这点,此时我们就知道,如果返回对象是一个局部对象,那么此时我们就不可以使用引用返回,就只能通过创建临时变量传值返回,这样就会导致效率较低,因为需要多执行一次拷贝构造和析构,但如果此时我们能将这种场景进行优化,优化成直接调用右值引用的构造函数,执行移动拷贝,那么就可以大大提高代码执行效率和优化资源管理方式,具体如下图所示:

无右值引用之前,C++98,传值返回具象图:
在这里插入图片描述
从图中可以看出,如果没有右值引用类型的构造函数,那么就只能走拷贝构造,虽然在编译器优化之后,两次拷贝构造被优化成了一次,但是如果对应局部对象在堆区上的数据非常多,或者说该对象是一个嵌套类型,那么就会导致执行一次拷贝构造的成本变得非常大,所以在C++11中,为了解决这个问题,就提出了右值引用,进而推出了移动构造的想法,具体如下图所示:
在这里插入图片描述
在将返回值str识别成一个右值的情况下,可以看出,此时可以将传值返回给优化到极致,直接变成调用一次移动拷贝就能完成,可以想象,效率得到了极大的提升,并且因为此时str本身就是作为一个返回值,也就是即将被销毁的值,将它从左值识别成右值本质上并不会带来任何的负面效果(类似于就是调用了move接口),所以这种编码方式是极其完美的,直接将代码执行效率和资源管理方式拉满

并且明白,在C++11中STL的所有容器,都增加了移动构造语法,本质也就是右值引用的拷贝构造函数,如下图所示:
在这里插入图片描述
发现s1的资源,因为在初始化s3对象时,编译器识别到s1是一个右值,所以直接执行移动拷贝,导致s1的资源直接被转移到了s3对象中

最终明白,STL中的容器不仅仅是只有拷贝构造函数增加了右值引用调用移动拷贝的写法,在其它许多的接口中,都有涉及到与右值引用调用移动拷贝,例如插入接口,如下:
在这里插入图片描述
可以发现,在插入数据时,当编译器识别到该值是一个右值,那么它就一定会去调用与之最匹配的右值插入接口,从而调用移动构造的写法,而不再像是左值写法,先去调用拷贝构造初始化该结点,然后再将该结点插入到对应的容器中,而是直接将对应的右值进行移动构造,也就是资源转换,然后直接插入对应的容器中,如下图所示:
在这里插入图片描述
注意:移动构造不敢不清楚,上述不仅有代码,还有讲解,本质就是一个swap,对指针进行交换而已

2.完美转发

搞定了上述有关移动构造的知识,此时我们就来学习一下右值引用的另一个大知识点,完美转发,同理,在搞懂什么是完美转发之前,我们首先要明白,什么是万能引用,如下图所示:
在这里插入图片描述
从图中,我们可以看出,由于此时我们使用了模板参数,所以此时 PerfectForward() 接口不仅可以接收左值,而且也可以接收右值,此时该接口就被我们称之为万能引用接口,搞懂了什么是万能引用,接下来我们就可以正式进入完美转发的学习,如下:

什么是完美转发
想要搞懂什么是完美转发,首先我们应该搞懂为什么要有完美转发,只要懂了为什么有,那么完美转发是什么自然不攻自破,并且想要搞懂为什么要有完美转发,那么同理,要搞懂其它相关知识,所以此时我们依旧先探讨一下,有关左值和右值的转变,如下图所示:
在这里插入图片描述
如图,此时我们发现,当一个右值作为参数进行传参时,相应函数接口在接收该值时,会将该值识别成左值,具体原理类似于我们使用右值去调用移动构造再去初始化对应的左值,同理被初始化的变量就类似于接收右值的那个参数,本质上都是一个可以直接获取到相应地址的左值,明白了该点之后,搞懂为什么要有完美转发,等于喝汤吃菜,如下:

明白上述知识之后,此时就可以抽象类比一下,假如此时你调用一个插入数据的接口,但是该接口并不是完整代码实现,而是使用自己对应的参数去复用另一个接口,那么此时根据上述原理,无论是右值还是左值,只要进行了参数传递,那么就都会变成一个左值,此时就可以想象到,如果你的参数从右值变成了左值,那么在之后的函数调用中,无论是拷贝构造还是其它接口,都只能被识别成左值,导致最终只能调用相关左值引用实现的函数接口,无法达到预期目标(想要通过右值引用去调用移动构造),如下图所示:
在这里插入图片描述
上述代码中唯一值得 注意 的是,我们此时string类是作为一个模板类别调用,所以在初始化list结点的时候,本质是在初始化string,所以具体执行拷贝构造还是移动构造本质是还是由string的拷贝构造函数是左值引用还是右值引用决定

使用完美转发解决上述问题
所以当我们碰到了上述问题,也就是一个右值被提前转换为了左值,此时的解决方法就是使用完美转发,本质就是让我们的右值一直保持右值属性,不被替换成左值,关键字:forward,具体如下图所示:
在这里插入图片描述

上述就是有关完美转相关的知识,具体就这样啦!

总结: 为了可以在晚上9点之前更新这篇博客,现在肚子空空,现在唯一的想法就是赶紧码完这最后几个字,然后出门去觅食,哈哈哈,总的来说右值引用、移动构造和完美转发这些知识,So,So,哈哈哈,反正我还没找到我写完博客觉得很难懂的知识点,哈哈哈,估计这就是博客的魅力吧!没有写博客搞不定的东西,啦啦啦啦!剩下有关右值引用的知识,下篇博客见,See you!

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

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

相关文章

Day_47选择排序

目录 一. 选择排序的实现 1. 简单选择排序 2. 性能分析 二. 代码实现 1. 核心代码 三. 代码展示 四. 数据测试 五. 总结 一. 选择排序的实现 1. 简单选择排序 选择排序的基本思想是:每一趟(如第i趟)在后面n-i1(i1,2,3...n-1&a…

Java 面试题:Spring,Spring MVC,Spring Boot 之间什么关系?

来,先和我看张图: Spring全家桶了为了解决不同场景的问题,逐渐演化出多套生态环框,如:Spring、SpringMVC、SpringBoot、SpringCloud。 Spring MVC和Spring Boot都属于Spring,Spring MVC是基于Spring的一个…

【每日算法】【168. Excel表列名称】

☀️博客主页:CSDN博客主页 💨本文由 我是小狼君 原创,首发于 CSDN💢 🔥学习专栏推荐:面试汇总 ❗️游戏框架专栏推荐:游戏实用框架专栏 ⛅️点赞 👍 收藏 ⭐留言 📝&…

Vue-ECharts使用说明

Vue-ECharts使用说明 vue-echarts 是在Apache Echarts官网的echarts使用方法上二次封装的组件,方便我们创建echarts图应用到我们的项目中。 参考: vue-echarts 官网:https://github.com/ecomfe/vue-echarts Apache Echarts 官网:h…

推荐一款比Flink CDC更好用的免费CDC工具

很多中大型企业都希望选择一款足够轻量好用的CDC工具,而且最好是小白用户都能使用的CDC工具,今天就推荐一款小白都能安装并立即使用的CDC工具给大家。 CDC(Change Data Capture)是一种用于捕获和传递数据库实时变更的技术。它允许…

怎样通过大数据获客?

零售商知道他们需要大数据,并且正在向前冲锋以进入游戏。但许多零售商继续面临挑战。应该收集什么类型的数据?应如何使用数据来生成见解?如何衡量投资回报率? 101data最近对美国各种规模的零售商进行了调查。当被问及哪些流程将受…

YOLOv5/v7 添加注意力机制,30多种模块分析②,BAM模块,CBAM模块

目录 一、注意力机制介绍1、什么是注意力机制?2、注意力机制的分类3、注意力机制的核心 二、BAM模块1、BAM模块的原理2、实验结果3、应用示例 三、CBAM模块1、CBAM模块的原理2、实验结果3、应用示例 大家好,我是哪吒。 🏆本文收录于&#xf…

测试之路-我曾经跨过无数Bug,也怼过各大佬开发

前言: 这是我从事测试的第五个年头的开端,忙忙碌碌到头发现好像忙了个寂寞。也忘了最初走向这条不归路的初心是什么。当时14年学完计算机出来找工作,看着茫茫人海,第一反应就是退缩,该找什么工作?开发&…

sparkSQL的使用

sparksql只能处理结构化数据 基于rdd构建dataframe对象 from pyspark.sql import SparkSession from pyspark.sql.types import StructType, StringType, IntegerTypeif __name__ __main__:spark SparkSession.builder.appName(test).master(local[*]).getOrCreate()sc spa…

JS-Object无序问题

1、 背景 在开发图表功能时,由于历史原因,后端返回的图表数据如下: 是对象类型,键为日期,值为日期和当天日期的值。在H5端、微信小程序端运行结果正常,结果到了百度小程序突然发现,这个值的顺…

IDEA2021.1.3版本lombok插件--代码爆红问题解决

最近,突然心血来潮将自己工作中开发使用的idea升级到2021.1.3版本,安装成功后,打开代码发现有关get、set方法全部爆红,不用想,肯定是Lombok插件问题,通过setting->plugin里面搜索Lombok居然不存在&#…

终于有人把Linux系统收发网络数据包的过程讲清楚了!

Linux 服务器收到网络数据包,需要经过哪些处理,一步步将数据传给应用进程的呢?应用进程发送数据包时,Linux 又是如何操作将数据包发送出去的呢?今天我们就来聊聊这个话题。 在准备好接收网络数据包之前,Lin…

Mysql数据库入门基础篇--sql语句简单使用

Mysql数据库入门基础篇--sql语句简单使用 🔻一、数据库创建、删除、选择1.1 🍃 create database 创建数据库1.2 🍃 使用 mysqladmin 创建数据库1.3 🍃 drop 命令删除数据库--一般不建议在数据库执行delete、drop等命令1.4 &#x…

redis入门学习

redis基本数据结构 redis的返回值 在设置一个key-value对的时候通常会返回ok告诉我们操作成功了,1代表成功,0代表失败,通常会根据返回值的不同处理不同的业务逻辑用redis.cn来查看命令 全局操作 flushdb清空内存数据库keys *展示所有存储…

第十八章:MySQL8其他新特性

第十八章:MySQL8其他新特性 18.1:MySQL8新特性概述 ​ MySQL从5.7版本直接跳跃发布了8.0版本 ,可见这是一个令人兴奋的里程碑版本。MySQL 8版本在功能上做了显著的改进与增强,开发者对MySQL的源代码进行了重构,最突出…

2023-06-08 Unity AssetBundle1——AB包介绍与使用

文章目录 一、AB 包介绍二、AB 包资源打包(一)导入 AB 包(二)将资源关联 AB 包(三)打包参数选项(四)打包结果(五)AB 包信息 三、加载 AB 包资源(一…

如何让访问者能更快地加载出你的网站?

​  在当今互联网时代,网站已成为人们获取信息、交流互动、进行商业活动等的主要场所之一。然而,由于网络环境的复杂性和不确定性,用户在访问网站时常常会遇到访问缓慢、卡顿等问题,从而影响了用户的使用体验。为了让用户更快地…

【数据结构】常见排序算法——快速排序的三种实现、 hoare版本、挖坑法、前后指针版本

文章目录 1.常见排序2.快速排序2.1hoare版本2.2快速排序优化2.3挖坑法实现2.4前后指针实现 1.常见排序 2.快速排序 快速排序(Quick Sort) 是一种常见的排序算法,也是一种基于分治算法的排序。该算法的基本思想是将一个数据集分成两个子集&…

实验四、shell编程

一、实验目的 1.了解shell的特点和主要种类。 2.掌握 shel1 脚本的建立和执行方式。 3.掌握bash的基本语法。 4.学会编写shell 脚本。 二、实验内容 shell 脚本的建立和执行。历史命令和别名定义。shell变量和位置参数、环境变量。bash的特殊字符。一般控制结构。算术运算及…

Redis事务和管道

一、Redis事务 1、定义 可以一次执行多个命令,本质上是一组命令的集合。一个事务中的所有命令都会序列化,按顺序的串行化执行而不会被其他命令插入,不能加塞。 2、作用 一个队列中,一次性、顺序性、排他性的执行一系列命令。 …