理解C++值类别(lvalue, rvalue, prvalue, xvalue)

C++ 值类别 (value categories)概述

在 C++中值类别并不是语言特性, 他们更多的是表达式的语法属性. 理解它们有助于理解:

  • 内置类型和用户定义的操作符
  • 引用类型
  • 移动语义

值类别也是不断演进的:

  • 在早期的 C 语言中, 只有两种值类别: 左值(lvalue)和右值(rvalue). 与之相关的概念也很简单.
  • 早期的 C++扩增了类(class), const修饰符, 和引用类型. 这使得值类别更加丰富.
  • 现代 C++ 引入了右值引用(rvalue references). 为此不得不引入更多的值类别来描述相关的行为.

左值和右值的定义

C Programming Language 这本书中, Kernighan 和 Ritchie 给出的定义是: 出现在赋值表达式=号左边的操作数(operand)是左值.

E1 = E2;
  1. 一个左值(lvalue)是指向一个对象(Object)的表达式. 一个对象则指一块存储区域.

    int n; // 定义了一个名为`n`的int类型对象
    n = 1; // 一个赋值表达式
    

    上面代码中:

    • n 表示一个 int 对象, 它是左值.
    • 1 表示一个整型常量, 它是右值.
  2. 右值(rvalue)被定义为: 不是左值的表达式.

    下面的语句尝试修改一个整型常量 1. 当然, C++会拒绝它.

    1 = n; // 明显不合法
    

    因为一个赋值语句会给一个对象赋值, 赋值语句的左边操作数必须是左值, 但是 1 不是左值, 它是右值.

为什么要区分左值和右值

  • 这样的话编译器可以假设右值(rvalue)不会占用存储空间.
  • 这给编译器机器码生成提供了更多的自由.

右值的存储

我们再次看这个例子:

int n;
n = 1;
  1. 一个编译器或许会把字面量1存放在数据区, 把1看做是一个左值. 这样的情况下它会生成下面的汇编代码:

    one:      ; 一个标签, 指代下面对象的地址
      .word 1 ; 为整数1分配一个存储位置
    

    那么编译器将会从one复制到n

    move n, one ; 复制`one`地址的值到`n`变量地址
    
  2. 实际上, 现代 CPU 上, 有源操作数是立即数(immediate operand)的指令. 源操作数是立即数意味着数值是指令的一部分.

    在汇编语言中, 会这样写:

    mov n, #1 ; 将数值 #1 拷贝到地址`n`
    

    这种情况下: 右值 1 将会作为指令的一部分在代码区.

    以 x86-64 为例, 我们假定n已经被加载到寄存器RAX中, 那么n=1就可以被写为: MOV RAX, 1, 翻译为机器码就是:

    B8 01 00 00 00
    

    其中:

    • B8是操作码, 表示向 RAX 寄存器赋值一个 32 位的立即数.
    • 01 00 00 00 是立即数 1 的小端序表示(最低有效字节位于最低地址).

字面量

很多的字面量是右值, 他们不一定占用数据存储. 包括:

  • 数字字面量, 如 3, 3.14
  • 字符字面量, 如 ‘a’, ‘b’

然而, 字符串字面量是左值. 它们占用存储空间. 比如"Hello"这种字符串字面量.


左值和右值的区别

  1. 一个左值可以出现在赋值表达式的任意一边, 比如:

    int m, n;
    m = n; // OK, m和n均是左值
    

    这个表达式将左值(n)当做右值来使用. 从 C++术语讲, 这进行了一次左值到右值的转换.

  2. 表达式的操作数可以是左值或右值.

    比如, '+'操作符必须是两个表达式.

    int x;
    
    x + 2; // OK, lvalue + rvalue
    2 + x; // OK, rvalue + lvalue
    
  3. 运算结果是右值: 对于内置的二元操作符(不包含赋值=操作符), 比如’+', 它的操作数可能是左值或右值, 它的结果是一个右值.

    • 表达式m+n的结果将放在一个编译器生成的临时变量中, 通常是一个 CPU 寄存器. 这样的临时变量通常被称为右值(rvalue).
    • 表达式m + 1 = n 是错误的, 因为m+1的结果是一个右值, 不能出现在赋值表达式的左边.
  4. 解引用操作符*是左值: 解引用操作符*的结果是一个左值. 一个指针p可以指向一个对象, 所以*p是一个左值.

    int arr
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

arong-xu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值