表达式求值
副作用与左值
类型转换
编码实践
到目前为止所涉及的运算符:
| 优先级 | 名称 | 符号 | 结合性 |
|---|---|---|---|
| 1 | 自增(后缀) | ++ |
left |
| 自减(后缀) | -- |
||
| 2 | 自增(前缀) | ++ |
right |
| 自减(前缀) | -- |
||
| 一元正号 | + |
||
| 一元负号 | - |
||
| 3 | 乘法类 | * / % |
left |
| 4 | 加法类 | + - |
left |
| 5 | 赋值 | = *= /= %= += -= |
right |
单目运算符 (1、2 级) 高于双目算术, 赋值 (5 级) 最低
结合性: 后缀 ++/-- 左结合; 前缀 ++/--、一元 +/-、赋值是右结合; 算术左结合
优先级表可用于给缺少括号的表达式加括号: 从最高优先级开始, 在运算符及其操作数周围加上括号 (下一页演示)
例子:
a = b += c++ - d + --e / -f |
排序 |
|---|---|
a = b += (c++) - d + --e / -f |
1 |
a = b += (c++) - d + (--e) / (-f) |
2 |
a = b += (c++) - d + ((--e) / (-f)) |
3 |
a = b += (((c++) - d) + ((--e) / (-f))) |
4 |
(a = (b += (((c++) - d) + ((--e) / (-f))))) |
5 |
表达式的值可能取决于子表达式的计算顺序——C 没有定义这个先后 (逻辑与或、条件、逗号运算符除外)
(a + b) * (c - d): 先算哪个括号? 不确定; 大多数表达式结果相同, 但子表达式修改操作数时就不成立:
a = 5;
c = (b = a + 2) - (a = 1);
第二条语句的执行结果是未定义的
避免在表达式中访问变量值的同时也修改该变量的值
为防止出现问题, 最好避免在子表达式中使用赋值运算符
相反, 建议使用一串分离的赋值表达式, 这样 c的值是确定的, 将始终为 6
除了赋值运算符之外, 唯一修改其操作数的运算符是自增和自减
很自然地认为j被赋值为4. 然而, j也可以被赋值为6——求值顺序不同, 结果不同
a = 5;
b = a + 2;
a = 1;
c = b - a;
i = 2;
j = i * i++;
c = (b = a + 2) - (a = 1);和j = i * i++;均会产生未定义的行为
未定义行为的可能影响:
当使用不同的编译器编译时, 程序运行的行为可能会有所不同
该程序可能一开始就无法编译
能编译但无法运行
能运行, 但程序可能会崩溃、行为不正常或产生无意义的结果
应避免未定义的行为
修改操作数的运算符被称为具有 副作用 , 如 i + j 没有 副作用
简单的赋值运算符有副作用: 它修改了左操作数, 表达式 i = 0 的结果为 0, 它的副作用是将 0 赋值给i
允许 v 类型值的地方都可以进行形如 v = e 的赋值:
i = 1;
k = 1 + (j = i);
printf("%d %d %d\n", i, j, k);
/* prints "1 1 2" */
嵌入式赋值 会使程序难以阅读, 容易隐藏错误
赋值运算符需要一个 左值 作为其左操作数
左值 表示存储在计算机内存中的对象, 而不是常量或计算结果
变量是左值
10或2 * i这样的表达式不是左值
12 = i; /*** WRONG ***/
i + j = 0; /*** WRONG ***/
-i = j; /*** WRONG ***/
编译器将产生一条错误消息, 例如"invalid lvalue in assignment"
隐式转换: 不同类型混合运算/赋值时, 编译器自动转换
赋值: 右值自动转换为左值变量的类型
运算: 较低类型提升为较高类型 (char/short → int; int + double → double)
int i;
double d = 3.14159;
i = d; /* i 为 3 (截断小数) */
d = i; /* d 为 3.0 */
1 / 2; /* 0 (整数除法) */
1.0 / 2; /* 0.5 (2 提升为 double) */
int a = 9, b = 4;
printf("%d\n", a / b); /* 2: 整数除法截断 */
printf("%f\n", a / b); /* 错! 用 %f 打印 int → 结果不可预测 */
printf("%f\n", (double)a / b); /* 2.250000: 先转换再除 */
printf("%f\n", a / 4.0); /* 2.250000: 4.0 是 double, 整体提升 */
常见的运行结果:
2
0.000000
2.250000
2.250000
类型转换发生在运算时: a / b 两个 int 相除, 先截断、后转换, 轮到 printf 时已经来不及了
%f 期待 double 却收到 int → 未定义行为, 常见打出 0.000000 或乱码——printf 不会替你检查类型
正确姿势: 转换一个操作数 (double)a / b, 或直接用浮点字面量 a / 4.0
强制转换: 显式地把值转换为目标类型
(float) 1 / 2; /* 0.5 */
(int) 3.9; /* 3 */
强制转换的优先级很高, 只作用于紧邻的操作数; 需要转换整个表达式时要加括号, 如 (int) (3.9 + 1.1)
强制转换还能避免溢出: 先提升, 再运算, 而不是算完再提升
long i; int j = 1000000;
i = (long) j * j; /* 正确: j 先转成 long, 乘法不会溢出 */
i = (long) (j * j); /* 错误: j * j 已经在 int 里溢出了 */
--
\(6\) 克氧气的分子数是多少?
\(Q = 6 / 32 \times 6.02 \times 10^{23}\)
两种格式输出, 结果均用科学计数法表示:
第一行: 小数点后保留 \(3\) 位 → %.3e
第二行: 保留 \(5\) 位有效数字 → %.5g
提醒: 6 和 32 都是整数——直接写 6 / 32 会发生什么? (上节课 9/4 的坑!) 先自己写, 再翻页看参考代码
/*
* mol.c(6克氧气的分子数)
*/
#include <stdio.h>
int main(void) {
const double MOL = 6.02E23;
const int GRAM_PER_MOL = 32;
int mass = 6;
double quantity = mass * 1.0 /GRAM_PER_MOL * MOL;
printf("quantity = %.3e\nquantity = %.5g\n", quantity, quantity);
return 0;
}
mass * 1.0 / GRAM_PER_MOL: 乘上 1.0 把运算提升为浮点, 避开整数除法的坑
%.3e 与 %.5g 分别控制小数位与有效数字
运行结果: quantity = 1.129e+23 与 1.1288e+23
int i = (int) 3.9; 中 i 的值是多少?(float) 1 / 2 的值是多少?
i = d; 中 d 是 double 型 3.99,赋值后 i 是多少?有精度损失提示吗?
j = i * i++; 的结果不确定?这类表达式应该怎么避免?