跳转至

知识点 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++)。

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

解析

?: 的条件部分先求值:

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

易错:

  1. 把 b++ 的值当成自增后的 2(选 A)
  2. 以为两个分支都会算(选了带 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

解析

函数实参的求值顺序是未指定的(标准只说不存在序列点),编译器可以从右往左求值,也可以从左往右。再加上 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 = (a + 1, b++, ++c);   // x 拿到的是 ++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, b++, ++c;       // 等价于 (x = a+1), (b++), (++c);

整句就变成三个并列的表达式。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']);
}
答案
ab
12
1
8
8
a
解析

逐行拆——

语句 输出 关键点
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 · 自增运算符与求值顺序

以下代码的输出是什么?

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 的判定条件是同一个变量被无序修改。


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 · 复合赋值的右结合

有以下语句:

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 -= 36 → a = 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 · 逻辑短路求值(&& 高于 ||)

若 a、b、c 均为 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. ++a:a 先自增为 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

解析

两条规则:

  1. op= 形式的赋值表达式的值 = 赋值之后该变量的新值
  2. % 的优先级高于 -(括号可以改变顺序)

逐项算:

选项 展开过程 表达式的值
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 的值为( )。

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 = 3、j = 3 + 6 = 9。(把这里的逗号当作两条独立语句处理,结果也一样。)
  2. 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 这种嵌套从最右边开始算。