【c++11】看完立马就懂--右值引用!!!

右值引用

  • 一、什么是右值?什么是左值?
  • 二、右值引用
  • 三、右值引用的好处
  • 四、万能引用
  • 五、完美转发

一、什么是右值?什么是左值?

首先,当我们看到右值的时候,我们很自然的就会产生疑问?
什么的右边呢?
等号的右边吗?
那么如果是按赋值=符号的右边来定义的话,那么,左值是不是就是=符号的左边的值呢?
但是,看到下面的这段代码,我们又感觉,上面的说法,貌似不太对!

#include<iostream>int main()
{int a = 10;int b = a;return 0;
}

仔细推敲,我们发现,10是个字面常量,和我们的a,在栈上创建的变量,貌似不是一个东西?这样来看,好像和我们上面的定义挺符合的啊?两个不同的东西,刚好用左值和右值这么个名称来进行区分。好像挺对的哦。
但是,b不也是在栈上开辟的变量吗?b在赋值符号=的左边,但是a也是栈上的变量,a在赋值符号=的右边呀!那这样子来看,左值和右值不就没什么区别了吗?????
————————————————————————————————
所以,上面的这种结论,肯定是不正确的!那么究竟什么是左值?什么是右值???
左值和右值,它肯定会存在区别!不然为什么要出现这样子的命名。通过作者的不懈努力的查阅资料!!!
左值,我们可以理解我们平常开辟在栈上的变量,例如上面的a,b这些变量,都可以叫做左值。
右值,通常我们认为,右值是那么字面常量,如10;表达式的返回值(a + b返回一个临时变量);函数的返回值(这个返回值不是引用);我们将这些统称为右值。
其中,我们最佳的区分方式就是,看看这个变量能不能取地址。
在这里插入图片描述
注意,字符串,我们将它认为是左值,它可以取地址,地址是首元素的地址。
在这里插入图片描述
在这里插入图片描述
————————————————————————————————

二、右值引用

引用我们都很熟悉,也就是

#include <iostream>int main()
{int a = 0;int &ra = a;return 0;
}

这种引用我们叫做左值引用,右值引用是长啥样子的?

#include <iostream>
int main()
{int&& ra = 10;return 0;
}

这样子的引用方式我们叫做右值引用。
那么我们就会产生以下的疑问?

  • 左值引用能不能引用右值?
  • 右值引用能不能引用左值?

左值能不能引用右值?
在这里插入图片描述
撕~~~~,好像不可以诶?仔细想了想,是不是和引用的权限升级有关呢?
因为10是个字面常量呀,它又不能修改,我用一个左值引用它,不就发生权限放大吗?那么我们给它加上一个const,来限制它的权限,是不是就能够左值引用右值了呢?
在这里插入图片描述
可以看到,编译器这次没有报错了,说明这样子的方式是可以的,也就说明了,const的左值引用可以引用右值,所以,这就解释了,为什么STL容器里面的拷贝构造,构造函数,为什么要对参数加上const的原因了,这样既能够将我们的左值传参过来,也可以将右值传参传过来。

右值引用能不能引用左值?
我们来尝试一下就知道了
在这里插入图片描述
编译器报错了,说明是不可以的?肯定不可以吗?想想原因,貌似想不出来什么原因了??????
是不是真的不可以?
通过我查阅C++11,发现了,c++11里面给我们提供了一个方法。也就是将左值move一下。
在这里插入图片描述
好像可以了,但是为什么move一下,a就可以被右值引用了呢?这样子写可以吗?
在这里插入图片描述
好像又不行了,所以我推断,move应该是根据左值a,来产生一个临时的右值作为返回值,然后右值引用就可以引用了。

三、右值引用的好处

右值引用说了半天,那么右值引用的出现到底有什么用啊?它难道就是为了能够替换const的左值引用,给右值也能够名正言顺的加上一个右值引用的名称吗?仅仅只是为了给我们的右值也有了地位,恩宠一下右值是吧?????
那肯定是不可能的啊,怎么可能会耗费那么大的力气搞出来一个右值引用,就为了恩宠一下右值???
对于它的作用我也很好奇,所以我也去看别人的博客和上网查资料,我看到一些博客上面介绍:右值引用可以延长生命周期???
我看到的时候,我满脸问号
在这里插入图片描述
什么玩意???延长生命周期???
延长啥的生命周期啊???
为什么要延长生命周期???
延长生命周期有啥用啊???
我表示很疑问和好奇,在这种好奇和疑问的驱动下,我就去看了《C++ Primer》这本书,我仿佛领悟到了一些!!!
那篇博客的作者可能想表达的是,延长资源的生命周期,右值引用可以说是一个非常非常强大牛逼的东西。
场景一:

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;string func()
{string s("aaaaaaaaaa");return s;
}int main()
{string s1 = func();return 0;
}

按照右值引用没有出现之前,我们的代码逻辑应该是这样子的
在这里插入图片描述
然后这里由于出现的s要拷贝构造一次,s1也要拷贝构造,编译器就会优化,直接跳过中间的那个临时变量,直接s1拷贝构造s(编译器优化就是我们的编译器其实是很智能的,如果发生那种创建一个变量,需要连续的构造几个中间变量的时候,编译器就会直接将中间的变量优化掉)。

这是右值引用没有出现的时候的分析,如果这个函数里面的string非常的大,那么在构造的时候,string肯定是深拷贝,那么这样子的代价真的太大了,如果返回的是vector< vector < int >>这样的类型,那么拷贝的代价真的太恐怖了,所以这里在右值引用没有出现之前,我们是通过输出型参数的方式来获取结果,也就是这样形式的代码风格

#include <iostream>void func(string& s1)
{.......
}

那么右值引用的出现,就可以解决这样的问题,如何解决?

string func()
{string s("aaaaaaaaaa");return s;
}int main()
{string s1 = func();return 0;
}

我们看到,func里面的s,它会随着函数调用的结束销毁栈帧,那么在里面的s,它的生命周期也就在那一个函数体里,函数销毁,那么s自然而然也要销毁,但是我们要返回s的内容,那么我们为什么不把s的资源夺过来,也就是将s夺舍!!!把它的资源占为己有,然后我的s1,它都已经要被赋值,把原先的内容给丢掉了,构造新的内容出来了,那么我为什么不把s的资源给拿过来,将我s1不要的资源丢给你s,你s都要销毁了,你就顺路把我那些垃圾,不要的资源也带走吧!!!这样的思路,就是右值引用的意义。
它将我们的将亡值,也就是即将销毁的s的资源的生命周期延续了下去,其实本质就是一种交换资源的方式。
那么这种实现也很简单,只需要创建一些指针变量,来交换我们的变量所指向在堆上创建的资源,获取到对方指向的内存块,这样就完成拷贝,这种拷贝我们称为移动拷贝
那么根据上面的思路,我们可以这样子玩

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;class A
{
public:A(int size = 0):_a(new int[size]),_size(size){}A(const A& it){cout << "A(const A& it) -- 左值引用" << endl;_size = it._size;_a = new int[_size];for (int i = 0; i < _size; i++){_a[i] = it._a[i];}}A(A&& it){cout << "A(A&& it) -- 右值引用" << endl;int* tmp = it._a;it._a = _a;_a = tmp;}private:int* _a = nullptr;int _size = 0;
};A func()
{A a(10);return a;
}int main()
{A a1 = func();return 0;
}

当我们运行到箭头位置的时候
在这里插入图片描述
我们看到a的地址的后四位是0510,然后接着运行
在这里插入图片描述
我们可以观察到,a的地址转移到了a1上面,而我们实现的方式,仅仅只是创建了一个临时的指针对象,交换双方指向的内容,而深拷贝要开辟空间,要把对方的值拷贝给我开辟的空间上,要调佣循环,这样的代价相比,移动构造的代价小的非常非常的多。

我们运行的结果
在这里插入图片描述

这样子,我们只需要很小的代价,就将资源获取到了,创造出右值引用的人实在是太牛了。所以右值引用我只能说,真香!
————————————————————————————————

四、万能引用

我们来看这段代码

template<class T>
void func(T&& x)
{cout << x << endl;
}int main()
{func(10);        //右值int a = 9;func(a);         //左值const int b = 8;func(b);         //const 左值func(move(a));   //move 左值 -> 右值func(move(b));   //move const 左值 -> const 右值return 0;
}

按理来说,我们的func函数里面看着是个右值,所以理所应当的应该要传一个右值过去,但是我们运行发现,
在这里插入图片描述
咦???怎么全部都可以编译通过并且运行呢???
这里需要科普一个知识,
首先,这是一个模版,它是通过具体的函数调用的值来实例化的,然后这里涉及到了一个叫做引用折叠的知识,如果传参传过去的是个左值,那么这里的T&&就会发生引用折叠,变成T&,而右值传过去依然是T&&。
那么这样的语法,它被称为万能引用,就是字面意思,模版的加入给予了它根据参数来实例化出不同的具体函数,这种方式让我们c++变得更加的灵活强大。

五、完美转发

我们也是一样,思考一下这段代码的结果


void test(int& x)
{cout << "void test(int& x)        -- 左值引用" << endl;
}void test(const int& x)
{cout << "void test1(const int& x) -- const 左值引用" << endl;
}void test(int&& x)
{cout << "void test(int&& x)       -- 右值引用" << endl;
}
void test(const int&& x)
{cout << "void test(const int&& x) -- const 右值引用" << endl;
}template<class T>
void func(T&& x)
{test(x);
}int main()
{func(10);        //右值int a = 9;func(a);         //左值const int b = 8;func(b);         //const 左值func(move(a));   //move 左值 -> 右值func(move(b));   //move const 左值 -> const 右值return 0;
}

我们来分析一下,根据上面学的万能引用,这里的输出结果应该是
右值
左值
const 左值
右值
const 右值
我们来查看编译器运行的结果
在这里插入图片描述
诶???这里就要产生疑问了,怎么全是左值了???很奇怪是不是??
我们之前说过,判断一个变量,是左值还是右值,关键在于能不能取地址,那么我们就怀疑,右值引用这个变量本身,能不能取地址呢?

int&& x = 10;cout << &x << endl;

在这里插入图片描述
我在vs2019下测试发现,右值引用本身竟然是一个左值,我们再来看它的汇编语言
在这里插入图片描述
我们发现,它好像是将10放在了栈上的某个位置,然后将这个位置用寄存器保存起来了,然后x仿佛被当成了一个指针, 然后将10存放在栈的位置存到x里面。
所以在我的编译器上,右值引用被当成是一个左值了,所以这就解释了为什么上面的代码执行结果全是左值!
那么我们如何解决这个问题?
c++11提供了forward来实现完美转发,即在传参过程中保证了参数的属性不会发生改变,也就是右值引用去当参数传递时,调用的是右值引用的函数,


void test(int& x)
{cout << "void test(int& x)        -- 左值引用" << endl;
}void test(const int& x)
{cout << "void test1(const int& x) -- const 左值引用" << endl;
}void test(int&& x)
{cout << "void test(int&& x)       -- 右值引用" << endl;
}
void test(const int&& x)
{cout << "void test(const int&& x) -- const 右值引用" << endl;
}template<class T>
void func(T&& x)
{test(forward<T>(x));
}int main()
{func(10);        //右值int a = 9;func(a);         //左值const int b = 8;func(b);         //const 左值func(move(a));   //move 左值 -> 右值func(move(b));   //move const 左值 -> const 右值return 0;
}

运行结果
在这里插入图片描述
这样就没有发生问题了!

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

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

相关文章

黑马鸿蒙学习5:LIST容器

LIST容器&#xff0c;其实就是如果FOREACH容器展示不全的话&#xff0c;会自动有滚动条了。要注意的是&#xff0c;LIST中必须有固定的listitem这个项&#xff0c;而且列表里面只能包含一个根组件。 必须把ROW容器放到listitem中&#xff0c;如下&#xff1a;

51、图论-岛屿数量

思路&#xff1a; 该问题要求在一个由 1&#xff08;表示陆地&#xff09;和 0&#xff08;表示水&#xff09;组成的二维网格中&#xff0c;计算岛屿的数量。岛屿被水包围&#xff0c;并且通过水平或垂直连接相邻的陆地可以形成。这个问题的核心是识别并计数网格中相连的陆地…

CSV解析

一直以为csv靠逗号&#xff08;,&#xff09;分割数据&#xff0c;那么只要用str.spilt(,,row)便可以将数据分割开。 事实证明想简单了&#xff0c;csv里还有这样的规定&#xff0c;如果数据内有双引号&#xff08;"&#xff09;和逗号&#xff08;,&#xff09;那么&…

车载电子电器架构 —— 售后诊断开发

车载电子电器架构 —— 售后诊断开发 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己…

在Postgres中,如何有效地管理大型数据库的大小和增长

文章目录 一、定期清理和维护1. VACUUM和ANALYZE2. 删除旧数据和归档 二、分区表三、压缩数据四、配置优化1. 调整维护工作负载2. 监控和日志 五、使用外部存储和扩展1. 外部表和FDW2. 扩展和插件 六、定期备份和恢复测试结论 管理大型数据库的大小和增长是数据库管理员&#x…

Java中的变量与常量

标识符 Java语言规定标识符由任意顺序的字母、下划线&#xff08;_&#xff09;、美元符号&#xff08;$&#xff09;和数字组成&#xff0c;并且第一个字符不能是数字。标识符也不能是Java中的关键字&#xff08;保留字&#xff09;。 在Java语言中&#xff0c;标识符的字母…

环境监测系统--------MQ系列气体检测模块驱动教程(保姆级教程)

⏩ 大家好哇&#xff01;我是小光&#xff0c;嵌入式爱好者&#xff0c;一个想要成为系统架构师的大三学生。 ⏩在环境检测中我们经常会用到检测气体的传感器&#xff0c;检测乙醇、甲烷、一氧化碳、氢气等等&#xff0c;博主呕心沥血对MQ系列传感器做一个史上最详细的使用教程…

网络安全产品---堡垒机

what 在网上搜索 运维审计与风险控制系统就是是堡垒机 我认为的堡垒机就是提供高效运维、认证管理、访问控制、安全审计和报表分析功能的云服务设备 实现高效运维的同时最大程度控制运维风险。 how 能够对运维人员维护过程进行全面跟踪、控制、记录、回放 支持细粒度配置…

政企版 WPS Pro 专业版注册安装教程

政企版 WPS Pro 专业版安装及激活步骤 第 1 步&#xff1a;下载压缩包&#xff08;内含注册码&#xff09;【无解压密码】。 第 2 步&#xff1a;解压缩后&#xff0c;运行 exe 文件&#xff0c;默认步骤安装即可。 第 3 步&#xff1a;安装完成后&#xff0c;新建一个 Word …

使用QQ邮箱进行登录验证

使用场景不多说&#xff0c;接下来直接看实现~ 登录到QQ邮箱&#xff0c;进入设置 打开IMAP/SMTP服务&#xff0c;记得把授权码记录下来&#xff0c;后面配置文件中需要用到 新建application的配置文件 spring:mail:# 指定邮件服务器地址host: smtp.qq.comusername: 你自己的q…

ROS 2边学边练(31)-- 管理大工程

前言 往往现实中的工程都是会包含很多节点很多参数很多主题的那种&#xff0c;如果单独通过各种ros2 run命令进行启动管理&#xff0c;恐怕难以招架&#xff0c;主要还是通过launch文件的方式进行管理&#xff0c;而launch文件也可以像节点那样按功能的不同模块化&#xff0c;最…

【学习】黑盒测试用例设计方法都有哪些

在软件测试中&#xff0c;黑盒测试是一种重要的测试方法&#xff0c;它专注于软件的外部行为&#xff0c;而不关心其内部结构和实现。黑盒测试的目标是确保软件的功能符合需求规格说明书中的要求。为了有效地进行黑盒测试&#xff0c;需要设计合理的测试用例。本文将详细介绍黑…

【Android】Activity task和Instrumentation杂谈

文章目录 activity taskInstrumentation机制参考 Android不仅可以装载众多的系统组件&#xff0c;还可以将它们跨进程组成ActivityTask&#xff0c;这个特性使得每个应用都不是孤立的。 activity task 从数据结构角度看&#xff0c;Task有先后之分&#xff0c;源码实现上采取了…

基于SpringBoot+Vue钢材销售管理系统的设计与实现

系统介绍 为了更好地发挥本系统的技术优势&#xff0c;根据钢材销售管理系统的需求&#xff0c;本文尝试以B/S经典设计模式中的Spring Boot框架&#xff0c;JAVA语言为基础&#xff0c;通过必要的编码处理、钢材销售管理系统整体框架、功能服务多样化和有效性的高级经验和技术…

高级IO简介

一、非阻塞IO 阻塞其实就是进入了休眠状态&#xff0c;交出了 CPU 控制权。 普通文件的读写操作是不会阻塞的&#xff0c;不管读写多少个字节数据&#xff0c; read() 或 write() 一定会在有限的时间内返回&#xff0c;所以普通文件一定是以非阻塞的方式进行 I/O 操作&…

【Visual Studio 2012中文版】下载安装以及使用方法

文章目录 前言一、下载安装包二、安装步骤1.双击VS2012_ULT_chs.iso文件打开2.双击vs_ultimate.exe打开安装程序3.选择要安装的功能4.软件正在安装&#xff0c;请耐心等待10分钟5.安装成功&#xff0c;点击“启动”6.激活码&#xff08;产品密钥&#xff09; 三、VS2012使用&am…

CRMEB Pro版营销功能规则解读

现在&#xff0c;无论是中小型企业拓宽渠道&#xff0c;还是传统企业转型&#xff0c;基本都在考虑布局线上&#xff0c;做微商城是一大趋势&#xff0c;相比传统第三方电商平台&#xff0c;独立部署的商城系统自主性更强&#xff0c;功能开发也更灵活。其中&#xff0c;作为电…

vue3 -- 项目使用自定义字体font-family

在Vue 3项目中使用自定义字体(font-family)的方法与在普通的HTML/CSS项目中类似。可以按照以下步骤进行操作: 引入字体文件: 首先,确保你的字体文件(通常是.woff、.woff2、.ttf等格式)位于项目中的某个目录下,比如src/assets/font/。 在全局样式中定义字体: 在你的全局…

[阅读笔记25][WebArena]A Realistic Web Environment for Building Autonomous Agents

这篇论文提出了WebArena这个环境与测试基准&#xff0c;在24年1月发表。 之前的agent都是在一些简化过的合成环境中测试的&#xff0c;这会导致与现实场景脱节。这篇论文构建了一个高度逼真、可复现的环境。该环境涉及四个领域&#xff1a;电子商务、论坛讨论、软件开发和内容管…

多种情况下合并单元格(S-Table+Vue3)

合并单元格(S-TableVue3) 跨行合并&#xff1a;rowspan“合并单元格的个数” 跨列合并&#xff1a;colspan"“合并单元格的个数” 文章目录 合并单元格(S-TableVue3)第一列跨行合并单元格templatescript效果 多列跨行合并单元格templatescript效果图 多行合并可参考 第一列…