C语言基础要素(010):复合赋值
赋值运算的右结合性
对于运算符的右结合性,我们从初始化与赋值开始。
int width = 2 * 3; // 使用表达式初始化
width = 3 + 2; // 使用表达式赋值
代码极其简单,但有两个问题需要你回答。
- 定义width变量时,它的初始值是多少?
- 给width变量赋值后,它的值又是多少?
相信你会随口说出,width的值分别是6与5(表达式的结果),而不是2与3("="右边的第一个数值)。稍加思索,你会发现自己先计算了赋值运算符右侧的整个表达式,然后将结果赋予width变量,我们称这类运算符具有右结合性。
double x, y, z, w;
x = y = z = w = 1.0;
// 等同上面的代码
double x, y, z, w;
w = 1.0f;
z = w;
y = z;
x = y;
左值与右值
我们曾以盒子类比了变量的存储特性,同样的我们也可以查看盒子中的物品,即读取变量的值。只要变量一直存在,我们就可以对其无限次读取,而且其值始终是存在的。
int a = 65;
printf("%d\n", a); // 输出65
printf("%d\n", a); // 输出65,这不会因为上面查看a的值而使盒子为空。
int b = a; // 使用a的值初始化b
printf("%d\n", a); // 输出65,这不会因为上次初始化b而使盒子a为空。
可以看出,一个变量(如这里的变量a)在不同的语义环境下其意义不同:
- 赋值时:变量取盒子的存储功能这一概念
- 读取时:变量取查看盒子中数据的概念
从赋值运算符的角度讲,变量在左侧时代表着写入操作,此时变量是个左值(lval)
,左值在程序中有一个确切的内存地址以用于写数据;在右侧时代表着读取操作,称为右值(rval)。下面的例子展示了可能的左值与右值表现方式。
// 左值 | 右值
//===================== | ================
int a = 3; // 变量a | 常数3
int b = a; // 变量b | 变量a
int c = a + b; // 变量c | a+b的结果
我们不能将一个变量赋值给一个常数(它不是一个盒子),但是,同一个变量却可以出现在赋值运算符的左右两侧,在左边取盒子存储的概念,在右边取读取盒中数值的概念。
int a = 3;
3 = a; // err
a = a; // ok
a = a + 3; // ok
复合赋值语句
像 a=a; 这种做法你几乎不会用,但让a自身发变化是可行的。比如说有一个记录英雄血量的hp变量,当角色获得能量后hp值增加,我们可以这样编写:
int hp = 9;
int food_hp = 10;
hp = hp + food_hp;
对于这种实现,C语言也提供了一种简化的语法,称为复合赋值运算。
int hp = 9;
int food_hp = 10;
hp += food_hp; // 少输几个字符
这里,我们使用 +=
运算符完成了等同的操作,当使用这种简写形式时,不论右侧表达式多复杂,始终要将它们看作是一个整体,并与左侧变量运算求值。
int a = 2;
a += 3; // 相当于 a = a + 3;
a *= 6 - 4; // 相当于 a = a * (6 - 4);
除了+=,*=
简写形式,C语言还提供了-=、/=、%=
简写运算符,再次对于/=,%=运算进行提醒:除数不能为0。
#include <stdio.h>
int main()
{
float x, y, z, w;
x = y = z = w = 1.0f;
printf("x = %f\n", x);
int a = 2;
a = a + 3; // 相当于 a += 3;
printf("a = %d\n", a);
a *= 6 - 4; // 相当于 a = a * (6 - 4);
printf("a = %d\n", a);
return 0;
}
练习
- 编写程序,让变量 area 存储矩形面积。
- 使用复合赋值运算符,让矩形面积缩小一半。
- 观察习题2中的程序,指出变量左右值使用情况。