5.2 表达式语句
表达式是用于实现以下一个或多个目的而使用的运算符和操作数的序列:
1)计算来自操作数的值。
2)指定对象或函数。
3)生成“副作用”。 (副作用是表达式的计算之外的所有操作 - 例如,修改对象的值。)
操作数也称为运算量或数据,可以是常量、变量或函数;只有数据的式子是最简单的表达式。
操作符描述的是对数据进行的操作,C++语言的操作符有40多种;根据操作符需要的操作数个数不同,将操作符分为3类:单目运算符、双目运算符和三目运算符;表达式可以嵌套,如2+3+5*sizeof (int)/345;
表达式根据某些约定、求值次序、结合性和优先级规则来进行计算
1)约定:类型转换的约定,由低≥高
2)求值次序:与编译器有关
表达式蕴涵着值,在执行时,被机器估值(求值),被估出的值是临时的,它何去何从取决表达式所在语句的操值语义。
表达式语句导致计算表达式。出于表达式语句的原因,不会发生控制或迭代的传输。
表达式语句的语法就是:
[expression ] ;
在执行下一个语句前,将计算表达式语句中的所有表达式并完成所有副作用。最常用的表达式语句是赋值和函数调用。由于表达式是可选的,因此单独的分号被视为空表达式语句,称为 null 语句。
5.2.1 左值和右值
每个 C++ 表达式是左值或右值。左值是指在单个表达式的外部保留的对象。可以将左值视为具有名称的对象。所有变量(包括不能更改的 (const) 变量)都是左值。左值是一个不在使用它的表达式的外部保留的临时值。若要更好地了解左值和右值之间的区别,请考虑下面的示例:
// lvalues_and_rvalues1.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;
int main()
{
int x = 3 + 4;
cout << x << endl;
}
在此示例中,x 是左值,因为它在定义它的表达式的外部保留。 表达式 3 + 4 是为一个右值,因为其计算结果为不在定义它的表达式的外部保留的临时值。
以下示例演示左值和右值的多种正确的和错误的用法:
// lvalues_and_rvalues2.cpp
int main()
{
int i, j, *p;
// Correct usage: the variable i is an lvalue.
i = 7;
// Incorrect usage: The left operand must be an lvalue (C2106).
7 = i; // C2106
j * 4 = 7; // C2106
// Correct usage: the dereferenced pointer is an lvalue.
*p = i;
const int ci = 7;
// Incorrect usage: the variable is a non-modifiable lvalue (C3892).
ci = 9; // C3892
// Correct usage: the conditional operator returns an lvalue.
((i < 3) ? i : j) = 7;
}
当引用对象引用时,通常会使用术语“左值”和“右值”。
拓展:
在C++11中所有的值必属于左值、右值两者之一,右值又可以细分为纯右值、将亡值。在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)。举个例子,int a = b+c, a 就是左值,其有变量名为a,通过&a可以获取该变量的地址;表达式b+c、函数int func()的返回值是右值,在其被赋值给某一变量前,我们不能通过变量名找到它,&(b+c)这样的操作则不会通过编译。
右值、将亡值
在理解C++11的右值前,先看看C++98中右值的概念:C++98中右值是纯右值,纯右值指的是临时变量值、不跟对象关联的字面量值。临时变量指的是非引用返回的函数返回值、表达式等,例如函数int func()的返回值,表达式a+b;不跟对象关联的字面量值,例如true,2,”C”等。
C++11对C++98中的右值进行了扩充。在C++11中右值又分为纯右值(prvalue,Pure Rvalue)和将亡值(xvalue,eXpiring Value)。其中纯右值的概念等同于我们在C++98标准中右值的概念,指的是临时变量和不跟对象关联的字面量值;将亡值则是C++11新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用),比如返回右值引用T&&的函数返回值、std::move的返回值,或者转换为T&&的类型转换函数的返回值。
将亡值可以理解为通过“盗取”其他变量内存空间的方式获取到的值。在确保其他变量不再被使用、或即将被销毁时,通过“盗取”的方式可以避免内存空间的释放和分配,能够延长变量值的生命期。
左值引用、右值引用:
左值引用就是对一个左值进行引用的类型。右值引用就是对一个右值进行引用的类型,事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在。
右值引用和左值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即进行初始化。而其原因可以理解为是引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。
左值引用通常也不能绑定到右值,但常量左值引用是个“万能”的引用类型。它可以接受非常量左值、常量左值、右值对其进行初始化。不过常量左值所引用的右值在它的“余生”中只能是只读的。相对地,非常量左值只能接受非常量左值对其进行初始化。
int &a = 2; # 左值引用绑定到右值,编译失败
int b = 2; # 非常量左值
const int &c = b; # 常量左值引用绑定到非常量左值,编译通过
const int d = 2; # 常量左值
const int &e = c; # 常量左值引用绑定到常量左值,编译通过
const int &b =2; # 常量左值引用绑定到右值,编程通过
右值引用通常不能绑定到任何的左值,要想绑定一个左值到右值引用,通常需要std::move()将左值强制转换为右值,例如:
int a;
int &&r1 = c; # 编译失败
int &&r2 = std::move(a); # 编译通过
下表列出了在C++11中各种引用类型可以引用的值的类型。值得注意的是,只要能够绑定右值的引用类型,都能够延长右值的生命期。