首页 > 编程语言 >【C++】看完就会--右值引用!!!

【C++】看完就会--右值引用!!!

时间:2024-08-19 22:55:14浏览次数:8  
标签:const 右值 -- 左值 C++ int 引用 func

右值引用

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

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

#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;
}

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

标签:const,右值,--,左值,C++,int,引用,func
From: https://blog.csdn.net/2403_86785171/article/details/141336870

相关文章

  • 合成孔径雷达回波生成,距离多普勒算法前提
    “革命的道路,同世界上一切事物活动的道路一样,总是曲折的,不是笔直的。”1.1回波生成1.1.1工作模式正侧式条带SAR1.1.2参数设置相关参数大小带宽150MHz中心频率3.0GHz载机速度200m/s载机高度10km下视角45°脉冲重复频率(PRF)300Hz脉冲宽度4.0......
  • [Python学习日记-10] Python中的流程控制(if...else...)
    简介        假如把写程序比做走路,那我们到现在为止,一直走的都是直路,还没遇到过分叉口,想象现实中,你遇到了分叉口,然后你决定往哪拐必然是有所动作的。你要判断那条岔路是你真正要走的路,如果我们想让程序也能处理这样的判断怎么办?很简单,只需要在程序里预设一些条件判断......
  • 逻辑回归C参数选择,利用交叉验证实现
    目录前言一、C参数二、交叉验证1.交叉验证是什么2.交叉验证的基本原理3.交叉验证的作用4.常见的交叉验证方法三、k折交叉验证四、C参数和k折交叉验证的关系五、代码实现1.导入库2.k折交叉验证选择C参数3.建立最优模型总结前言        逻辑回归(Logist......
  • 设计模式实战:即时通讯应用的设计与实现
    系统功能需求用户管理:支持用户注册、登录、注销、个人信息更新等功能。消息传递:支持即时消息发送、接收、存储和显示,支持文本、图片、语音等多种消息类型。在线状态管理:实时跟踪和显示用户的在线状态。消息通知:在消息到达时发送推送通知给用户。聊天记录管理:支持聊天......
  • Jira从4.4.5升级到6.4.14实施方案
    1、开始之前1.1、停止当前所用插件1.2、确认插件版本目前公司4.4.4版本使用了两个第三方插件。插件名称版本用途待升级版本备注issue-alternative-assignee1.6.3流程中人员选择插件1.7.81.6.3对应Jira4.4.41.7.8对应Jira6.4.14JIRASuiteUtilities......
  • 汽车资讯一:新能源汽车市场迎来爆发式增长,绿色出行成新风尚!
    随着全球对环境保护意识的日益增强以及科技的飞速发展,新能源汽车市场正以前所未有的速度扩张,成为汽车行业的新宠儿。据最新市场研究报告显示,今年第一季度,全球新能源汽车销量同比大幅增长,多个国家和地区的新能源汽车渗透率创下历史新高,绿色出行正逐渐成为人们的新风尚。技术创......
  • PowerShell 脚本是什么?
     目录前言什么是PowerShell?PowerShell的历史PowerShell脚本的定义PowerShell的基本语法1.命令和Cmdlet2.变量3.控制结构4.函数5.模块PowerShell脚本的应用场景1.系统管理2.数据处理3.网络管理4.自动化测试5.云计算PowerShell脚本的编写与......
  • Oracle运算符:从等号到空值运算的使用技巧
    在Oracle数据库中,关系运算符和逻辑运算符用于在SQL查询中定义条件。1.等号(=)运算符作用:用于精确匹配字段的值。适用场景:适用于比较数值、字符串、日期等数据类型,要求条件严格相等。例子:SELECTename,salFROMempWHEREdeptno=10;查询部门编号为10的所有员工姓名和......
  • Oracle数据库必学!超实用的9个字符串处理函数
    Oracle查询语句中的单行函数,特别是一些常用的字符串处理函数。1.ConCAT函数作用:将两个字符串连接在一起,生成一个新的字符串。使用方法:concat(字符串1,字符串2)示例:SELECTconcat('Hello,','World')FROMDUAL;结果为:“Hello,World”。2.CHR与ASCII函数CHR函数......
  • Oracle索引使用原则:优化查询性能的关键
    1.索引信息的查询:要获取数据库中索引的相关信息,如索引类型、所在表、是否唯一索引等,可以查询与索引相关的数据字典视图。常用的数据字典视图包括dba_indexes、dba_ind_columns、user_indexes和user_ind_columns等。dba_indexes和dba_ind_columns视图需要DBA权限才能访问,......