C++内功修炼干货,进大厂必须会的C++左值与右值,最适合小白看的文章!
Posted CodeBowl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++内功修炼干货,进大厂必须会的C++左值与右值,最适合小白看的文章!相关的知识,希望对你有一定的参考价值。
左值和右值是C++比较重要也比较复杂的知识点,可能学习C++很久的人对这俩个概念都不是很熟悉!我在大学中对他们也是没有概念,最近学到这里,发现网上对这俩的解释也千奇百怪,所以经过三天的学习和整理,整理出一份还不错的笔记,建议大家重点看这一篇,不要被其他文章带跑偏了。
左值和右值的定义
在C++11中所有的值必属于左值、右值两者之一。右值又可以细分为纯右值、将亡值。在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)。
举个例子,int a = b + c,a 就是左值,其有变量名为a,通过&a可以获取该变量的地址;表达式b+c、函数int func()的返回值就是右值,在其被赋值给某一变量前,我们不能通过变量名找到它,&(b+c)这样的操作则不会通过编译。
左值
左值与右值这两概念是从 c 中传承而来的,在 c 中,左值指的是既能够出现在等号左边也能出现在等号右边的变量(或表达式)
右值
右值指的则是只能出现在等号右边的变量(或表达式).
扩展
在理解C++11的右值前,先看看C++98中右值的概念:C++98中右值是纯右值,纯右值指的是临时变量值、不跟对象关联的字面量值。临时变量指的是非引用返回的函数返回值、表达式等,例如函数int func()的返回值,表达式a+b;不跟对象关联的字面量值,例如true,2,”C”等。
C++11对C++98中的右值进行了扩充。 在C++11中右值又分为纯右值(prvalue,Pure Rvalue)和将亡值(xvalue,eXpiring Value)。其中纯右值的概念等同于我们在C++98标准中右值的概念,指的是临时变量和不跟对象关联的字面量值;将亡值则是C++11新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用),比如返回右值引用T&&的函数返回值、std::move的返回值,或者转换为T&&的类型转换函数的返回值。
将亡值可以理解为通过“盗取”其他变量内存空间的方式获取到的值。在确保其他变量不再被使用、或即将被销毁时,通过“盗取”的方式可以避免内存空间的释放和分配,能够延长变量值的生命期。(通过右值引用来续命)
左值引用和右值引用
首先在C++11之前,只有左值引用,在C++11之后,才有右值引用。
我们学习右值引用之前,一定要带着问题去学习去思考,“引入一种额外的引用类型当然增加了语言的复杂性,但是我们为什么要增加复杂性呢,肯定同时也会带来优越性!”
概念
左值引用就是对一个左值进行引用的类型。右值引用就是对一个右值进行引用的类型,事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在。
右值引用和左值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即进行初始化。而其原因可以理解为是引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。
首先我们明确了最普通的左右值引用是:
左值引用:对一个左值进行引用
右值引用:对一个右值进行引用【C++11之后才引入右值引用】
那么还有没有一些特殊的引用呢?答案是有的!
左值引用绑定右值
关于右值,在 c++11 以前有一个十分值得关注的语言的特性:右值能被 const 类型的引用所指向,所以如下代码是合法的。
const cs& ref = get_cs();
而且准确地说,右值只能被 const 类型的 reference 所指向,非 const 的引用则是非法的:
1. // error
2. cs& ref = get_cs();
注意:常量左值引用是个“万能”的引用类型。它可以接受非常量左值、常量左值、右值对其进行初始化。不过常量左值所引用的右值在它的“余生”中只能是只读的。相对地,非常量左值只能接受非常量左值对其进行初始化。
int &a = 2; # 左值引用绑定到右值,编译失败
int b = 2; # 非常量左值
const int &c = b; # 常量左值引用绑定到非常量左值,编译通过
const int d = 2; # 常量左值
const int &e = c; # 常量左值引用绑定到常量左值,编译通过
const int &b =2; # 常量左值引用绑定到右值,编译通过
右值引用绑定左值
右值值引用通常不能绑定到任何的左值,要想绑定一个左值到右值引用,通常需要std::move()将左值强制转换为右值,例如:
int a;
int &&r1 = a; # 编译失败
int &&r2 = std::move(a); # 编译通过
int &&r2 = 2; # 编译通过
实例
#include <iostream>
void process_value(int& i)
{
std::cout << "LValue processed: " << i << std::endl;
}
void process_value(int&& i) //右值引用
{
std::cout << "RValue processed: " << i << std::endl;
}
int main()
{
int a = 0;
process_value(a);
process_value(1);
}
我们在main函数中,非常正常地传入了一个左值和一个右值,所以输出结果也是正确的!
给右值引用传入一个左值
问题的来源:
r2是一个右值引用,但是r2本身是不是左值呢?C++11对此做出了区分:
如果它有一个名字,那么它是一个左值。否则,它是一个右值。
int main()
{
int a = 0;
process_value(a);
int&& x = 3;
process_value(x);
}
x 是一个右值引用,指向一个右值3,但是由于x是有名字的,所以x在这里被视为一个左值,所以在函数重载的时候选择为第一个函数。
右值引用的意义 我们如果想在实际开发中用到右值引用,我们就要搞清楚为什么我们需要右值引用,也就是右值引用的意义! 直观意义 为临时变量续命,也就是为右值续命,因为右值在表达式结束后就消亡了,如果想继续使用右值,那就会动用昂贵的拷贝构造函数。
这里引出了临时变量:
临时变量
C++ 中的临时变量指的是那些由编译器根据需要在栈上产生的,没有名字的变量。主要的用途主要有两类:
- 函数的返回值,
- 类型转换时的中间变量
一般来说,C++ 中的临时变量在表达式结束之后 (full expression) 就被会销毁,但也有例外的时候,如果这个临时变量被用来初始化一个引用的话,那这个临时变量的生命周期就会被延长,直到引用被销毁,从而不会因此产生悬空(dangling)的引用。
也就是说,一旦一个临时变量被引用,它的生命周期就变得和引用它的变量一样长!
右值引用是用来支持转移语义的。
转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高C++应用程序的性能。临时对象的维护 ( 创建和销毁 ) 对性能有严重影响。
转移语义是和拷贝语义相对的,可以类比文件的剪切与拷贝,当我们将文件从一个目录拷贝到另一个目录时,速度比剪切慢很多。通过转移语义,临时对象中的资源能够转移其它的对象里。
在现有的 C++ 机制中,我们可以定义拷贝构造函数和赋值函数。要实现转移语义,需要定义转移构造函数,还可以定义转移赋值操作符。对于右值的拷贝和赋值会调用转移构造函数和转移赋值操作符。如果转移构造函数和转移拷贝操作符没有定义,那么就遵循现有的机制,拷贝构造函数和赋值操作符会被调用。 普通的函数和操作符也可以利用右值引用操作符实现转移语义。
参考资料
【1】https://www.cnblogs.com/catch/p/3500678.html
【2】https://mp.weixin.qq.com/s?src=11×tamp=1626703408&ver=3200&signature=-8fSD48eWwkkMchC3DjpghdBHkf4GySAjvEFSs1PsiwZfOP11nqAzk6XszTE9XQXFkGp7OP5G1gLLAmAehwOlqpw8yMwA0X3FMKfMrwzQE4-HrKQHSgAbIYDp6s-OP&new=1
【3】临时变量:https://www.cnblogs.com/catch/p/3251937.html
【4】牛逼:http://c.biancheng.net/view/7847.html
写作不易,希望给个关注,有问题可以随时私聊交流
以上是关于C++内功修炼干货,进大厂必须会的C++左值与右值,最适合小白看的文章!的主要内容,如果未能解决你的问题,请参考以下文章