知识点 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(x = 4)。
按扫描出来的字面写法(没有括号)x = a+1, b++, ++c; 计算,逗号运算符优先级最低,整句等价于 (x = a+1), (b++), (++c);,x 拿到的是 a+1 = 2,gcc 实测 x = 2,对应选项 A。
要得到 4,原题必须带括号,即 x = (a+1, b++, ++c);,这时逗号表达式的值取最后一个子表达式 ++c = 4,gcc 实测 x = 4,对应选项 C。
两相对照:材料给的答案与"没有括号"的写法对不上、与"带括号"的写法正好吻合,说明原题里的那对括号在扫描时丢了。考试按材料给的答案 C 作答。
逗号运算符的优先级最低,赋值 = 高于逗号。所以原句等价于:
即:x = 1+1 = 2,然后 b 变为 3、c 变为 4。x 就是 2。
要让答案等于 4,原题必须是:
结论:材料上那份题面应是扫描时丢了括号(或答案漏改)。真遇到这一题,按材料给的答案选 C,但心里要清楚:没有括号就是 2。
这题的正向价值
它把"逗号表达式"这个冷门考点暴露出来了,顺便复习:
(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 && 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 的判定条件是同一个变量被无序修改(同一个变量被无序修改才是 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,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 这种嵌套,从最右边往里剥。