赋值运算符
赋值运算符将右侧表达式的值存入左侧变量。C 语言除了简单的 = 赋值外,还提供 +=、-=、*=、/=、%= 等复合赋值运算符,它们将运算和赋值合并为一步。赋值表达式本身也有值——赋值后左侧变量的值——这一特性让连续赋值成为可能,但也可能写出晦涩的代码。
简单赋值
= 将右侧值赋给左侧变量:
int x;
x = 10; /* x 的值变为 10 */
int a, b, c;
a = b = c = 0; /* 连续赋值:从右到左 */
/* 等价于:a = (b = (c = 0)); */
/* c = 0 的值是 0,赋给 b;b = 0 的值是 0,赋给 a */
赋值表达式的值就是赋值后左侧变量的值。这一特性使得连续赋值合法,但也容易写出难以理解的代码:
if (x = 5) /* 将 5 赋给 x,表达式值为 5(真) */
printf("x is 5\n"); /* 总是执行! */
/* 正确做法 */
if (x == 5) /* 比较 x 是否等于 5 */
printf("x is 5\n");
= 和 == 的混淆是 C 语言中最常见的错误之一。开启编译器警告(-Wall)可以捕获大部分这类问题:
gcc -Wall -Wextra program.c
/* 警告:suggest parentheses around assignment used as truth value */
复合赋值运算符
复合赋值运算符将二元运算和赋值合并:
| 运算符 | 等价形式 | 示例 |
|---|---|---|
+= | a = a + b | a += 5 |
-= | a = a - b | a -= 3 |
*= | a = a * b | a *= 2 |
/= | a = a / b | a /= 4 |
%= | a = a % b | a %= 10 |
&= | a = a & b | a &= 0xFF |
|= | a = a | b | a |= 0x10 |
^= | a = a ^ b | a ^= mask |
<<= | a = a << b | a <<= 2 |
>>= | a = a >> b | a >>= 1 |
int sum = 0;
sum += 10; /* sum = sum + 10;sum 变为 10 */
sum -= 3; /* sum = 7 */
sum *= 2; /* sum = 14 */
sum /= 4; /* sum = 3(整数除法) */
sum %= 2; /* sum = 1 */
复合赋值的好处:
- 代码更简洁
- 左侧表达式只计算一次(对复杂表达式有性能优势)
/* 简单变量无区别 */
arr[i] = arr[i] + 5;
arr[i] += 5; /* 等价 */
/* 复杂表达式有区别 */
get_matrix()[row][col] = get_matrix()[row][col] + 5;
/* 两次函数调用 */
get_matrix()[row][col] += 5;
/* 只计算一次左侧地址 */
赋值与类型转换
赋值时,右侧值会自动转换为左侧变量的类型:
int i;
double d = 3.14;
i = d; /* double 转为 int,小数部分截断:i = 3 */
float f;
f = i; /* int 转为 float:f = 3.0 */
unsigned int u;
u = -5; /* 有符号转为无符号:u 变成很大的正数 */
窄化转换(将大范围类型赋给小范围类型)可能丢失信息,编译器通常会警告:
double pi = 3.14159;
int approx = pi; /* 警告:隐式转换可能丢失数据 */
/* 显式转换消除警告 */
int approx2 = (int)pi; /* 明确意图:我知道会截断 */
连续赋值与可读性
连续赋值可以初始化多个变量:
int x = 0, y = 0, z = 0; /* 推荐:清晰 */
int a, b, c;
a = b = c = 0; /* 可行,但稍难读 */
在 for 循环中,赋值表达式有时用于简洁的初始化:
int i, j;
for (i = 0, j = 10; i < j; i++, j--) {
/* i 从 0 递增,j 从 10 递减 */
}
注意:逗号运算符 , 与连续赋值不同。a = b = c = 0 是赋值运算符的右结合,i = 0, j = 10 是逗号运算符。
常见错误
混淆 = 和 ==:
if (x = 0) /* 将 0 赋给 x,表达式值为 0(假) */
printf("Never\n"); /* 不会执行 */
if (x == 0) /* 比较 x 是否等于 0 */
printf("Maybe\n"); /* 取决于 x 的值 */
在不该赋值的地方赋值:
const int max = 100;
max = 200; /* 编译错误:不能修改 const */
int arr[5];
arr = NULL; /* 编译错误:数组名不是可修改的左值 */
复合赋值的副作用:
int i = 5;
int result = (i += 3) + i; /* 未指定行为!i 在同一表达式中被修改两次 */
/* 结果可能是 16 或 18,取决于求值顺序 */
/* 安全做法 */
int i = 5;
i += 3;
int result = i + i; /* 明确:16 */
赋值给不兼容的指针:
int *p;
float f = 3.14f;
p = &f; /* 警告:类型不兼容 */