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

引言:

北京时间: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!

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

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

相关文章

第八章 图像压缩

文章目录 第八章 图像压缩8.1基础知识8.1.1 编码冗余8.1.4图像信息的度量8.1.5保真准则8.1.6图像压缩模型8.17图像格式、容器和压缩标准 8.2一些基本的压缩方法8.2.1霍夫曼编码8.2.2Golomb编码8.2.3算术编码8.2.4LZW编码8.2.5行程编码8.2.6基于符号的编码8.2.7比特平面编码8.2.…

WebGIS 信息系统-系统实现

WebGIS 信息系统-系统实现 主界面基本模块地图基本管理模块地图属性主界面大纲视图实现创建选项菜单添加点击事件实现加载底图图层和定位图层完善这个类文件 主界面基本模块 (1 )平移:单击按钮将鼠标置于平移状态,可用来清除当前图形操作工具…

人工智能 | 技能人才评价证书分析及人工智能/计算机类证书推荐

目录 简介技能人才评价证书相关介绍1. 国家职业资格证书2. 职业技能等级证书 推荐证书目录1. 计算机类的国家职业资格证书a. 证书推荐b. “软高***”和“PMP”的区别 2. 计算机类的职业技能等级证书a. 工信部认证b. 中国人工智能学会证书 专业证书的作用1. 抵税2. 领取职业技能…

全志V3S嵌入式驱动开发(USB camera驱动)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 soc和mcu的一个重要区别,就是soc会涉及到大量的音视频操作,当然音视频也就包括了camera摄像头这部分。v3s本身支持csi接口和…

ChatGPT:开放AI平台的最新进展和功能

第一章:引言 在过去的几年中,人工智能技术取得了长足的发展,其在各个领域的应用也日益广泛。而在AI技术中,自然语言处理(NLP)一直是备受关注的领域之一。ChatGPT作为OpenAI的开放AI平台上的一项重要技术&am…

慕课:笔记

课程链接:直面JavaScript中的30个疑难杂症_JavaScript面试题-慕课网 第二章:数据类型 数据类型是每门编程语言的必修之课,你是否对JavaScript的数据类型和检测存在困惑,本章节将为你揭晓其中的奥秘,让你对数据类型有…

【Tableau案例】神奇宝贝属性及实力强弱|数据可视化

提前声明:神奇宝贝的数据分析仅供参考,不涉及对于神奇宝贝的各种评价,另外我是初学tableau,涉及到使用的tableau操作可能很简单,复杂的还掌握不熟练,之后会拿时间系统学习tabelau。 数据预处理 该数据集有…

CODESYS模拟量超限报警功能块

博途PLC模拟量超限报警功能块详细介绍请参看下面文章链接: PLC模拟量超限报警功能块_RXXW_Dor的博客-CSDN博客模拟量偏差报警功能块请参看下面文章:模拟量偏差报警功能块(SCL代码)_RXXW_Dor的博客-CSDN博客工业模拟量采集的相关基础知识,可以查看专栏的系列文章,这里不再赘…

OSPF故障定位没思路?照这篇抄就行

我的网工朋友大家好。 好久没聊OSPF技术了,相关基础且经典的内容,公众号陆陆续续分享过一些,趣味科普,面试考题,实验操作,都有涉及。 按照惯例,先给你整一波优质的往期内容: 《 5个…

Spring Boot之静态资源映射 | 超级详细,建议收藏

写在前面🔥 上几期,我们重点讲解了Spring Boot集成Swagger-UI实现在线API文档及美化UI界面,而这期,在 Web 应用中会都会涉及到大量的静态资源,例如 图片、JS、CSS 和 HTML 等。我们知道,Spring MVC 导入静态…

WebGIS 信息系统-主界面基本模块功能设计

文章目录 系统文件夹结构主界面地图基本管理模块功能设计手机定位模块功能设计经纬度路径生成功能设计导航模块功能设计用户管理模块功能设计 系统文件夹结构 为了使项目容易管理和维护,在开发之前需要确定项目的系统文件夹结构,即系统文件夹结构设计。…

C++ / QT 旅游产品管理系统

一、项目介绍 旅游产品管理系统 你是一家旅行社的 IT 主管,现在需要你设计并实现一个旅游产品管理系统。 1 ) 基本功能要求 * 实现基础界面: 参照现有的旅游产品管理系统:查看产品、选择产品、使用说明等内容 * 支持旅游产品…

用Python搭建监控平台详解

概要 监控和运维,是互联网工业链上非常重要的一环。监控的目的就是防患于未然。通过监控,我们能够及时了解到企业网络的运行状态。一旦出现安全隐患,你就可以及时预警,或者是以其他方式通知运维人员,让运维监控人员有时…

Springboot 核心注解和基本配置解读

目录 1. Springboot 入门与原理 1.1 Springboot 简介 1.1.1 什么是Springboot 1.1.2 Springboot 主要优点 1.2 Springboot 相关注解 1.2.1 元注解 1.2.1.1 Target 1.2.1.2 Retention 1.2.2 Configuration 1.2.3 Import 1.2.3.1 直接注入 1.2.3.2 实现 ImportSelector…

Error: Cannot find module ‘webpack‘ 问题解决办法

这句话的意思是:没有找到webpack模块。 就算之前你装了webpack,那肯定是非全局安装 所以要全局安装 npm install --save-dev webpack 问题解决

4.蜂鸣器

1.了解蜂鸣器: 蜂鸣器在使用3.3V无法驱动,需要借助到三极管的放大特性(IcβIb)将蜂鸣器驱动;本次原理图中,接R33电阻是因为当STM32单片机复位时处于浮空状态,防止小电流让蜂鸣器响而添加的。 2.蜂鸣器原理图&#xf…

剖析Linux文件系统

Linux 文件系统体系结构是一个对复杂系统进行抽象化的有趣例子。通过使用一组通用的 API 函数,Linux 可以在许多种存储设备上支持许多种文件系统。例如,read 函数调用可以从指定的文件描述符读取一定数量的字节。read 函数不了解文件系统的类型&#xff…

Java——《面试题——Redis篇》

前文 java——《面试题——基础篇》 Java——《面试题——JVM篇》 Java——《面试题——多线程&并发篇》 Java——《面试题——Spring篇》 Java——《面试题——SpringBoot篇》 Java——《面试题——MySQL篇》​​​​​​ Java——《面试题——SpringCloud》 Java…

使用Nginx的反向代理来访问服务器例子——Nginx笔记

因为网站上的视频加载过慢,想使用nginx服务器实现HLS视频播放服务。顺便记录一下通过Nginx的方向代理来访问服务器。这里在原先的项目上进行改造。原先的项目已经部署在公网,使用tomcat服务器,可以直接用地址进行访问。 1.这里使用的8080端口…

阿里飞猪三面

(有许多人是用青春的幸福作成功的代价的。——莫扎特) 背景 该岗位是阿里飞猪的前端部门,岗位名称是node.js高级/专家开发工程师。主要负责用NodeJs作为后端技术,向上层Java,Node等业务服务,提供中间层基础…