LearnCpp 9.2 — 值类别(左值和右值)

值类别(左值和右值)

在我们讨论我们的第一个复合类型(左值引用)之前,我们先来讨论什么是左值。在第1.10课——表达式简介中,可以执行以产生值的常量、变量、运算符和函数调用的组合”。(表达式是由一个或多个运算对象组成,对表达式求值将得到一个结果,字面值和变量是最简单的表达式。)
例如:

#include <iostream>

int main()
{
    std::cout << 2 + 3; // 表达式2 + 3产生值5
    return 0;
}

在上面的程序中,对表达式2 + 3求值以产生值 5,然后将其打印到控制台。
在第 5.4 课——递增/递减运算符和副作用中,我们还注意到表达式可以产生超过表达式范围外的副作用:

Side effect就是“副作用”,通常是对于一个函数而言的,说一个函数“有副作用”或者“没有副作用”。如果一个函数修改了自己范围之外的资源,那就叫做有副作用,反之,就是没有副作用。

#include <iostream>

int main()
{
    int x { 5 };
    ++x; //该表达式语句具有增加x的副作用
    std::cout << x; 

    return 0;
}

在上面的程序中,表达式++x增加 的值x,并且即使在表达式完成求值之后,该值仍然保持变化。
除了产生值和副作用之外,表达式还可以做一件事:它们可以计算对象或函数。我们稍后将进一步探讨这一点。

表达式的属性


为了帮助确定表达式应该如何计算以及它们可以在哪里使用,C++ 中的所有表达式都有两个属性:类型和值类别。

表达式的类型

表达式的类型等价于计算表达式产生的值、对象或函数的类型。例如:

#include <iostream>

int main()
{
    auto v1 { 12 / 4 }; // int / int => int
    auto v2 { 12.0 / 4 }; // double / int => double

    return 0;
}

对于v1,编译器将确定(在编译时)具有两个int操作数的除法将产生int结果,
所以int是表达式的类型。通过类型推断,int将为作为v1的类型。对于v2,编译器将确定(在编译时)一个double操作数和一个int操作数的除法将产生double结果。请记住,算术运算符必须具有匹配类型的操作数,因此在这种情况下,int操作数被转换为 double,并执行浮点除法。double这个表达式的类型也是如此。
编译器可以使用表达式的类型来确定表达式在给定上下文中是否有效。例如:

#include <iostream>

void print(int x)
{
    std::cout << x;
}

int main()
{
    print("foo"); // error: print() was expecting an int argument, we tried to pass in a string literal

    return 0;
}

在上面的程序中,print(int)函数需要一个int参数。但是,我们传入的表达式的类型(字符串字面量"foo")不匹配,并且找不到转换。因此产生编译错误。
请注意,表达式的类型必须在编译时确定(否则类型检查和类型推导将不起作用)——但是,表达式的值可以在编译时(如果表达式为 constexpr)或运行时确定(如果表达式不是 constexpr)。


表达式的值类别

现在考虑以下程序:

int main()
{
    int x{};

    x = 5; // valid: 我们可以给x赋值5
    5 = x; // error: 不能将x赋给数字字面量5(右值)

    return 0;
}

这些赋值语句之一是有效的(将值5赋给 variable x)而另一个则无效(将值赋给x意味着什么?)
那么编译器如何知道哪些表达式可以合法地出现在赋值语句的两侧呢?答案在于表达式的第二个属性:value category 表达式的值类别表明表达式是否解析为一个值、一个函数或某种类型的对象
在 C++11 之前,只有两种可能的值类别:左值(lvalue)和 右值(rvalue)

在 C++11 中,添加了三个附加值类别(glvalue、prvalue和xvalue)以支持名为移动语义(move semantics.)

广义左值[泛左值, glvalue, generalized left value]表达式是一个左值表达式或者x值表达式。广义表达式“有身份”,它可能能够“被移动”,也可能不能“被移动”。


纯右值[prvalue, pure right value]表达式是没有“身份”,能够“被移动”的表达式。


x值[xvalue, expiring value]表达式是既有“身份”[has identify],也能够“被移动”[can be moved from]的表达式。

作者注
在本课中,我们将依然使用 C++11 之前的值类别视图,因为这可以更好的介绍值类别(这就是我们目前所需要的全部内容)。我们将在以后的章节中介绍移动语义(以及额外的三个值类别)。

左值和右值表达式

左值(发音为“ell-value”,是“left value”或“locator value”的缩写,有时写为“l-value”)是一个表达式,其计算结果为具有标识的函数或对象。如果对象或函数具有标识符(例如变量或命名函数)或可确定取到的内存地址(可以使用 取内存地址符operator&,我们将在第 9.6 课- 指针简介中介绍),

#include <iostream>

int main()
{
    int x{};

    std::cout << x << '\n'; // x is an lvalue expression

    return 0;
}

在上面的程序中,表达式x是一个左值表达式,因为它的计算结果是变量x(它有一个标识符)。
由于在语言中引入了常量,左值有两种子类型:可修改的左值是可以修改其值的左值。不可修改的左值是其值不能被修改的左值(因为左值是 const 或 constexpr)。

#include <iostream>

int main()
{
    int x{};
    const double d{};

    std::cout << x << '\n'; 
    std::cout << d << '\n'; 

    return 0;
}

右值(发音为“arr-value”,“右值”的缩写,有时写为r-value)表达式不是左值。常见的右值包括文字字面量(string除外,它们是左值)和函数或运算符的返回值。右值仅存在于使用它们的表达式的范围内。

#include <iostream>

int return5()
{
    return 5;
}

int main()
{
    int x{ 5 }; // 5是右值表达式
    const double d{ 1.2 }; // 1.2 是右值表达式

    std::cout << x << '\n'; // x 是可修改的右值表达式
    std::cout << d << '\n'; // d 是不可修改的右值表达式
    std::cout << return5(); // return5() 是右值表达式(因为结果是按值返回的)
    std::cout << x + 1 << '\n'; // x + 1 是右值
    std::cout << static_cast<int>(d) << '\n'; // 转换结果是右值

    return 0;
}

您可能想知道为什么return5()和x + 1是右值:答案是因为这些表达式产生的值必须立即使用(在表达式的范围内)或者它们被丢弃。
现在我们可以回答为什么x = 5有效但无效的问题5 = x:赋值操作要求赋值的左操作数是可修改的左值表达式,而右操作数是右值表达式。后一个赋值 ( 5 = x) 失败,因为表达式5不是左值。

我的理解左值对应于一个实实在在的内存位置,右值只是临时的对象,它的内存对程序来说只能读不能写。以上原则或许不能精确描述左值和右值的定义,但是足够我们理解左值和右值的应用。

int main()
{
    int x{};

    x = 5; // valid:
    5 = x; // error: 

    return 0;
}

相关内容

可以在此处链接找到左值和右值表达式的完整列表。在 C++11 中,右值分为两个子类型:纯右值和 xvalues,所以我们在这里讨论的右值是这两个类别的总和。

左 值到 右值的转换

上面我们说过赋值运算符期望正确的操作数是右值表达式,那么为什么这样的代码可以工作呢?
在这里插入代码片`
答案是因为左值会隐式转换为右值,所以可以在需要右值的任何地方使用左值。

int main()
{
    int x{ 1 };
    int y{ 2 };

    x = y; // y 是可以修改的左值但是有效

    return 0;
}`

我看一下汇编
现在考虑这个片段:

int main()
{
    int x { 2 };

    x = x + 1;

    return 0;
}

在此声明中,该变量x在两种不同的上下文中使用。在赋值运算符的左侧,x是一个左值表达式,其计算结果为变量 x。在赋值运算符的右侧,x + 1是一个计算结果为 value 的右值表达式3。
现在我们已经介绍了左值,我们可以进入我们的第一个复合类型:左值引用lvalue reference.

关键见解
作为识别左值和右值表达式的经验法则:
左值是可寻址的变量,有持久性;
右值一般是不可寻址的常量,函数返回的值或在表达式求值过程中创建的无名临时对象,短暂性的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值