C语言期末速成3:运算符与表达式
算术、赋值、自增自减、逗号表达式与强制类型转换,附一张完整运算符优先级表。
本文是《C语言期末速成》系列第 3 篇。上一章我们认识了数据,这一章学”怎么摆弄数据”——运算符与表达式。全篇围绕一句话:表达式一定有数值。搞清楚每个运算符”最后得出什么值”,你就掌握了本章的一切。
考点清单
- 表达式一定有数值——这是理解一切运算符的钥匙
- 关系(比较)表达式的结果只能是 1(真)或 0(假);
a < b < c连写不是区间判断,要用&&连接 /两边都是整数时结果是整数;%两边必须都是整数,结果取余数- 赋值表达式值为最左边的变量值;赋值的左边只能是一个变量
- 复合赋值要先”补括号”再算:
a *= 2+3等价于a = a * (2+3) - 自增自减:
++a先加后用,a++先用后加 - 逗号表达式值取最右边那个表达式的值,优先级最低
- 强制类型转换写法是
(int)a,类型一定要带括号 - 优先级口诀:
!> 算术 > 关系 >&&>||> 赋值 > 逗号
一、算术运算符
+ - * 分别对应数学上的加、减、乘,想必各位都比较熟悉了,在此不再展开。而除法以及取余运算在 C 语言中有一些特殊的地方,需要特别注意。
1.1 整数除法:/ 的两种命运
/ 的行为取决于两边的类型:
3 / 2 结果 1 /* 两边都是整数 → 结果取整数部分(商),丢弃余数 */
3 / 2.0 结果 1.5 /* 有一边是小数 → 结果就是小数 */
3.0 / 2 结果 1.5
这里隐藏着 C 的一个机制:除法运算要求两边类型一致,不一致时会做隐式转换(见本章第四节)。
3 / 2.0会先把3转成3.0再除,所以结果是小数。
这个坑是考试重灾区:5/2=2、7/3=2、-7/2=-3(C 里整型除法向 0 截断)。写程序想保留小数,务必让其中一边是小数,例如 n/2.0 或 (double)n/2。
1.2 取余 %:只认整数
% 是取余数,不是除号!而且两边必须是整数,出现小数就非法。
7 % 3 结果 1 /* 7 = 2×3 + 1 */
10 % 2 结果 0 /* 能被整除 */
5 % 5 结果 0
它的用处极广:判断奇偶 n % 2 == 0、判断整除 n % 5 == 0、取个位 n % 10。第 6 章循环里你会频繁遇到它。
余数的符号(加深):% 的结果符号跟随被除数(% 左边的数)。左边是正数,余数就是正数;左边是负数,余数就是负数:
100 % 12 → 4 100 % -12 → 4
-100 % 12 → -4 -100 % -12 → -4
这个规律在改错题里偶尔出现,注意别想当然以为”余数永远非负”。
1.3 % 取个位 / 逆序输出(经典)
n % 10 拿到个位,n / 10 丢掉个位——这两步组合,能逐位拆解一个整数:
int n = 1234;
printf("%d", n % 10); /* 4:个位 */
n = n / 10; /* 123 */
printf("%d", n % 10); /* 3:十位 */
二、关系(比较)运算符
六个关系运算符用来比较两个值,结果只有 1(真)或 0(假):
| 运算符 | 含义 | 例子(a=3, b=4) |
|---|---|---|
< | 小于 | a < b → 1 |
<= | 小于等于 | b <= 4 → 1 |
> | 大于 | a > b → 0 |
>= | 大于等于 | b >= 4 → 1 |
== | 等于 | a == b → 0 |
!= | 不等于 | a != b → 1 |
关系表达式的值一定是 1 或 0——C 语言没有专门的”逻辑类型”,任何判断真假的地方都是非 0 即真,0 即假。
2.1 常见坑:a < b < c 不是数学连写
数学上的 0 < x < 10 在 C 里不是区间判断,而是从左到右一步步算:
int x = 5;
0 < x < 10 → (0 < x) < 10 → 1 < 10 → 1 /* 碰巧为真 */
0 < x 先算出 0 或 1,再拿这个 0/1 去和 10 比。要表达”x 在 0 和 10 之间”,必须用逻辑与连接(第 5 章):0 < x && x < 10。
2.2 字符也能比较:比的是 ASCII 码
字符比较实际是 ASCII 码大小比较(第 2 章):'a' < 'b' 即 97 < 98 → 1。判断”是不是数字字符”就靠它:
ch >= '0' && ch <= '9' /* 判断 ch 是不是数字字符 */
2.3 == 与 =:一个等号之差(必考)
==是比较:判断两边是否相等;=是赋值:把右边塞给左边。
if (x == 5) /* 比较:x 等于 5 吗? */
if (x = 5) /* 赋值:把 5 塞给 x,表达式值恒为 5(非 0)→ 条件恒为真! */
x = 5 作为条件表达式永远为真(5 非 0),这是改错题最爱挖的坑(第 5 章还会再考)。
2.4 浮点数不要用 == 精确比较(加深)
0.1 + 0.2 == 0.3 /* 结果是 0(假)! */
浮点数在二进制里大多存不精确(0.1 无法用二进制有限小数表示),所以比较两个浮点是否”相等”,要用差的绝对值小于一个很小的数:
fabs(a - b) < 1e-6 /* 近似相等就认为相等 */
三、赋值运算符:= 与它的家族
3.1 赋值的方向与规则
- 赋值表达式
a = b = 5的值是最左边变量 a 的值,即 5; - 常量不可以赋值:
5 = a非法,左边必须是变量; - 赋值的左边只能是一个变量:
a + b = c非法。
3.2 定义时的”连续赋值”陷阱(必考)
int x = y = 10; /* 错!定义变量时不可以连续赋值,y 还没定义 */
int x, y;
x = y = 10; /* 对!定义完成后可以连续赋值 */
原因是:定义语句里 y 还未被声明,y = 10 中 y 无类型可依,编译器直接报错。
3.3 赋值时的隐式类型转换
不同类型赋值会自动转换:
int x = 7.7; /* 对:x 是 int,取整 → 7(小数部分被丢弃) */
float y = 7; /* 对:y 是 float,→ 7.0 */
规律:把”高精度”赋给”低精度”会丢失信息(7.7 → 7),把”低精度”赋给”高精度”则自动补零(7 → 7.0)。int x = 7.7; 合法,x 就是 7——这是三种”取整丢小数”的情形之一,第四节汇总。
TIP虽然高精度赋值给低精度是合法的,但在实际程序设计中应该尽量避免,或者显式的给出类型转换,以免产生歧义。(你不觉得
int x = 7.7;这种写法很魔丸吗~~~)
3.4 复合赋值运算符
+=、-=、*=、/=、%= 等是”自身运算后写回”的简写:a += b 等价于 a = a + b。
必考陷阱:右边先整体算完,再和左边运算。换句话说,先在心里给右边打上括号:
int a = 2;
a *= 2 + 3; /* 等价于 a = a * (2 + 3) = 2 × 5 = 10 */
而不是 a = a * 2 + 3。同理:
int b = 6;
b %= 4 + 1; /* b = b % (4+1) = 6 % 5 = 1 */
口诀:复合赋值,右边先算,补上括号再动手。
四、自增自减
4.1 前置 vs 后置:一个字的差别
设 a = 5:
| 写法 | 表达式的值 | 运算结束后 a |
|---|---|---|
++a | 6(先加,再用) | 6 |
a++ | 5(先用,再加) | 6 |
--a | 4 | 4 |
a-- | 5 | 4 |
背诵口诀:++ 在前,先加后用;++ 在后,先用后加。
要理解本质,把它想成两步操作的组合:
++a:第 1 步给a加 1,第 2 步把新值作为表达式结果;a++:第 1 步把当前a的值作为表达式结果,第 2 步再给a加 1。
无论前置还是后置,两步都执行完后,变量 a 的值都一样(都加了 1)——不同的只是”表达式那一刻拿到的值”。
int a = 5;
int m = ++a; /* m = 6,a = 6 */
int n = a++; /* n = 6,a = 7 */
4.2 混在表达式里的连环坑
单独写 a++; 和 ++a; 效果相同,但塞进表达式就差很多:
int a = 3;
printf("%d %d\n", a++, a++); /* 别这样写!结果依赖求值顺序,不同编译器可能不同 */
这类”同一个变量在一个表达式里被多次自增”的写法属于未定义行为,考试要么不考,要么只考最简单的单步推导。遇到时按”先取旧值用,再自增”的顺序手推即可。
五、强制类型转换与隐式转换
5.1 强制类型转换:(类型) 表达式
(int)a /* 对:类型必须带括号 */
int(a) /* 错:这种函数式写法在 C 中非法 */
最经典的区别题:
(int)(a + b) /* 先算 a+b,再把结果转成 int */
(int)a + b /* 只把 a 转成 int,再加 b,b 不受影响 */
double d = 7.8;
int i1 = (int)d; /* 7:d 取整 */
int i2 = (int)(d+0.2); /* 8:(7.8+0.2)=8.0 转 int */
5.2 三种”取整丢小数”的情形(必考汇总)
- 赋值给整型:
int a = 1.6;→a = 1 - 强制转换:
(int)1.6→1 - 整数除法:
1 / 2→0、3 / 2→1
这三者都是”把小数部分直接丢掉”,注意和四舍五入区分——第 4 章会给出实现四舍五入的公式。
5.3 隐式转换规则(加深)
不写 (int) 时,C 也会自动转换类型,规则按”低优先级 → 高优先级”进行,直觉就是**“小的往上凑,凑到能放下为止”**:
char/short → int → unsigned → long → float → double
- 整数运算时,
char、short先提升为int(称为”整型提升”); - 混合运算时,低精度转高精度:
'a' + 1中'a'先变成 97 再加; - 所以
3 + 2.5里3会先变3.0,结果是5.5。
char c = 'a'; /* 97 */
printf("%d\n", c + 1); /* 98:c 提升为 int 再加 */
六、逗号表达式:优先级最低的”顺序执行”
逗号表达式 (表达式1, 表达式2, ...) 的规则:
- 从左到右依次求值各表达式;
- 整个表达式的值 = 最后一个表达式的值。
int x = (2, 3, 4); /* 值 = 4,x = 4 */
但注意加不加括号,命运完全不同:
int z = (2, 3, 4); /* 整个括号内是逗号表达式,z = 4 */
int z = 2, 3, 4; /* 逗号优先级最低:先赋值 z = 2,再逗号隔开 */
第二行里,因为赋值 = 的优先级高于逗号,所以 z = 2 先执行,后面 , 3, 4 只是被逗号连接、不赋值——z 是 2。这就是”逗号运算符级别最低”的威力。
| 表达式 | 结果 |
|---|---|
(2, 3, 4) | 4 |
z = (2, 3, 4) | z 为 4 |
z = 2, 3, 4 | z 为 2 |
七、优先级与结合性总表(加深)
考试不要求背整张表,但要求知道相对次序和几个特殊案例。下表按优先级从高到低排列(越靠上越先算):
| 优先级 | 运算符 | 说明 |
|---|---|---|
| 最高 | () [] -> . | 函数调用、下标、成员 |
! ++ -- - (类型) * & | 单目运算 | |
* / % | 乘除取余 | |
+ - | 加减 | |
<< >> | 移位 | |
< <= > >= | 关系比较 | |
== != | 相等比较 | |
& | 位与 | |
^ | 位异或 | |
| | 位或 | |
&& | 逻辑与 | |
|| | 逻辑或 | |
?: | 条件(三目) | |
= += *= 等 | 赋值(右结合) | |
| 最低 | , | 逗号 |
几条必须记住的结论:
- 赋值优先级倒数第二,逗号最低;
!(逻辑非)优先级很高,比算术运算符还高:!a + 1是(!a) + 1;- 关系运算低于算术:
a + b > c是(a+b) > c; ==低于<:a < b == c是(a < b) == c;- 三个位逻辑的次序:
&>^>|,且都排在关系、相等之后、逻辑&&/||之前。所以a & 1 == 1会先算1 == 1再位与,想判断最低位必须写(a & 1) == 1; - 赋值是右结合:
a = b = c从右往左,即a = (b = c); - 不确定时加括号最保险——括号永远最高优先级,可读性也最好。
易错点 / 陷阱
5/2算成 2.5:两边都是整数,结果取整数部分,是 2。%当除号:%是取余,两边必须整数;7%2=1不是 3。int x = y = 10;当合法:定义时不能连续赋值,先分开定义。a *= 2+3算成a=a*2+3:复合赋值右边先算,等价于a*(2+3)。a++和++a混用:记住”在后先用后加”。int(3.9)当强制转换:正确写法是(int)3.9,括号在类型上。(int)a+b和(int)(a+b)混淆:前者只转a。z = 2, 3, 4认为 z 是 4:逗号优先级比赋值低,先执行z=2。a < b < c当数学区间:先算a<b得 0/1,再和c比;区间判断要写a<b && b<c。==写成=:x = 5是赋值,表达式值恒为 5(非 0),条件永远为真。
自测小练习
- 求值
7 / 2、7 % 2、7 / 2.0。 int a = 2; a *= 3 + 4;之后a是多少?int a = 8;依次执行a++后a为几?++a后呢?int a = 3, b = 4, c = 5;求c = (a, b, c)和c = a, b, c两种写法下c的值。int x = 9.9;x是多少?(int)9.9呢?9/4呢?int a = 3, b = 4, c = 5;求a < b < c和(a < b) == 1的值。
参考答案
7/2 = 3(整数除法)、7%2 = 1(余数)、7/2.0 = 3.5。a *= 3+4等价于a = a*(3+4) = 2×7 = 14。a++后a = 9;再++a后a = 10。(单步操作后变量都是加 1 的结果)c = (a,b,c)→c = 5;c = a, b, c→ 逗号优先级最低,先c = a,所以c = 3。x = 9(赋值取整);(int)9.9 = 9;9/4 = 2(整数除法)。a < b < c:先算a<b得 1,再算1 < c得 1;(a<b) == 1:1 == 1得 1。