ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

左右值的概念

2020-06-18 15:02:23  阅读:656  来源: 互联网

标签:左右 右值 对象 左值 C++ 概念 引用 表达式


https://corecppil.github.io/CoreCpp2019/Presentations/Dan_Saks_Lvalues_and_Rvalues.pdf

简述

原版PPT 有31页,我主要摘取几个重要的点。

下面所说的对象都是广义的对象(object) 一个int float 都可以看作一个对象。而 类对象 与区分。

最后的总结是我自己总结的。

正文翻译

概览
  • 左右值并非C++ 语言特性,相反,它是表达式的属性。
  • 本篇将会通过以下视角理解左右值:
    • 内置运算符的行为
    • 为执行操作而生成的汇编
    • 相关编译器错误含义
    • 引用类型
    • 重载运算符
  • 左右值的含义已经发生变化
    • 在早期C ,左右值得概念非常简单。
    • 早期C++,增加了类,const,引用,使得左右值概念复杂起来。
    • 现代C++增加了右值引用,使得左右值进一步复杂
  • 本文从历史起源来解释左右值
左值

在 The C Programming Language 中,有以下的定义:

  • 左值 起源于 赋值表达式 E1 = E2 ,其中左操作数 E1 必须为左值表达式。

  • 一个左值是一个和对象相关的表达式。而一个对象是内存的一块区域。

    int n; //定义一个int 对象命名为 n
    n = 1; //赋值表达式
    

    在赋值表达式中:

    • n 是一个子表达式,和int 对象相关。它是左值
    • 1 是一个子表达式,但是没有和某个对象相关。它是右值
  • 所以右值的定义就是非左值

深入底层

为什么需要区分左值和右值?

  • 编译器可以假定右值不需要存储空间。(这块意思右值逻辑上不占空间,所以没有引用数组),
  • 这在为右值表达式生成代码提供了足够的自由。

继续上面的例子 n = 1

  • 如果 1 是左值的话,编译器认为 1 和一个初始化为1 的内存中对象相关联,假定这块区域称为 one

    • 当执行赋值编译器会生成类似于 mov n, one 即将one 所代表区域的数据拷贝到 n 所代表区域。

    • 而在某些CPU 上,提供直接操作数的功能,类似于 mov n, 1 即将值1 拷贝到n 所代表的区域。

    • 在这种情况下,右值永远不会作为一个存储在数据空间中的对象。相对的,它作为代码空间指令的一部分。

    • 在某些CPU 上,当将 1 赋值给对象,可能会采取这个方式: clr n inc n 即先将n 清零,再增加1 。

假设有这个例子 1 = n

  • 显然C/C++都会报错。但是精确原因是什么?
    • 赋值语句将一个值赋值给一个对象。
    • 左操作数必须为左值
    • 而1 是右值。

总结:所有C 中表达式 要么是左值,要么是右值。左值和数据空间中对象相关联,右值是非左值。对于非类类型的C++ 也是正确的。但如果有类类型,就不那么准确(类对象通常不管左右值都保存在数据空间)。

其它类型

字面值:大部分字面量都是右值(1,3.2,‘c’ 等),它们不一定占用数据空间。但有些字面量(“abcdefg” 等) 却是左值,占用数据空间。

枚举常量:枚举常量也是右值。

将左值用作右值

如果是非类类型,就直接将左值视为右值。如果是类类型,将会执行左值到右值的转换。

表达式的结果

表达式例如 m + n 产生的结果存放在编译器生成的临时对象,通常存放在寄存器中。像这些的临时对象都是右值。

所以 m + 1 = n 先计算 m + 1 产生右值,向右值赋值产生错误。

再例如 &n &1 其运算对象必须是左值,因为右值和数据空间中对象无关,不可寻址。它产生的结果却是右值。

再次强调 左值是编译期的属性,如果 *p 它运算对象可以是左值,也可以是右值例如( *(p+1) ),但它返回的是左值。即使p 指向nullptr ,编译也不会出错,不过运行会出错。

在C/C++ 中,非类类型的右值不会占用数据空间。而任何类型的左值都会占用。(虽然有时候编译器会优化左值,使其不占用空间,但你应该假定左值都是占用空间)。

常对象

常对象是可寻址的对象。

编译器可能会存储常对象,也可能会优化掉(例如C 中常量是常变量,而 C++ 中会被编译器替换)

C++引用类型

掌握以上左值和右值概念有助于理解C++的引用,引用使C++ 重载运算符的行为类似于内置运算符。

引用用于关联有名对象。引用底层是常量指针,自动解引用产生左值。

enum month {
Jan, Feb, Mar, ~~~, Dec, month_end
};
typedef enum month month;
~~~
for (month m = Jan; m <= Dec; ++m) {
~~~
}

这样的代码在C 中可以正常工作,然而在C++中编译错误。内置的++ 不能用于枚举变量。

常量引用

常量引用可以绑定到非常量,也可以绑定到常量。而普通引用只能绑定非常量。

常量引用自动解引用产生不可改的左值。

类似于普通 指针,普通引用只能绑定到左值上。

常量引用既可以绑定到左值也可以绑定到右值。

const int &thr = 3; 在这种情况下,运行到这时,程序会创建一个int 临时量,其值为3。然后用thr 绑定这个临时量。当离开 thr 的范围,程序销毁这个临时量。

const double &dthr = thr; 程序先会将thr 的值转化为 double ,创建double 临时量,然后保存转化的值。之后用 dthr 绑定到这个临时量。最后,离开范围会销毁这个临时量。

右值引用

在C++ 03 中称为引用的,在C++ 11 中称为 左值引用,为了与新增的右值引用所区分。

右值引用只能绑定右值,哪怕是常右值引用。而常左值引用也可以绑定右值。

现代C++ 通常使用右值操作来避免不必要的拷贝。

与成员函数

成员函数可以用引用限定符重载,左右值调用不同版本。

总结

左右值这个概念最初是从 C 语言来的,其为了CPU 能做某些优化而设置。此时的概念是和内存中区域相关的对象都成为左值,而右值是它的补集。右值不占内存空间。右值通常都是存在 CPU 中的数据。而字面值有可能右值(‘c’),也有可能是左值(“asdfdsasd”)。

到了早期C++ (C++ 11 之前),出现了引用等。此时右值有时也占用空间(例如右值类对象)。

到了现代C++ (C++ 11及以后),出现了左值引用,右值引用的概念。为了与右值引用区分,之前的引用统称为左值引用。右值引用的出现主要是为了避免不必要的拷贝。

标签:左右,右值,对象,左值,C++,概念,引用,表达式
来源: https://www.cnblogs.com/starrys/p/13157586.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有