我们时常让计算机作出各种各样的计算,但如果不能将计算结果截留下来,先前的劳动便成了昙花一现,变得没有了意义。
也许我们一时还可以用cout
,将算得的结果立即变为屏幕上的黑底白字:
cout << 1 + 1;
也许我们还可以用cin
和变量。变量的存在给予了我们截留运算结果的可能,但目前为止,我们还仅仅在让它承载从键盘获得的值:
int x, y, z;
cin >> x >> y >> z;
cout << x * y * z;
接下来,我们便学习如何为变量赋予真正接纳数据的能力。
变量可以被赋予一个值,这种操作便叫作赋值:
int x, y, z;
x = 1;
y = 2 + 3;
z = x + 2 * y;
赋值时使用单一的一个等于号,左侧为被赋值的对象,右侧为用于赋值的值。左侧的可持久存储的对象所含的值称为左值,右侧的赋值之后结果就会被丢弃的值称为右值。
分左、右值的目的何在?其实这是想为程序员传达这样一种讯息:左值(例如变量、类的成员)不会凭空小时,它就在那里,你总有机会将其值取走;右值(例如一段表达式)就真的是一闪即逝,不论你是否将这个值存进某个左值所在的容器,或是将其作为某一段在它之上的表达式的一部分继续计算,求完这个值,它便会消失。赋值的目的,也本就是将如过客般的右值,变为原本左值所在容器的新主人。
看起来好复杂的样子。
不必忧虑,我们仍然可以继续接下来的学习,因为左、右值的性质直到靠后的章节才会用到。目前为止,我们仅需了解过左、右值,并且能做到使用一个单一的等于号赋值,就足够了。
赋值可以有什么新花样么?
答案是,有!下面来看一看混杂了各种运算符的奇怪赋值方法:
a += 1;
b -= 5;
c *= a + b + c;
这些是什么玩意?为什么等于号和普通符号粘在一起了?
其实,它们与以下表达式是等价的:
a = a + 1;
b = b - 5;
c = c * (a + b + c);
如果你是第一次看到这种表达式,不由得会觉得不合情理。毕竟,像a = a + 1
这种表达式在数学上根本不可能成立!
等一等,我们之前有了解过,两个等号才是比较相等,而一个等号的含义是赋值。
也就是说,这个表达式其实并没有什么毛病,只不过是有些违背常规思维罢了。
为了理解这个式子到底是怎么计算的,我们需要站在计算机的角度,具体地看每个步骤:
- 取出
a
,我们先假设a
原本等于 2 吧。 - 计算
a + 1
,也就是 2 + 1 = 3。 - 将算得的 3 存进
a
,此时a
便从原本的 2 变成了 3。
噢!这不就是让a
自己加一嘛!
照此推下去,我们也可以得出,b = b - 5
是指让b
自己减去 5,c = c * (a + b + c)
是让c
自己乘上a + b + c
的和。
刚开头的像a += 1
这样的式子,便是像a = a + 1
这样的式子的简写形式。如同+=
这种将其它二元运算符与赋值用的等于号拼起来是想传达一种理念:它左侧的变量不仅是此次运算的参与者,并且同时也是运算对象的承载者。这样的操作称为复合赋值。
我们经常会遇到要让变量自增或自减 1 的情形。
例如,游戏到了新的一回合,那就:
round += 1;
打怪升了一级,那就:
level += 1;
游戏角色阵亡,扣除一次复活机会,那就:
chances -= 1;
也许你会觉得,这样写也差不多够用了诶,起码比round = round + 1
这种的好多了。
不过,还有更简便的写法,它们被称作递增与递减:
round++; // 或 ++round
level++; // 或 ++level
chances--; // 或 --chances
得,这下连等于号都不用写了。
看起来是挺简单的——把+=
或-=
改为++
与--
就好了。不过,与复合赋值不同的是,这个++
与--
是既可写在变量之前,也可写在变量之后的。
这么做有区别么?有!简而言之,区别在于是先取值后赋值,还是先赋值后取值。
我们写个程序试一试:
#include <iostream>
using namespace std;
int main()
{
int n = 233, m = 233;
cout << ++n << m++ << endl;
cout << n << m << endl;
return 0;
}
运行时的输出是这样的:
234 233
234 234
前一个n
似乎合情合理,输出的是加过一后的值,但后一个m
却还没加就把数字打出来了。不过,从第二行cout
的输出结果可以看出,它们都自己加了一,工作的挺正常。
我们可以从中看出,如果递增(减)运算符是写在某变量之前的(称作前置运算符或前缀运算符),那么递增(减)将立即执行,你所收集到的值也会是加(减)过后的样子;如果写在某变量之后(称作后置运算符或后缀运算符),这个变量的递增(减)操作虽然也会立即执行,但执行过后你取得的并不是加(减)过后的值,而是该变量原本的值复制得到的一份副本。当我们将递增(减)用在了复合表达式时,便需要额外注意这些问题了。除非必要,我们一般使用前置版本的运算符,以避免不必要的复制造成的性能损失。
也许有聪明的读者,在思考能不能把值赋给一个右值,比如说数字之类的。
很遗憾,这种操作是不合法的——右值并没有储存其值的空间,哪怕你真的把值给了它,它也没地方存,只能是丢弃:
233 = 666; // 错误,233 是个字面量,属于右值
1.5 = 3.4; // 错误,1.5 是个字面量,也属于右值
另外,你也无法为一个常量赋值——常量是不可变的量,怎么能容许被赋值改变呢?
constexpr auto a = 233;
const auto b = a * 3;
a = 666; // 错误,constexpr 下的是常量中的常量,编译时就定型了,根本改不了
b = 666; // 错误,带有 const 意味着你不得改变它的值
现在我们回顾一下:
- 赋值,用于改变一个变量的值,如
a = 1 + b
。 - 复合赋值,可以用在自增或自减的情况,如
a += 3
,它与a = a + 3
是等价的。 - 递增与递减,是复合赋值的特殊情况,为一个变量自行加一或减一,如
a++
与++a
均和a += 1
等价。它既可写在变量前,也可写在变量后,分别称作前置运算符(前缀运算符)与后置运算符(后缀运算符),这将会决定它赋值与取值的先后顺序。 - 列表初始化等高级些的初始化方法,也适用于赋值。
- 不能为一个右值或常量赋值。
-
不以计算机运行,直接口述下述程序的运行结果,然后再以计算机验证你的答案:
int n = 3; cout << n++ << endl; cout << ++n << endl;