C++ 左值、右值、右值引用

2023-11-07

C++ 左值、右值、右值引用

发表于2016/2/3 15:49:35  98人阅读

分类: C++

在C语言中,我们常常会提起左值(lvalue)、右值(rvalue)这样的称呼。而在编译程序时,编译器有时也会在报出的错误信息中会包含左值、右值的说法。不过左值、右值通常不是通过一个严谨的定义而为人所知的,大多数时候左右值的定义与其判别方法是一体的。一个最为典型的判别方法就是,在赋值表达式中,出现在等号左边的就是“左值”,而在等号右边的,则称为“右值”。比如:

a = b + c;

在这个赋值表达式中,a就是一个左值,而b + c则是一个右值。这种识别左值、右值的方法在C++中依然有效。不过C++中还有一个被广泛认同的说法,那就是可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值。那么这个加法赋值表达式中,&a是允许的操作,但&(b + c)这样的操作则不会通过编译。因此a是一个左值,(b + c)是一个右值。

这些判别方法通常都非常有效。更为细致地,在C++11中,右值是由两个概念构成的,一个是将亡值(xvalue,eXpiring Value),另一个则是纯右值(prvalue,Pure Rvalue)。

其中纯右值就是C++98标准中右值的概念,讲的是用于辨识临时变量和一些不跟对象关联的值。比如非引用返回的函数返回的临时变量值(我们在前面多次提到了)就是一个纯右值。一些运算表达式,比如1 + 3产生的临时变量值,也是纯右值。而不跟对象关联的字面量值,比如:2、‘c’、true,也是纯右值。此外,类型转换函数的返回值、lambda表达式(见7.3节)等,也都是右值。

而将亡值则是C++11新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用),比如返回右值引用T&&的函数返回值、std::move的返回值(稍后解释),或者转换为T&&的类型转换函数的返回值(稍后解释)。而剩余的,可以标识函数、对象的值都属于左值。在C++11的程序中,所有的值必属于左值、将亡值、纯右值三者之一。

注意 事实上,之所以我们只知道一些关于左值、右值的判断而很少听到其真正的定义的一个原因就是—很难归纳。而且即使归纳了,也需要大量的解释。

在C++11中,右值引用就是对一个右值进行引用的类型。事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在。通常情况下,我们只能是从右值表达式获得其引用。比如:
T && a = ReturnRvalue();

这个表达式中,假设ReturnRvalue返回一个右值,我们就声明了一个名为a的右值引用,其值等于ReturnRvalue函数返回的临时变量的值。

为了区别于C++98中的引用类型,我们称C++98中的引用为“左值引用”(lvalue reference)。右值引用和左值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即进行初始化。而其原因可以理解为是引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。

在上面的例子中,ReturnRvalue函数返回的右值在表达式语句结束后,其生命也就终结了(通常我们也称其具有表达式生命期),而通过右值引用的声明,该右值又“重获新生”,其生命期将与右值引用类型变量a的生命期一样。只要a还“活着”,该右值临时量将会一直“存活”下去。

所以相比于以下语句的声明方式:
T b = ReturnRvalue();

我们刚才的右值引用变量声明,就会少一次对象的析构及一次对象的构造。因为a是右值引用,直接绑定了ReturnRvalue()返回的临时量,而b只是由临时值构造而成的,而临时量在表达式结束后会析构因应就会多一次析构和构造的开销。

不过值得指出的是,能够声明右值引用a的前提是ReturnRvalue返回的是一个右值。通常情况下,右值引用是不能够绑定到任何的左值的。比如下面的表达式就是无法通过编译的。
int c;
int && d = c;

相对地,在C++98标准中就已经出现的左值引用是否可以绑定到右值(由右值进行初始化)呢?比如:
T & e = ReturnRvalue();
const T & f = ReturnRvalue();

这样的语句是否能够通过编译呢?这里的答案是:e的初始化会导致编译时错误,而f则不会。

出现这样的状况的原因是,在常量左值引用在C++98标准中开始就是个“万能”的引用类型。它可以接受非常量左值、常量左值、右值对其进行初始化。而且在使用右值对其初始化的时候,常量左值引用还可以像右值引用一样将右值的生命期延长。不过相比于右值引用所引用的右值,常量左值所引用的右值在它的“余生”中只能是只读的。相对地,非常量左值只能接受非常量左值对其进行初始化。

既然常量左值引用在C++98中就已经出现,读者可能会努力地搜索记忆,想找出在C++中使用常量左值绑定右值的情况。不过可能一切并不如愿。这是因为,在C++11之前,左值、右值对于程序员来说,一直呈透明状态。不知道什么是左值、右值,并不影响写出正确的C++代码。引用的是左值和右值通常也并不重要。不过事实上,在C++98通过左值引用来绑定一个右值的情况并不少见,比如:
const bool & judgement = true;

就是一个使用常量左值引用来绑定右值的例子。不过与如下声明相比较看起来似乎差别不大。
const bool judgement = true;

可能很多程序员都没有注意到其中的差别(从语法上讲,前者直接使用了右值并为其“续命”,而后者的右值在表达式结束后就销毁了)。

事实上,即使在C++98中,我们也常可以使用常量左值引用来减少临时对象的开销,如代码清单3-20所示。

代码清单3-20
#include <iostream>
using namespace std;

struct Copyable {
    Copyable() {}
    Copyable(const Copyable &o) {
        cout << "Copied" << endl;
    }
};

Copyable ReturnRvalue() { return Copyable(); }
void AcceptVal(Copyable) {}
void AcceptRef(const Copyable & ) {}

int main() {
    cout << "Pass by value: " << endl;
    AcceptVal(ReturnRvalue()); // 临时值被拷贝传入
    cout << "Pass by reference: " << endl;
    AcceptRef(ReturnRvalue()); // 临时值被作为引用传递
}
// 编译选项:g++ 3-3-5.cpp -fno-elide-constructors

在代码清单3-20中,我们声明了结构体Copyable,该结构体的唯一的作用就是在被拷贝构造的时候打印一句话:Copied。而两个函数,AcceptVal使用了值传递参数,而AcceptRef使用了引用传递。在以ReturnRvalue返回的右值为参数的时候,AcceptRef就可以直接使用产生的临时值(并延长其生命期),而AcceptVal则不能直接使用临时对象。

编译运行代码清单3-20,可以得到以下结果:
Pass by value:
Copied
Copied
Pass by reference:
Copied

可以看到,由于使用了左值引用,临时对象被直接作为函数的参数,而不需要从中拷贝一次。读者可以自行分析一下输出结果,这里就不赘述了。而在C++11中,同样地,如果在代码清单3-20中以右值引用为参数声明如下函数:
void AcceptRvalueRef(Copyable && ) {}

也同样可以减少临时变量拷贝的开销。进一步地,还可以在AcceptRvalueRef中修改该临时值(这个时候临时值由于被右值引用参数所引用,已经获得了函数时间的生命期)。不过修改一个临时值的意义通常不大,除非像3.3.2节一样使用移动语义。
就本例而言,如果我们这样实现函数:
void AcceptRvalueRef(Copyable && s) {
   Copyable news = std::move(s);
}

这里std::move的作用是强制一个左值成为右值(看起来很奇怪?这个我们会在下面一节中解释)。该函数就是使用右值来初始化Copyable变量news。当然,如同我们在上小节提到的,使用移动语义的前提是Copyable还需要添加一个以右值引用为参数的移动构造函数,比如:
Copyable(Copyable &&o) { /* 实现移动语义 */ }

这样一来,如果Copyable类的临时对象(即ReturnRvalue返回的临时值)中包含一些大块内存的指针,news就可以如同代码清单3-19一样将临时值中的内存“窃”为己用,从而从这个以右值引用参数的AcceptRvalueRef函数中获得最大的收益。事实上,右值引用的来由从来就跟移动语义紧紧相关。这是右值存在的一个最大的价值(另外一个价值是用于转发,我们会在后面的小节中看到)。

对于本例而言,很有趣的是,读者也可以思考一下:如果我们不声明移动构造函数,而只声明一个常量左值的构造函数会发生什么?如同我们刚才提到的,常量左值引用是个“万能”的引用类型,无论左值还是右值,常量还是非常量,一概能够绑定。那么如果Copyable没有移动构造函数,下列语句:
Copyable news = std::move(s);

将调用以常量左值引用为参数的拷贝构造函数。这是一种非常安全的设计—移动不成,至少还可以执行拷贝。因此,通常情况下,程序员会为声明了移动构造函数的类声明一个常量左值为参数的拷贝构造函数,以保证在移动构造不成时,可以使用拷贝构造(不过,我们也会在之后看到一些特殊用途的反例)。

为了语义的完整,C++11中还存在着常量右值引用,比如我们通过以下代码声明一个常量右值引用。
const T && crvalueref = ReturnRvalue();

但是,一来右值引用主要就是为了移动语义,而移动语义需要右值是可以被修改的,那么常量右值引用在移动语义中就没有用武之处;二来如果要引用右值且让右值不可以更改,常量左值引用往往就足够了。因此在现在的情况下,我们还没有看到常量右值引用有何用处。

表3-1中,我们列出了在C++11中各种引用类型可以引用的值的类型。值得注意的是,只要能够绑定右值的引用类型,都能够延长右值的生命期。

表3-1 C++11中引用类型及其可以引用的值类型
引用类型 可以引用的值类型 注  记
 非常量左值 常量左值 非常量右值 常量右值 
非常量左值引用 Y N N N 无
常量左值引用 Y Y Y Y 全能类型,可用于拷贝语义
非常量右值引用 N N Y N 用于移动语义、完美转发
常量右值引用 N N Y Y 暂无用途

有的时候,我们可能不知道一个类型是否是引用类型,以及是左值引用还是右值引用(这在模板中比较常见)。标准库在<type_traits>头文件中提供了3个模板类:is_rvalue_reference、is_lvalue_reference、is_reference,可供我们进行判断。比如:
cout << is_rvalue_reference<string &&>::value;

我们通过模板类的成员value就可以打印出stirng &&是否是一个右值引用了。配合第4章中的类型推导操作符decltype,我们甚至还可以对变量的类型进行判断。当读者搞不清楚引用类型的时候,不妨使用这样的小工具实验一下。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ 左值、右值、右值引用 的相关文章

  • VS2019+Qt Release模式下显示打印信息

    使用VS作为Qt的开发工具 在Debug模式下开发 软件的运行效率不如Release模式下高 所以经常会出现在Release模式下 查看程序的打印输出 只需要在项目属性配置一下即可 点击项目右键 属性 生成事件 生成后事件 在命令行里添加下
  • 再见!微软官宣放弃Mac 版 Visual Studio IDE

    程序员的成长之路 互联网 程序员 技术 资料共享 关注 阅读本文大概需要 5 分钟 来自 撰稿丨千山 对于Visual Studio 只要是开发者 或多或少都接触过 发布于1997年的Visual Studio标志着微软第一次将这么多开发工
  • CMake使用小结

    CMake使用小结 指定本地库的位置 set Qt5 DIR path list APPEND CMAKE PREFIX PATH Qt5 DIR 设置编译输出的路径 set CMAKE ARCHIVE OUTPUT DIRECTORY D
  • 【计算机毕业设计】242高校图书馆设计与实现

    一 系统截图 需要演示视频可以私聊 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术 让传统数据信息的管理升级为软件存储 归纳 集中处理数据信息的管理方式 本高校图书馆就是在这样的大环境下诞生 其可以帮助管理者在短时间内处理完毕庞大的
  • cmake tutorial

    Tutorial 1 最基本的项目是从源代码构建可执行文件 对于简单的项目 三行CMakeLists txt足够了 这就是本教程的起点 在Step1目录下创建一个CMakeLists txt 如下所示 cmake minimum requi
  • ‘open3d.open3d.geometry.PointCloud‘ object has no attribute ‘voxel_down_sample‘

    scene cloud open3d geometry PointCloud scene cloud points open3d utility Vector3dVector scene points scene cloud scene c
  • 利用Visual Studio程序写出循环结构求1+3+5+....+100的值

    下面是在 Visual Studio 中使用 C 编写循环结构求 1 3 5 100 的值的示例代码 int sum 0 for int i 1 i lt 100 i 2 sum i Console WriteLine sum 上面的代码使
  • 用C语言打印九九乘法表

    运用c语言的分支和循环的知识就可以打印出来9 9的乘法表 效果如图 具体代码 可以深刻理解循环和嵌套循环的应用 int main int i 0 行数 for i 1 i lt 9 i 行数 打印9行 int j 0 列数 for j 1
  • 关于Visual Studio 不支持x64 内联汇编分析

    记录一下今天的大坑 实在是有必要记录一下 调程序发现参数在函数传递时 出现了异常的值 已经确认不是指针破坏的问题 用汇编看了下 发现汇编寄存器地址都取错了 在release开启o2优化时出现 关掉又正常 实在是百思不得其解 对于内联汇编 其
  • VS2019安装Qt插件教程,发现下载不了问题解决

    1 打开VS 最上方工具栏中点击扩展窗口 选择管理扩展 2 在右边搜索中搜索qt出现以下界面 这时可能出现问题 再点击下载发现迟迟下载不了 或者是下载到一定地步后无法下载 再或者是下载完成后安装无反应 解决办法 点击有点的详细信息或者进入如
  • VS登录问题 无法刷新账户凭证 (Microsoft Visual Studio)

    1 问题描述 VS登录时 弹出错误 无法刷新此账户的凭证 2 解决办法 打开默认的浏览器 我的电脑上默认浏览器是搜狗 1 打开选项设置 2 打开Internet选项 点击连接 再点击局域网设置 3 勾选自动检测设置 取消其它勾选 4 VS恢
  • 【已解决】因为计算机丢失D3DCOMPILER_47.dll 的解决方法

    因为计算机丢失D3DCOMPILER 47 dll 的解决方法 D3DCompiler 47 dll是电脑系统中一个非常重要的文件 许多的游戏或者软件运行都需要它的支持 如果没有这个文件可能会造成一些游戏或者软件运行不了 假如您的系统提示
  • 缺少msvcp120.dll、msvcr120.dll解决办法

    缺少msvcp120 dll msvcr120 dll解决办法 丢失或缺少msvcp120 dll msvcr120 dll等这些报错是因为我们没有安装vc 运行库 看一下报错对应的数字对应的版本 msvcp msvcr60 71和80 d
  • Visual Studio 2022配置PCL1.12.1版本点云库

    说明 这个配置步骤是当时自己参考2019配置的 当时网上还没有VS2022的配置步骤 我在自己电脑上是配置成功了 所以我将配置过程记录了下来 仅供参考 1 软件下载 Microsoft Visual Studio 2022 Pro http
  • Visual Studio Code 的安装教程和配置C语言环境(详解版)

    最近想装一个VS Code 来写C C 程序 但是看了网上的很多教程发现并不是那么的好 大部分都尝试失败了 摸索了很久找到了一个比较可靠的方法 目录 一 Visual Studio Code 的安装教程 二 接下来就是C语言的环境配置 三
  • 轻松解决Visual Studio登陆凭证问题

    很多同学在使用Visual Studio的时候 在试用期过后 需要登陆验证时 往往会登陆不上 出现 无法刷新用户凭证 无法获得信赖 等问题 如图 解决方式 点击账户选项 使用系统Web浏览器
  • VS+QT开发Ocx/ActiveX控件 一

    VS QT开发Ocx ActiveX控件 一 VS QT开发Ocx ActiveX控件 网页中全屏 二 QT开发ActiveX控件 一 所用IDE版本 需用管理员权限 二 创建ActiveX 三 qt desinger 四 regsvr32
  • Vscode中Python无法将pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称

    在Python需要pip下载插件时报错 是因为没有把Python安装路径下的Scripts添加到系统的path路径中 设置完之后重新启动Vscode就可以使用pip了
  • 解决方案不显示分类的

    解决方案无法显示头文件 源文件等分类 现象 正常显示 错误显示 原因 对应解决方案的filters文件里有不匹配的标签
  • openCV在Visual Studio2019下的集成使用

    文章目录 下载OpenCV工具 选择合适库文件 使用visual studio创建空项目 测试运行 运行结果 下载OpenCV工具 官网下载实在太慢 还老实下不下来 下面从网上找到些别人分享的一些版本 从3 4到4 7 放到了网盘里 请按需

随机推荐

  • 解决Ubuntu中you are in emergency mode(紧急模式)问题

    我安装的ubuntu18 04 win10的双系统 不知道为什么我的ubuntu开机出现you are in emergency mode 有人说是未正常关键导致 但是我关机都是shutdown 我们现在来解决you are in emer
  • 【CSS】CSS 选择器

    CSS 选择器 1 基础选择器 1 1 元素选择器 语法 标签名 元素选择器会选中对应标签名的HTML元素 例如 p div span 等 1 2 类选择器 语法 类名 类选择器会选中class属性为指定类名的HTML元素 例如 div c
  • 29岁,转行python,一举拿下14k的offer,高薪方法太绝了

    前言 我转行之前从事的工作是商场管理 努力了4年左右的时间才做到楼层经理 但是工资太低并且事情太多 薪资才6K 我的工作需要东奔西跑 每次前往下一个工作地点的时候 我就在想我真的喜欢这种生活吗 偶然有一天 在微信平台上看到的一则Python
  • 美通企业日报

    今日要闻 2019中国最佳表现城市排行榜发布 世界著名智库米尔
  • 【C语言程序设计】学生信息管理系统

    目录 一 实验任务 二 实验代码 三 运行效果 一 实验任务 学生信息管理系统设计 学生信息包括 学号 姓名 年龄 性别 出生年月 地址 电话 E mail等 试设计一学生信息管理系统 使之能提供以下功能 1 系统以菜单方式工作 2 学生信
  • Python 1——温度转换代码分析

    Python 1 主要参考中国大学MOOC 北京理工大学课程 TempConvert2 py 注释 TempStr input 请输入带有符号的温度值 if TempStr 1 in F f C eval TempStr 0 1 32 1
  • c语言程序设计薪水,C语言程序设计  输入10名职工的职工号和工资,计算平均工资并输出低于平均工资的职工号和工资。...

    满意答案 偶要蟹黄堡 2013 11 24 采纳率 40 等级 12 已帮助 6292人 我给你编写了一个简单的 你自己参考一下 自己润色吧 include typedef struct employee char employee num
  • GNSS系列(1)------GNSS坐标系转换

    由于工作需要 最近开启了GNSS系列文章的撰写工作 发布于公司官网 现将其同步至CSDN 原文链接 http onemo10086 com school article 165 GNSS定位不准确 漂移了好几公里 是怎么回事呢 相信有不少用
  • Unity窗口标题栏显示项目路径

    Unity项目开发过程中会有主干分支等等很多版本 同时打开多个项目时无法区分各个Unity窗口是哪个版本 可以把项目完整路径设置到窗口标题栏方便区分 方法如下 UpdateUnityEditorProcess cs using System
  • 类 模板 已经声明为非类 模板

    类 模板 已经声明为非类 模板 解决方案 原因是在当前包下 已经定义了AA类 解决方案 更改模板类AA的类名
  • Linux系统环境搭建

    一 VM虚拟机创建步骤 1 打开VM点击新创建虚拟机 一直点击下一步到下图显示这里 然后点击自定义硬件 选择IOS映像文件 选择映像文件后点击关闭 然后启动虚拟机 点击连接网络开关 显示已连接后 点击开始安装虚拟机 设置密码 安装完成后点击
  • Ubuntu 生成core文件

    man core 命令 可查阅关于core dump file的详细信息 下文是我的一些总结 core dump file 是进程终止时的内存映像 是个磁盘文件 可用来调试 但是有时程序显示 段错误 核心已转储 时 并没有生成core文件
  • STM32实现红外感应传感器功能

    原理 人体都有恒定的体温 一般在 37 度 所以会发出特定波长 10um 左右的红外线 人体发射的 10um 左右的红外线通过菲涅尔滤光片增强后聚集到红外感应源上 这种元件在接收到人体红外辐射温度发生变化时就会失去电荷平衡 向外释放电荷 后
  • 西门子tcpip通讯实例_西门子1200PLC,组态王与Access数据库通讯--⑤设置PLC设备

    西门子1200PLC 组态王与Access数据库通讯 设置PLC设备 设置PLC设备 1 0 打开组态王软件 打开工程 会看到如下界面 点击COM2 2 0 可以看到新建 点击新建设备 现在我们要连的是西门子系列的1200 所有选择西门子
  • vscode C++配置

    一般配置 在创建了工作区域以后 会有一个 vscode文件夹 里边有launch json tasks json两个文件 launch json 使用 IntelliSense 了解相关属性 悬停以查看现有属性的描述 欲了解更多信息 请访问
  • 向eclipse的JavaWeb项目中导入jar包--备忘录

    一 在你所需的jar包网站下载对应的jar包 如org apache commons lang jar 二 复制粘贴到该JavaWeb的WEB INF目录下的lib目录下 如 三 右键该jar包选择Build Path gt Configu
  • 【华为机试真题 JAVA】字符串删除后的最大数值-100

    题目描述 给定一个由纯数字组成以宇符串表示的数值 现要求字符串中的每个数字最多只能出现2次 超过的需要进行删除 删除某个重复的数字后 其它数字相对位置保持不变 如34533 数字3重复超过2次 需要册除其中一个3 删除第一个3后获得最大数值
  • cuda C++ cmake makefile

    https blog csdn net u011679999 article details 80041081 spm 1001 2014 3001 5502 https blog csdn net comedate article det
  • 控制微信公众号物理键返回url

    总有一些奇葩的需求 在公众号中 如果你写的模块是一个公众号的子模块 那么当你在模块的第一级时 再点返回 应该是返回到公众号的主页 也就是模块区域 而不是返回你上一个地址 直接上代码 1 先监听到物理返回键 在mounted钩子定义 再去de
  • C++ 左值、右值、右值引用

    C 左值 右值 右值引用 发表于2016 2 3 15 49 35 98人阅读 分类 C 在C语言中 我们常常会提起左值 lvalue 右值 rvalue 这样的称呼 而在编译程序时 编译器有时也会在报出的错误信息中会包含左值 右值的说法