知识点 03 · 运算符、表达式与求值顺序¶
本页最后更新于 ,内容可能过时
1 · 自增自减与短路求值¶
假设 a = -1, b = 1, c = 0,则执行 a++&&++b||c++; 后,a、b、c 三个变量的值分别是( )。
- A. 0,1,1
- B. 0,1,0
- C. 0,2,0
- D. 0,2,1
答案
C
解析
先把优先级理清楚:++ 高于 && 高于 ||,所以表达式是 (a++) && (++b) || (c++),按结合性写成 ((a++) && (++b)) || (c++)。
a++:先取a的原值-1(非 0 即真),再把a变成0。结果 = 真。- 左边为真,
&&必须继续 → 求值++b:b先加 1 变成2,结果 =2(真)。 A && B整体为真;||遇到左边为真 → 短路,右边的c++完全不执行。- 所以最终
a = 0、b = 2、c = 0(保持原值)。✅
易错:忘了
||会短路,把c算成 1(选 A 或 D)。另外-1在条件里是真,不是假。
2 · 自增自减与短路求值¶
设 int a = 0, b = 1, c = 2, x; 执行 x = ++a ? b++ : c++; 后,a、b、c、x 的值分别是( )。
- A. a=1, b=2, c=3, x=2
- B. a=1, b=2, c=2, x=2
- C. a=1, b=2, c=2, x=1
- D. a=1, b=1, c=3, x=3
答案
C
解析
?: 的条件部分先求值:
++a:a先自增为1,值1为真;- 三目运算符只求值一个分支:取
b++,c++不执行; b++的值是b的原值 1,然后b变成2;- 所以
x = 1,b = 2,c = 2(不变)。✅
易错:
- 把
b++的值当成自增后的 2(选 A)- 以为两个分支都会算(选了带
c=3的选项)
3 · 求值顺序与未定义行为¶
若以下程序执行后,输出为( )。
- A. i = 4, j = 8
- B. i = 5, j = 9
- C. i = 5, j = 10
- D. 结果未定义
答案
D
解析
同一个表达式里对 i 既前置自增又后置自增,而它们之间没有任何序列点(sequence point)。
C 标准明确规定这类代码属于 未定义行为(undefined behavior, UB)——不同编译器、不同优化级别下结果可以不一样,甚至可能崩溃。
这是本课程的一条核心结论:不要在一个表达式里对同一变量多次自增/自减。
易错
按"先算 ++i 再算 i++"推演出一个具体数字(比如选 B)。这类写法只能取"未定义/不确定"。
标准把"两个序列点之间对同一对象修改两次以上"或"修改后又读取其值"判为 UB,标准没有给出确定结果。
4 · 求值顺序与未定义行为¶
对于 int i = 3; 执行 printf("%d,%d\n", i, i++); 输出为( )。
- A. 3,4
- B. 3,3
- C. 4,4
- D. 结果不确定
答案
D
解析
函数实参的求值顺序是未指定的(标准只说不存在序列点),编译器可以从右往左求值,也可以从左往右。再加上 i 和 i++ 之间没有序列点,整个表达式是未定义行为。
在实践中:
- 若从右往左求值 →
i++先算,i变成 4;再取i得 4 → 输出4,3; - 若从左往右求值 → 取
i= 3;再算i++得 3 → 输出3,3。
结论:结果不确定(D)。 这也正是很多教材/编译器警告的来源。
易错:选 B(按从左到右的直觉判断)。
printf的参数求值顺序标准没有规定。
5 · 逗号运算符与优先级¶
设有:int a=1, b=2, c=3, x; 则执行 x = (a+1, b++, ++c); 后 x 的值为( )。
- A. 2
- B. 3
- C. 4
- D. 报错
答案
C
解析
括号里是一个逗号表达式。逗号表达式的值取最后一个子表达式的值,这里是 ++c:
c 从 3 自增为 4,所以 ++c 的值是 4,x = 4。
括号里前两项照常执行。a+1 算出来没用上,b 自增为 3。
gcc 实测执行后:x = 4, a = 1, b = 3, c = 4。
与选项 A(2)的差别全在那对括号上。
逗号运算符的优先级低于赋值。若题面把括号丢掉,写成
整句就变成三个并列的表达式。x 只拿到 a+1 = 2,与 b++、++c 无关。
逗号表达式有两条规则:
(a, b, c)的值 = 最后一个子表达式的值;- 逗号运算符优先级最低(低于赋值)。所以
x = a+1, b++中,x拿到的是a+1的值,不是b++的值。
6 · 浮点数比较¶
如果 a=3.0, b=2.1, c=0.9,则下列表达式的值为 0 的是( )。
- A.
a>b - B.
a-b==c - C.
b++>c - D.
c=(a>b)
答案
B
解析
逐项算——
- A:
3.0 > 2.1为真 → 值1。 - B:
a-b=3.0 - 2.1。浮点数无法精确表示 2.1、0.9,实际结果是0.8999999999999999,而c是0.90000000000000002。两者不相等 → 值为0。✅ - C:
b++ > c,先用b的原值2.1 > 0.9为真 → 值1(之后b变成 3.1,但表达式值仍是 1)。 - D:
c = (a>b)→a>b为 1,赋值给c使c = 1.0,整个赋值表达式的值是 1.0 → 不是 0。
浮点数不能直接用
==判断相等,应改为fabs(a-b) < 1e-6(EPS容差比较)。同一写法在另一道程序题里也出现过:
if (avg == 60.0),题面写明"若学生改为if (avg == 60.0),不给分"。
7 · 宏展开与运算符结合性¶
(程序阅读题,每题 3 分)下面程序的输出是?
#include <stdio.h>
#define M 1+2
void main()
{
char str[6]={'a','b','\0','c','d','\0'};
int x = 8, *p = &x;
int b[5], i;
printf("%s\n",str);
for(i=0;i<=4;i++) b[i]=i*3;
printf("%d\n",b[4]);
printf("%d\n",0<=x<=3);
printf("%d\n",2*M*3);
printf("%d\n",*p);
printf("%c\n",str['\0']);
}
解析
逐行拆——
| 语句 | 输出 | 关键点 |
|---|---|---|
printf("%s\n",str) |
ab |
str 是 {'a','b','\0','c','d','\0'},%s 遇到第一个 '\0' 就停,后面的 c、d 不会被打印 |
b[4] |
12 |
b[i]=i*3,i=4 → 12 |
0<=x<=3 |
1 |
关系运算符左结合:(0<=8)=1,再算 1<=3=1。恒为真——由关系运算符左结合造成 |
2*M*3 |
8 |
宏展开:M 是 1+2,展开为 2*1+2*3 = 2+6 = 8(不是 2*3*3=18) |
*p |
8 |
p=&x,x=8 |
str['\0'] |
a |
'\0' 的值是 0,所以这是 str[0] = 'a' |
本题涉及的六个考点
本题同时涉及字符串结束符、数组越界、宏展开、关系运算左结合、指针解引用、字符常量的数值。
- 宏只是文本替换,不会替你加括号。写宏要加足括号:
#define M (1+2)才是2*3*3=18;带参数的宏更要写成#define SQUARE(x) ((x)*(x))。 0<=x<=3这种"数学写法"在 C 里恒为真,需要写成0<=x && x<=3。
8 · 运算符优先级¶
下列运算符中优先级最高的是__。
- A.
< - B.
+ - C.
% - D.
++
答案
D
解析
++ 是单目(一元)运算符,优先级属于最高那一档,远高于所有二元运算符;其余三项的次序是 % > + > <。
选 C 是只记得算术比关系高,忘了单目运算符优先级最高。
一元运算符(++ -- ! & * + -)的优先级高于任何二元运算符。
9 · 逗号与关系运算符左结合¶
设 a 为整型变量,与表达式 10<a<15 相等的表达式是__。
- A.
1 - B.
a==11||a==12||a==13||a==14 - C.
0 - D.
!(a<10) && !(a>=15)
答案
A(1)
解析
关系运算符左结合,所以 10<a<15 实际是 (10<a)<15:10<a 先算出 0 或 1,这个 0/1 再去和 15 比,恒为真,整个表达式的值永远是 1。
B、D 表达的是"10<a<15"的数学含义(会随 a 变化,D 在 10≤a<15 时为 1、否则为 0),
但题目问的是"与该表达式的值相等",而 10<a<15 的值恒为 1,所以取 A;D 与"a 在 10 到 15 之间"等价,但与 10<a<15 的值不总是相同。要写"a 在 10 到 15 之间"需要写成 10<a && a<15。
10 · 自增运算符与求值顺序¶
以下代码的输出是什么?
- A. 8
- B. 9
- C. 10
- D. 未定义行为
答案
B(9)
解析
a++ 取原值 5(a 随后变成 6),++b 先加 1 使 b = 4 再取新值 4,所以 5 + 4 = 9。
关键结论:这不是未定义行为——两个自增作用在不同变量上,不存在"同一对象在两个序列点之间被修改两次"的问题,结果完全确定。
选 D 是把"表达式里出现自增"直接等同于 UB;UB 的判定条件是同一个变量被无序修改。
11 · 逻辑运算与德摩根定律¶
下列哪个逻辑表达式等价于 !(a > b && a < c)?
- A.
(a <= b) || (a >= c) - B.
(a >= b) || (a <= c) - C.
(a > b) || (a < c) - D.
!(a < b && a > c)
答案
A
解析
德摩根定律:!(X && Y) = !X || !Y,其中 !(a > b) 就是 a <= b、!(a < c) 就是 a >= c,合起来正是 A。
B 把两个比较符的方向弄反了——! 取反时比较符也要一起翻(> 变 <=);
C 既没取反、又把 && 换成了 ||;D 只把里层的两个比较反着写,外层的 ! 和 && 原封不动,整体不等价。
12 · 逻辑运算与德摩根定律¶
设有 int x, y;,循环语句 while (!x && !y) 中的循环条件表达式等价于( )。
- A.
!(x == 0 && y == 0) - B.
!(x == 0 || y == 0) - C.
!(x != 0 || y != 0) - D.
x = 0 && y = 0
答案
C
解析
先把 !x 读成"x == 0",等价地写成 !(x != 0);!y 同理是 !(y != 0)。
于是 !x && !y = !(x != 0) && !(y != 0),对整个式子用德摩根定律提取外层取反 → !(x != 0 || y != 0),即 C。
A 的取反方向错了(把 && 的取反写成了 &&);D 用的是赋值号 = 而不是关系符 ==,本身就写错了。
13 · 绝对值的逻辑表达式(德摩根)¶
请指出以下能够表示 |a+b| < 5 的逻辑表达式。
- A.
(a+b) < 5 && (a+b) < -5 - B.
!((a+b) >= 5 && (a+b) <= -5) - C.
!((a+b) >= 5 || (a+b) <= -5) - D.
(a+b) < 5 && !((a+b) < -5)
答案
C
解析
先把数学条件翻成"夹逼"形式:|a+b| < 5 ⟺ -5 < a+b < 5;它的反面是"a+b >= 5 或 a+b <= -5"(碰到任一边界就越界)。
取反回去即 !((a+b) >= 5 || (a+b) <= -5),所以是 C。
- A 错:用
&&把两个互斥条件串起来,恒为假。 - B 错:德摩根展开时忘了把
&&换成||。 - D 错:
!((a+b) < -5)等价于a+b >= -5,只挡住下界、漏了上界。
范围判断的两个边界:
|x| < k先列出>= k或<= -k两个边界,取反时&&与||要互换。
14 · 复合赋值的右结合¶
有以下语句:
则输出结果是( )。
- A. -132
- B. -60
- C. -24
- D. 0
答案
B(-60)
解析
复合赋值运算符 +=、-= 右结合,所以 a += a -= a * a 等价于:
从最右边往里算:
a * a= 6 × 6 = 36(此刻a还没被改);a -= 36→a = 6 - 36 = ``-30,这个赋值表达式的值就是a的新值-30;- 再算
a += -30:注意此时式子左边的a已经是 -30 了(上一步刚改过),所以a = -30 + (-30) = ``-60。✅
易错
第 3 步左边用了 a 最开始的 6,即按从左到右算成 a + a - a*a = 6 + 6 - 36 = -24(选 C)。
复合赋值的左侧在"赋值那一刻"读的是变量当时的当前值,而内层已经把它改成 -30 了。
选 D 的 0 是按"先算成 36 再减 36"得到;A 的 -132 是符号或顺序算错。
一串复合赋值连着写时,从最右往左算,每算一层都在更新同一个变量。
附带说明:严格按 C 标准,这一句里 a 被"无序地读又写",gcc 15.2.0 编译时会给出 -Wsequence-point 警告(operation on 'a' may be undefined)。
按"右结合 + 从右往左算"推出来的 -60 与 gcc 实测输出一致,答案取 B。
15 · 逻辑短路求值(&& 高于 ||)¶
若 a、b、c 均为 int 型变量,则执行以下语句后:
(4) a 值为( )。
- A. 不正确
- B. 0
- C. 2
- D. 1
(5) b 值为( )。
- A. 1
- B. 2
- C. 不正确
- D. 0
(按 (4)(5) 两小问作答,共用一个程序段。)
答案
(4) C(a = 2) (5) A(b = 1)
解析
本题只需判断一件事——优先级:&& 高于 ||。所以
实际是
而不是"从左到右依次算"。接着按短路规则走:
++a:a先自增为 2,表达式的值是 2(非 0 即真);||的左边已经为真 → 短路,右边整个(++b && ++c)一个字符都不执行;- 于是
b保持原值 1,c也保持原值 1。
所以 (4) 选 C、(5) 选 A。✅
易错
按"从左往右"想成 ((++a || ++b) && ++c) 或者以为 || 两边都会算,于是把 b 算成 2((5) 选 B)。只要 || 左边为真,右边就是死代码。
对比来看:若把原式换成 ++a && ++b || ++c(外层变成了 ||),由于 && 左边为真,++b 会被求值,此时 b 等于 2。这道题的关键是看清 && 和 || 谁在外层。
另外复查一个小地方:(4) 的选项 A 和 (5) 的选项 C 都写着"不正确",这其实是在问"该值是否可确定"——本题没有任何未定义行为(三个不同变量各改一次),结果是完全确定的。
16 · 复合赋值与取余¶
假定有以下变量定义,则能使赋值为 3 的表达式是( )。
- A.
x %= (k %= 5) - B.
x %= (k - k % 5) - C.
x %= k - k % 5 - D.
(x %= k) - (k %= 5)
答案
D
解析
两条规则:
op=形式的赋值表达式的值 = 赋值之后该变量的新值%的优先级高于-(括号可以改变顺序)
逐项算:
| 选项 | 展开过程 | 表达式的值 |
|---|---|---|
| A | 括号里 k %= 5 → k = 7 % 5 = 2;再 x %= 2 → x = 12 % 2 = 0 |
0 |
| B | 括号里 k - k % 5 = 7 − 2 = 5;x %= 5 → x = 12 % 5 = 2 |
2 |
| C | 没有括号,但 % 本来就比 - 高 → 和 B 完全等价 |
2 |
| D | 先 x %= k → x = 12 % 7 = 5,该子表达式的值是 5;再 k %= 5 → k = 2,该子表达式的值是 2;相减 |
3 ✅ |
题目问的是"表达式的值"。D 里两个括号的值分别是 x、k 的新值 5 和 2,相减得 3。
B、C 两个选项的值相同,都是 2。
易错
把 x %= (k - k % 5) 看成 x %= (k - (k % 5)) 之后忘了 % 的优先级,误算成 (7 - 7) % 5 = 0;或者忘记 x 会先被改掉,以为 D 里后面的减法仍用 x = 12。
附带说明:严格按 C 标准,D 是个边角情况——x %= k 读了 k,同一表达式的另一半又写了 k,两者之间没有序列点,gcc 15.2.0 编译时会给出 -Wsequence-point 警告(operation on 'k' may be undefined)。
gcc 实测按从左往右求值,结果稳定为 3,与给出的答案一致,答案取 D。
17 · 逗号表达式与复合赋值¶
执行下面语句后,x 的值为( )。
- A. 15
- B. -12
- C. 10
- D. 3
答案
B(-12)
解析
分两段看:
i = 3, j = i + 6;—— 这是一条逗号表达式语句(逗号运算符优先级最低,低于=),它依次执行两个赋值:i = 3、j = 3 + 6 = 9。(把这里的逗号当作两条独立语句处理,结果也一样。)x -= j += i;—— 右边先算:j += i→j = 9 + 3 = 12,这个子表达式的值就是 j 的新值 12;再x -= 12→x = 0 - 12 = ``-12。✅
易错
- 把
j += i读成不起作用的j + i→ 得-3,选项里没有; - 忽略
x初值为 0,按"0 减一个数再取绝对值"算 → 选 12 或 A; - 把两个复合赋值当成同时进行 → 算不清
j到底用 9 还是 12。
a op= b 的值 = 赋值之后 a 的新值;x -= j += i 这种嵌套从最右边开始算。