C/C++ 左值和右值:区别、引用及转化_c++ 左值引用转换引用-程序员宅基地

技术标签: c++  c语言  物联网  嵌入式  开发语言  

C/C++ 左值和右值:区别、引用及转化

左值与右值:

左值:

指表达式结束后依然存在的持久对象。可以取地址,可以通过内置(不包含重载) & 来获取地址,我们可以将一个右值赋给左值。

右值:

表达式结束就不再存在的临时对象。不可取地址,不可以通过内置(不包含重载) & 来获取地址。由于右值不可取地址,因此我们不能将任何值赋给右值。
使用 = 进行赋值时,= 的左边必须为左值,右值只能出现在 = 的右边。
程序示例:

// x 是左值,666 为右值
int x = 666;   // ok 
int *y = x; // ok
int *z = &666 // error
666 = x; // error
int a = 9; // a 为左值
int b = 4; // b 为左值
int c = a + b // c 为左值 , a + b 为右值
a + b = 42; // error

函数返回值即可以是左值,也可以是右值:

int setValue()
{
    return 6;
}

int global = 100;

int& setGlobal()
{
    return global;    
}
setValue() = 3; // error!
setGlobal() = 400; // OK

左值引用和右值引用:

引用的定义在之前的章节中已经介绍过。

左值引用:

左值引用可以区分为常量左值引用和非常量左值引用。左值引用的底层实现是指针实现。
非常量左值引用只能绑定到非常量左值,不能绑定到常量左值和右值。如果绑定到非常量右值,就有可能指向一个已经被销毁的对象。
常量左值引用能绑定到非常量左值,常量左值和右值;

int y = 10;
int& yref = y;  // ok
int& xref = 10; // error, 非常量左值引用绑定右值
const &xref = 10; // ok, 常量左值引用绑定右值
int a = 10;
int b = 20;
int& zref = a + b // error, a + b为右值

int &aref1 = a;  //ok, 非常量左值引用绑定非常量左值
const int &aRef2 = a; //ok, 常量左值引用绑定非常量左值
const int c = 4;   
int &cref1 = c;  // error,非常量左值不能绑定常量右值
const int &cref2 = c; //ok, 常量左值引用绑定常量左值
const int &ref2 = a + b;    //ok, 常量左值引用绑定到右值(表达式)

如果函数的形参定义为非常量的左值引用,则会出现错误,因为此时我们将一个左值引用绑定到右值上:

void fnc(int& x)
{
}
int main()
{
    fnc(10);  // error!
}

如果函数的形参定义为常量的左值引用,则可以正常运行,因为此时我们将一个常量左值引用绑定到一个右值上:

void fnc(const int& x)
{
}
int main()
{
    int x = 10;
    fnc(x);   // ok!
    fnc(10);  // ok!
}

右值引用:

右值引用 (Rvalue Referene) 是 C++ 11 中引入的新特性 , 它实现了转移语义 (Move Sementics)和精确传递 (Perfect Forwarding),&& 作为右值引用的声明符。右值引用必须绑定到右值的引用,通过 && 获得。右值引用只能绑定到一个将要销毁的对象上,因此可以自由地移动其资源。
从实践角度讲,它能够完美解决 C++ 中长久以来为人所诟病的临时对象效率问题。从语言本身讲,它健全了 C++ 中的引用类型在左值右值方面的缺陷。从库设计者的角度讲,它给库设计者又带来了一把利器。从使用者的角度来看,可以获得效率的提升,避免对象在传递过程中重复创建。
右值引用两个主要功能:

1.消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
2.能够更简洁明确地定义泛型函数。

#include <iostream>
using namespace std;

int g_val = 10;

void ProcessValue(int &i) {                         // 左值引用
    cout << "lValue processed: " << i << endl;
}

void ProcessValue(int &&i) {                        // 右值引用
    cout << "rValue processed: " << i << endl;
}

int GetValue() { // 返回右值
    return 3; 
} 

int& getVal() { // 返回左值引用
    return g_val; 
}

int main() {
    int a = 0;
    int b = 1;
    int &alRef = a;             // 左值引用
    int &&rRef1 = 1;            // 临时对象是右值
    int &&rRef2 = GetValue();   // 调用的函数为右值
    ProcessValue(a);            // 左值
    ProcessValue(getVal());     // 左值引用
    ProcessValue(1);            // 临时对象是右值
    ProcessValue(GetValue());   // 调用的函数为右值
    ProcessValue(a+b);          // 表达式为右值
    return 0;
}
/*
lValue processed: 0
lValue processed: 10
rValue processed: 1
rValue processed: 3
rValue processed: 1
*/

有了右值引用后,函数调用可以写为如下,此时我们用右值引用绑定到右值上:

void fnc(int&& x)
{

}

int main()
{
    int x = 10;
    fnc(x);   // error, 右值引用不能绑定到左值上
    fnc(10);  // ok!
}

左值转换成右值:
左值转换为右值
我们可以通过 std::move 可以将一个左值强制转化为右值,继而可以通过右值引用使用该值,以用于移动语义,从而完成将资源的所有权进行转移。

#include <iostream>
using namespace std;

void fun(int& tmp) 
{ 
  cout << "fun lvalue bind:" << tmp << endl; 
} 

void fun(int&& tmp) 
{ 
  cout << "fun rvalue bind:" << tmp << endl; 
} 

void fun1(int& tmp) 
{ 
  cout << "fun1 lvalue bind:" << tmp << endl; 
} 

int main() 
{ 
    int var = 11; 
    fun(12); // 右值引用
    fun(var); // 左值引用
    fun(std::move(var)); // 使用std::move转为右值引用
    fun(static_cast<int&&>(var));  // 使用static_cast转为右值引用
    fun((int&&)var); // 使用C风格强转为右值引用
    fun(std::forward<int&&>(var)); // 使用std::forwad<T&&>为右值引用
    fun1(12); // error
    return 0;
}
/*
fun rvalue bind:12
fun lvalue bind:11
fun rvalue bind:11
fun rvalue bind:11
fun rvalue bind:11
fun rvalue bind:11
*/
引用折叠:

通过类型别名或者通过模板参数间接定义,多重引用最终折叠成左值引用或者右值引用。有两种引用(左值和右值),所以就有四种可能的引用+引用的组合(左值 + 左值,左值 + 右值,右值 + 左值,右值 + 右值)。如果引用的引用出现在允许的语境,该双重引用会折叠成单个引用,规则如下:
所有的右值引用叠加到右值引用上仍然还是一个右值引用;T&& && 折叠成 T&&
所有的其他引用类型之间的叠加都将变成左值引用。T& &&,T&& &, T&& 折叠成 T&。

#include <iostream>
using namespace std;

typedef int&  lref;
typedef int&& rref;

void fun(int&& tmp) 
{ 
    cout << "fun rvalue bind:" << tmp << endl; 
} 

void fun(int& tmp) 
{ 
    cout << "fun lvalue bind:" << tmp << endl; 
} 

int main() 
{ 
    int n = 11; 
    fun((lref&)n);
    fun((lref&&)n);
    fun((rref&)n);
    fun((rref&&)n);
    return 0;
}
/*
fun lvalue bind:11
fun lvalue bind:11
fun lvalue bind:11
fun rvalue bind:11
*/
万能引用类型:

在模板中 T&& t 在发生自动类型推断的时候,它是未定的引用类型(universal references),它既可以接受一个左值又可以接受一个右值。如果被一个左值初始化,它就是一个左值;如果它被一个右值初始化,它就是一个右值,它是左值还是右值取决于它的初始化。

template<typename T>
void f(T&& param); 

template<typename T>
class Test {
    Test(Test&& rhs); 
};

对于函数 templatevoid f(T&& t),当参数为右值 10 的时候,根据 universal references 的特点,t 被一个右值初始化,那么 t 就是右值;当参数为左值 x 时,t 被一个左值引用初始化,那么 t 就是一个左值。
上面的例子中,param 是 universal reference,rhs 是 Test&& 右值引用,因为模版函数 f 发生了类型推断,而 Test&& 并没有发生类型推导,因为 Test&& 是确定的类型了。正是因为右值引用可能是左值也可能是右值,依赖于初始化,我们可以利用这一点来实现移动语义和完美转发。
参考资料:
链接:https://leetcode.cn/leetbook/read/cmian-shi-tu-po/vd00s1/
来源:力扣(LeetCode)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_31442071/article/details/134995322

智能推荐

jquery 获取子 div_jq 子级别div-程序员宅基地

文章浏览阅读908次。获取 dom 对象$("#divId").children("div").get(0);$("#divId").children("div")[0];获取 jquery 对象$("#divId").children("div").eq(0);$($("#divId").children("div").get(0));【Java面试题与答案】整理推荐基础..._jq 子级别div

基于Springboot + vue实现的交通管理在线服务系统-程序员宅基地

文章浏览阅读271次,点赞4次,收藏4次。管理员管理:负责添加、删除、修改管理员账号,并设置相应的权限,确保管理员团队的专业性和高效性。新闻信息管理:发布、编辑和删除交通新闻、政策更新、路况信息等,保持信息的实时性和有效性。驾驶证业务管理:在线提交驾驶证申请、查询、更新、补办等业务,并实时查看办理进度。新闻信息查看:浏览系统发布的交通新闻、政策更新、路况信息等,了解最新的交通动态。机动车业务管理:在线提交车辆注册、年检、转移、报废等业务申请,并获取办理结果。用户管理:管理用户账号,包括用户注册、登录、权限设置等,确保系统的安全性。

打印系统开发(42)——静默打印_静默打印是什么意思-程序员宅基地

文章浏览阅读4.4k次。1.问题描述希望每次打印时,都是用固定的打印机打印并且不希望弹出对话框进行设置,此时便可以设置静默打印。1.1什么是静默打印静默打印即点击打印时不弹出选项窗口和打印机设置窗口直接进行打印。1.2支持静默打印的打印方式零客户端打印、本地打印、服务器端打印支持静默打印。2.静默打印设置方法2.1 零客户端打印设置方法注:只支持 IE点击模板-打印..._静默打印是什么意思

STM32+74HC595:带领你10分钟用对74HC595_74hc595连接stm32-程序员宅基地

文章浏览阅读2.4w次,点赞14次,收藏68次。使用的是STM32CBT8,小模块用起来性价比超级高,资源丰富,移植u/COS及HTTP、MQTT协议等等用起来简直欲罢不能,摇摇欲仙!BUT:IO口资源太少了,我想让你驱动100个LED,你缺告诉我,我的要求太多,你满足不了......还好,找到了74HC595,但是网上很多资源讲的我看了半天才总结、提炼并另辟蹊径出来精髓===============================_74hc595连接stm32

莱昂哈德·欧拉生平及其成就简介_欧拉的物理成就-程序员宅基地

文章浏览阅读4.1k次,点赞2次,收藏8次。莱昂哈德·欧拉(Leonhard Euler ,1707年4月15日~1783年9月18日),瑞士数学家、自然科学家。1707年4月15日出生于瑞士的巴塞尔,1783年9月18日于俄国圣彼得堡去世。欧拉出生于牧师家庭,自幼受父亲的影响。13岁时入读巴塞尔大学,15岁大学毕业,16岁获得硕士学位。欧拉是18世纪数学界最杰出的人物之一,他不但为数学界作出贡献,更把整个数学推至物理的领域。他是数学史上最多产的数学家,平均每年写出八百多页的论文,还写了大量的力学、分析学、几何学、变分法等的课本,《无穷小分析引论》、_欧拉的物理成就

Error: PL/SQL: ORA-00980: 同义词转换不再有效_sql数据库中同义词转换不再有效-程序员宅基地

文章浏览阅读1.5w次。今天在写存储过程的时候,碰到一个问题,在执行存储过程的时候总是报错--同义词转换不再有效,发现一个查询语句中的一个表原来使用的是一个同义词,就试着把这个同义词单独拿出来进行查询操作,发现并没有问题。最后,经过一番努力,发现该同义词并不是直接指向一个实体表,而是指向另一个同义词。所以,将改同义词的指向改为直接指向原实体表的指向,问题得到解决。即同义词指向的 object ow_sql数据库中同义词转换不再有效

随便推点

html页面点击按钮上传文件,点击按钮实现文件上传及控制文件上传类型-程序员宅基地

文章浏览阅读4.3k次。1.原生js实现文件上传html部分:上传文件js部分:upload(event) { //代替执行上传功能let it = event.target;$(it).next().click();},UploadFile() { //上传文件let msg = new FormData();msg.append('file', $('#uploadBillsInp')[0].files[0..._formdata.append('enctype', 'multipart/form-data');

Android后台源码,Android8.0的后台Service优化源码解析-程序员宅基地

文章浏览阅读245次。今天在用户的错误列表上看到这么个bugjava.lang.RuntimeException: Unable to start receiver com.anysoft.tyyd.appwidget.PlayAppWidgetProvider:java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com...._to start receiver com.mediatek.engineermode.emstartreceiver: java.lang.secur

深刻对比一下阿里云服务器和腾讯云服务器的优劣和区别_腾讯云与阿里云的优劣-程序员宅基地

文章浏览阅读2.5w次,点赞10次,收藏19次。我来简单对比阿里云服务器和腾讯云服务器的优劣和区别腾讯云相比阿里云优势不明显。阿里云比腾讯云开放的时间更早,辅助系统更完善些,功能更多可用性更强。但腾讯云不是单纯卖云服务的,凡是要接入腾讯的生态(比如微信小程序等)必须得用腾讯云服务器,腾讯云迅速发展壮大。腾讯云也在慢慢完善,大多数应用场景也都能满足,但就是对很多新技术的支持总是比阿里云慢一些,高级的配置定制也少一些。服务器结构不是很复杂的话用......_腾讯云与阿里云的优劣

应用C预处理命令_c 添加预处理命令-程序员宅基地

文章浏览阅读1.6k次。********************************LoongEmbedded******************************** 作者:LoongEmbedded(kandi)时间:2011.10.17类别:C基础************_c 添加预处理命令

Acrobat 版本校验异常,请检查网络连接是否正常:NotAllowedError;安全性设置禁止访问本属性或方法。_版本校验异常,请检查您的电脑网络连接是否正常-程序员宅基地

文章浏览阅读1.3w次。一、上传企业所得税纳税申报表时,Acrobat提示版本校验异常,请检查您的电脑网络连接是否正常:NotAllowedError;安全性设置禁止访问本属性或方法。二、解决方法打开Acrobat DC 阅读器的然后 在菜单栏 --找到编辑--再选择首选项,添加该文件(如图)或者添加文件夹路径(注意:添加文件夹下面的路径将全部都会有权限,如果不是非必要,可以直接添加文件。)完_版本校验异常,请检查您的电脑网络连接是否正常

贪心算法——C++实现中级案例_c++贪心算法代码-程序员宅基地

文章浏览阅读95次。在贪心算法中,我们每次都选择当前状态下最优决策,然后更新状态,直到达到最终状态。本文将介绍几个经典的贪心算法案例,并给出C++代码实现。有n个任务需要调度,每个任务需要占用一个时间单位,并且有一个冷却期k。贪心算法本身也是一个很好的思维训练工具,可以帮助我们更好地理解问题本质和设计高效的算法。给定一个按升序排列的整数数组,将其划分成多个长度至少为3的连续子序列,每个子序列只包含连续的整数。有m个孩子和n个糖果,每个孩子有对应的贪婪值g_i和每个糖果有对应的大小s_i。贪心算法——C++实现中级案例。_c++贪心算法代码

推荐文章

热门文章

相关标签