跳转至

知识点 03 · 运算符、表达式与求值顺序

本页最后更新于 ,内容可能过时

1 · 自增自减与短路求值

假设 a = -1, b = 1, c = 0,则执行 a++&&++b||c++; 后,abc 三个变量的值分别是( )。

  • A. 0,1,1
  • B. 0,1,0
  • C. 0,2,0
  • D. 0,2,1
答案

C

解析

先把优先级理清楚:++ 高于 && 高于 ||,所以表达式是 (a++) && (++b) || (c++),按结合性写成 ((a++) && (++b)) || (c++)

  1. a++:先取 a 的原值 -1(非 0 即真),再把 a 变成 0。结果 = 真。
  2. 左边为真,&& 必须继续 → 求值 ++bb 先加 1 变成 2,结果 = 2(真)。
  3. A && B 整体为真;|| 遇到左边为真 → 短路,右边的 c++ 完全不执行。
  4. 所以最终 a = 0b = 2c = 0(保持原值)。✅

易错:忘了 || 会短路,把 c 算成 1(选 A 或 D)。另外 -1 在条件里是真,不是假。


2 · 自增自减与短路求值

int a = 0, b = 1, c = 2, x; 执行 x = ++a ? b++ : c++; 后,abcx 的值分别是( )。

  • 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

解析

?: 的条件部分先求值:

  1. ++aa 先自增为 1,值 1 为真;
  2. 三目运算符只求值一个分支:取 b++c++ 不执行;
  3. b++ 的值是 b 的原值 1,然后 b 变成 2
  4. 所以 x = 1b = 2c = 2(不变)。✅

易错:① 把 b++ 的值当成自增后的 2(选 A);② 以为两个分支都会算(选了带 c=3 的选项)。


3 · 求值顺序与未定义行为

若以下程序执行后,输出为( )。

int i = 3, j;
j = (++i) + (i++);
  • 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

解析

函数实参的求值顺序是未指定的(标准只说不存在序列点),编译器可以从右往左求值,也可以从左往右。再加上 ii++ 之间没有序列点,整个表达式是未定义行为。

在实践中:

  • 若从右往左求值 → 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 = a+1), (b++), (++c);

即:x = 1+1 = 2,然后 b 变为 3、c 变为 4。x 就是 2。

要让答案等于 4,原题必须是:

x = (a + 1, b++, ++c);   // 逗号表达式整体取最后一个子表达式的值 ++c = 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,而 c0.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-6EPS 容差比较)。

同一考点还出现在一道程序题里: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']);
}
答案
ab
12
1
8
8
a
解析

逐行拆——

语句 输出 关键点
printf("%s\n",str) ab str{'a','b','\0','c','d','\0'}%s 遇到第一个 '\0' 就停,后面的 cd 不会被打印
b[4] 12 b[i]=i*3i=412
0<=x<=3 1 关系运算符左结合:(0<=8)=1,再算 1<=3=1。恒为真——关系运算符左结合造成的经典陷阱
2*M*3 8 宏陷阱:M1+2,展开为 2*1+2*3 = 2+6 = 8(不是 2*3*3=18
*p 8 p=&xx=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)<1510<a 先算出 01,这个 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 · 自增运算符与求值顺序

以下代码的输出是什么?

int a = 5, b = 3;
printf("%d\n", (a++) + (++b));
  • 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 >= 5a+b <= -5"(碰到任一边界就越界)。

取反回去即 !((a+b) >= 5 || (a+b) <= -5),所以是 C。

  • A 错:用 && 把两个互斥条件串起来,恒为假。
  • B 错:德摩根展开时忘了把 && 换成 ||——这正是本题设的坑。
  • D 错:!((a+b) < -5) 等价于 a+b >= -5,只挡住下界、漏了上界。

范围判断要"两头都管":|x| < k 先把两个边界列出来(>= k<= -k),取反时 &&|| 必须换。


14 · 复合赋值的右结合

有以下语句:

int a = 6;
a += a -= a * a;
printf("%d", a);

则输出结果是( )。

  • A. -132
  • B. -60
  • C. -24
  • D. 0
答案

B(-60

解析

复合赋值运算符 +=-= 右结合,所以 a += a -= a * a 等价于:

a += (a -= (a * a));

从最右边往里算:

  1. a * a = 6 × 6 = 36(此刻 a 还没被改);
  2. a -= 36a = 6 - 36 = ``-30,这个赋值表达式的值就是 a 的新值 -30
  3. 再算 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 · 逻辑短路求值(&& 高于 ||

abc 均为 int 型变量,则执行以下语句后:

a = b = c = 1;
++a || ++b && ++c;

(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 || ++b && ++c;

实际是

++a || (++b && ++c);

而不是"从左到右依次算"。接着按短路规则走:

  1. ++aa 先自增为 2,表达式的值是 2(非 0 即真);
  2. || 的左边已经为真 → 短路,右边整个 (++b && ++c) 一个字符都不执行;
  3. 于是 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 的表达式是( )。

int k = 7, x = 12;
  • A. x %= (k %= 5)
  • B. x %= (k - k % 5)
  • C. x %= k - k % 5
  • D. (x %= k) - (k %= 5)
答案

D

解析

先立两条规矩:① op= 形式的赋值表达式的值 = 赋值之后该变量的新值;② % 的优先级高于 -(括号可以改变顺序)。逐项算:

选项 展开过程 表达式的值
A 括号里 k %= 5k = 7 % 5 = 2;再 x %= 2x = 12 % 2 = 0 0
B 括号里 k - k % 5 = 7 − 2 = 5;x %= 5x = 12 % 5 = 2 2
C 没有括号,但 % 本来就比 - 高 → 和 B 完全等价 2
D x %= kx = 12 % 7 = 5,该子表达式的值是 5;再 k %= 5k = 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 的值为( )。

int i, j, x = 0;
i = 3, j = i + 6;
x -= j += i;
  • A. 15
  • B. -12
  • C. 10
  • D. 3
答案

B(-12

解析

分两段看:

  1. i = 3, j = i + 6; —— 这是一条逗号表达式语句(逗号运算符优先级最低,低于 =),它依次执行两个赋值:i = 3j = 3 + 6 = 9。(就算把这里的逗号看成笔误、当作两条独立语句,结果也一样,所以这题不受排版影响。)
  2. x -= j += i; —— 右边先算:j += ij = 9 + 3 = 12,这个子表达式的值就是 j 的新值 12;再 x -= 12x = 0 - 12 = ``-12。✅

易错

  • j += i 读成不起作用的 j + i → 得 -3,选项里没有,容易卡住;
  • 忘了 x 初值是 0,绕成"0 减一个数再取绝对值" → 选 12 或 A;
  • 把两个复合赋值当成同时进行 → 算不清 j 到底用 9 还是 12。

一句话:a op= b 的值 = 赋值之后 a 的新值,所以碰到 x -= j += i 这种嵌套,从最右边往里剥。