跳到主要内容
🔍
1.2.3已发布beginner · 约 4 课时 · P0

运算符与优先级

掌握全部运算符与优先级/结合性、短路求值与位操作宏,避开"优先级≠求值顺序"的陷阱

  • 来源
  • 补充
  • 纠错
  • 待确认

标记说明:【来源】来自上传资料 · 【补充】课程新编 · 【纠错】按勘误表修正 · 【更新】过时内容已现代化 · 【待确认】无法可靠还原

完成标准(本章)

  • 📖 已阅读:滚动 ≥ 80% 且有效阅读 ≥ 180
  • ✏️ 已练习:小练习正确率 ≥ 60%
  • 📝 已通过测验:分数 ≥ 60
  • 🛠️ 已掌握还需完成实践任务
学习状态:未开始

1.2.3 运算符与优先级

本章来源:运算符分类、优先级与结合性、自增自减、三目与逗号来自《第一阶段讲义》15.x 节【来源】,经重新组织表述;自增口诀已【纠错】(err-7.1-21:前置 ++ 先自增再参与运算,资料口诀写反);原资料优先级表符号提取丢失【待确认】——本章优先级表按 C11 标准(N1570 §6.5)重排;位操作宏、"优先级≠求值顺序"与未定义行为辨析、示例与练习为新编【补充】(约 5%)。平台适用性 universal。

① 学习目标

  1. 说出优先级与结合性各自解决什么问题,并会查表而不会背表出错;
  2. 用四个位操作宏(置位/清位/取反/判位)完成寄存器位操作;
  3. 说出 &&/|| 的短路语义,并用它写出 p != NULL && *p > 0 这类安全写法;
  4. 区分优先级(决定表达式结构)与求值顺序(操作数的求值先后,多数未指定);
  5. 说清为什么 i++ + i++ 是未定义行为(UB),并养成"一个表达式里同一变量最多改一次"的习惯;
  6. 正确说出前置/后置 ++ 的语义(前置先自增再参与运算)。

② 前置知识

  • 必选:1.2.2(类型与 signed/unsigned——移位、溢出边界都建立在类型之上)。

③ 核心概念【来源】

概念说明
优先级决定不同运算符"谁先结合":a + b * c* 高于 +(先算 b*c)。C 有 15 级,拿不准一律加括号——括号既是文档也是保险
结合性决定同级运算符"从左还是从右":7 / 2 * 2 左结合=6(不是 7/4);赋值是右结合a = b = c 先 b=c
算术/赋值+ - * / %(整数除法截断、% 只用于整数);+= 等复合赋值
关系/逻辑< <= > >= == !=&&/|| 短路求值:左边已能定结果就不算右边——p != NULL && *p > 0 安全;&/| 作逻辑时不短路(两边都求值)
位运算& | ^ ~ << >>:操作的是每一位。右移对负数是实现定义的(多数平台算术右移);移位位数不得为负/超宽(UB)
自增/自减**【纠错】**前置 ++i 先自增再参与运算;后置 i++ 先参与运算再自增(资料口诀写反,见 err-7.1-21);i++ + i++UB——同一表达式多次修改同一变量且无序列点
sizeof运算符不是函数:sizeof(int)sizeof 表达式 都合法(1.2.2 展开)
三目条件 ? 值1 : 值2:只求值被选中的那一个分支
逗号依次求值,取最右结果(for 循环头常见)
位操作四宏SET_BIT(reg,n)=reg|(1u<<n)CLEAR_BIT=reg & ~(1u<<n)TOGGLE_BIT=reg ^ (1u<<n)IS_SET=(reg>>n)&1u——嵌入式寄存器操作高频写法
优先级 ≠ 求值顺序优先级只决定结构;f() + g() * h() 中 f/g/h 的调用顺序未指定(先 g 还是先 f 实现可自选)。顺序敏感必须拆开写

④ 通俗解释【补充】

  • 优先级是"运算的靠近权":谁优先级高,谁先把旁边的数"抱走"。a + b * c* 先把 b、c 抱走,+ 只能捡剩下的;
  • 结合性是"同级别的排队方向":左结合=从左往右抱;赋值是右结合(从右往左),所以 a = b = c 先给 b 赋值;
  • 短路是"看得见结果的活就不干"A && B 里 A 已经是假,B 算不算都不影响结果,干脆不算——这就是 p != NULL && *p > 0 敢放心解引用 p 的原因(p 为空时右边不执行);
  • 求值顺序是"副作用的先后":结构(优先级)定死了,但"谁先动手"(f() 还是 g() 先被调用)多数情况标准没说——想按顺序,就拆成多行。

⑤ 示例代码

代码示例与验证记录

  • examples/ex1-bit-macros.c四个位操作宏(SET/CLEAR/TOGGLE/IS_SET)✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-15
    编译:gcc ex1-bit-macros.c -o ex1 -std=c11 -Wall -Wextra -Wpedantic
    适用环境:LinuxWindows(MinGW)
    展开预期输出(实测)
    SET_BIT(0,3)    = 0x8
    再 SET_BIT(8,0) = 0x9
    CLEAR_BIT(9,3)  = 0x1
    TOGGLE_BIT(1,7) = 0x81
    IS_SET(0x81,7)  = 1
    IS_SET(0x81,3)  = 0
    

    差异说明:本机实测(0 警告)。0x8→0x9→0x1→0x81 的连续效果与手算一致; 注意宏参数与整体都加了括号、位移用 1u(unsigned)避免 int 符号位 UB

    完整源码见 /code 代码示例页

  • examples/ex2-precedence.c优先级/结合性/短路/三目/逗号演示✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-15
    编译:gcc ex2-precedence.c -o ex2 -std=c11 -Wall -Wextra -Wpedantic
    适用环境:LinuxWindows(MinGW)
    展开预期输出(实测)
    a + b * c = 14(* 高于 +)
    (a + b) * c = 20(括号改变结合)
    a = b = c 之后: a=4 b=4 c=4
    5 < 3 && one() = 0(one 未被调用)
    1 || zero()  = 1(zero 未被调用)
    (a > b) ? 100 : 200 = 200
    (a=1, b=2, a+b) = 3(逗号表达式取最右值)
    sizeof(int) = 4(sizeof 是运算符不是函数)
    

    差异说明:本机实测(0 警告)。关键观察:输出中没有"调用 one"/"调用 zero"两行——证明 && 与 || 的短路 (右边函数未被调用);初版逗号演示 (1,2,3) 触发 -Wunused-value 警告,改为带赋值的写法后 0 警告

    完整源码见 /code 代码示例页

  • examples/ex3-ub-static.txtUB 与求值顺序辨析(conceptual:静态讲解,不运行)概念讲解示例✓ 文档核对(非执行)· C11 标准 N1570 条文核对, 2026-08-15
    编译:无(静态讲解文本;UB 代码不运行)
    适用环境:LinuxWindows(MinGW)
    展开文档核对记录
    (无运行输出——本示例为静态讲解文本,见文件内容)
    

    差异说明:内容按 C11 标准 N1570 §6.5p2(未定义行为)、§6.5.2.2p10(函数调用顺序未指定)、 §6.5.13/6.5.14/6.5.15/6.5.17(&&、||、?:、逗号的顺序保证)核对;未运行任何 UB 代码

    完整源码见 /code 代码示例页

⑥ 编译与运行方法

gcc ex1-bit-macros.c -o ex1 -std=c11 -Wall -Wextra -Wpedantic
ex1.exe        (Linux: ./ex1)
  • ex1 演示四个位操作宏的连续效果(本机实测输出见示例卡:0x8 → 0x9 → 0x1 → 0x81);
  • ex2 演示优先级、结合性、短路、三目与逗号(真实输出见示例卡;注意 one()/zero() 未被调用证明短路);
  • ex3 是 conceptual 示例(静态讲解,不运行)i++ + i++ 等 UB 与求值顺序未指定的辨析文本。

⑦ 常见错误

症状原因解决
【纠错】把"前置 ++"记成先赋值后运算资料口诀写反(且 15.3 节与习题解析自相矛盾)前置 ++i 先自增再参与运算;后置 i++ 先参与再自增;见 err-7.1-21
i++ + i++ 结果"随编译器变"同一表达式多次修改同一变量且无序列点 → UB,标准不保证任何结果拆成多行,一个表达式内同一变量最多修改一次
7 / 2 * 2 算成 1误以为"乘除谁先谁后不固定"同优先级左结合:7/2=3 → 3*2=6;拿不准加括号
a = b = c 报"使用未初始化"或结果不对不懂赋值右结合从右向左:先 b=c 再 a=b
1 & 21 && 2 分不清& 位与(两边都求值、无短路);&& 逻辑与(短路)条件判断一律 &&/||;位运算才用 &/|
负数右移结果"两台机器不一样"负数的右移是实现定义的(多数平台算术右移)需要可预期行为就用 unsigned 操作数
移位 33 位"没效果"移位位数 ≥ 类型位宽是 UB保证 0 ≤ n < 位宽;用 1u 而非 1(int 符号位问题)

⑧ 小练习

小练习

学习自测:提交后才显示答案与解析(前端判分,不作为正式考试)
  • ex-1-2-3-1.表达式 `a + b * c` 中先算什么?(单选)

    ◌ 未作答
  • ex-1-2-3-2.代码 `int r = i++ + i++;` 的结果是?(单选)

    ◌ 未作答
  • ex-1-2-3-3.`1 || f()` 中 f() 会被调用吗?(单选)

    ◌ 未作答
  • ex-1-2-3-4.求值:`7 / 2 * 2` = ?(单选)

    ◌ 未作答
  • ex-1-2-3-5.求值:`7 % 3 + 1` = ?(单选)

    ◌ 未作答
  • ex-1-2-3-6.求值:`3 << 2 | 1` = ?(移位优先级高于位或)(单选)

    ◌ 未作答
  • ex-1-2-3-7.求值:`~0`(int 类型)= ?(单选)

    ◌ 未作答
  • ex-1-2-3-8.求值:`(1 << 4) - 1` = ?(单选)

    ◌ 未作答
  • ex-1-2-3-9.求值:`5 & 3` = ?(单选)

    ◌ 未作答
  • ex-1-2-3-10.求值:`2 + 3 * 4 / 2` = ?(* 与 / 同级左结合)(单选)

    ◌ 未作答

⑨ 章节测验

章节测验

5 题题库 · 随机抽 5 题 · 及格线 60 分 · 前端判分(学习自测)
开始测验 →

⑩ 实战任务

实践任务

完成 10 道求值题 + 自写四个位操作宏程序

独立完成练习区的 10 道求值题(对照解析核对),并**不照抄 ex1** 自写一个 含 SET_BIT/CLEAR_BIT/TOGGLE_BIT/IS_SET 四个宏的程序,验证置位/清位/取反/判位结果。

输入与输出

无外部输入。交付物:① 10 道求值题作答记录(与解析一致);② 自写位操作宏程序 (编译 0 警告、输出与手算一致)。

功能要求

  • 完成练习区 10 道求值题并记录答案
  • 自写四个位操作宏(参数与整体都加括号、用 1u)
  • 程序至少覆盖:置位、清位、取反、判位各一次并打印结果

限制条件

  • 编译命令 -std=c11 -Wall -Wextra -Wpedantic,0 警告
  • 不运行任何未定义行为代码(如 i++ + i++)

验收步骤(自检清单 0/3

验收标准

  • 10 道求值题答案与 answer_key 一致(验收步骤 1)
  • 位操作宏程序编译 0 警告(验收步骤 2)
  • 输出与手算一致(如置位 0→0x8、清位 0x9→0x1、取反 0x1→0x81)(验收步骤 3)

常见失败原因

  • 宏参数没加括号(SET_BIT(x, a+b) 展开出错)
  • 用 1 而不是 1u(int 符号位左移 UB)
  • 照抄 ex1 而不是自己写
  • 求值题里把 i++ + i++ 当可计算的题去猜

可选扩展

  • 把四个宏包装成函数对比宏与函数的差异(类型检查 vs 文本替换)
  • 用 IS_SET 写一个遍历某字节所有置位位的小程序

完成必要清单后才能计入"已完成实践"(学习状态自动推导,不提供一键完成)

⑪ 面试问题

面试问题

  • 运算符优先级和求值顺序(evaluation order)是一回事吗?高频C/C++ · medium

    要点:不是。优先级/结合性决定表达式结构(谁先与谁结合);求值顺序决定操作数"何时被求值",而函数调用等求值顺序多数是未指定的。

    优先级回答"结构问题":a + b * c 中 * 先与 b、c 结合;结合性回答"同级谁先结合"(赋值右结合)。 求值顺序回答"副作用先后":f() + g() * h() 中 f/g/h 谁先被调用,标准未指定(C11 只有少数 序列点/顺序保证:&&、||、?:、逗号等)。面试关键句:优先级≠求值顺序;想控制顺序就拆行。 更危险的是 UB:i++ + i++ 同一表达式多次修改同一变量且无序列点,标准不保证任何结果。

    追问:
    • 追问:哪些运算符保证求值顺序?(&&、||、?: 只求值被选分支、逗号从左到右)
    评分要点:
    • 优先级与求值顺序的区分
    • 未指定 vs 未定义行为
    • 保证顺序的少数运算符
  • 短路求值(short-circuit)是什么?写一个实战中用它的例子。高频C/C++ · easy

    要点:左边为假、|| 左边为真时,右边不再求值。实战例子:p != NULL && *p > 0(先判空再解引用)。

    短路是 && 与 || 的语言级保证:结果已定即停。它的工程价值在于"安全前置检查": p != NULL && *p > 0(p 为空时右边不执行,不会解引用空指针); i < n && arr[i] > 0(越界保护)。注意 & 和 | 没有短路(两边都求值), 把 & 当 && 用是常见错误(既算错语义又失去保护)。副作用函数(f() && g())依赖短路时要注意可读性。

    追问:
    • 追问:为什么位与 & 与逻辑与 && 不能混用?(位与 vs 逻辑与;无短路 vs 短路)
    评分要点:
    • 短路语义
    • 安全前置检查实例
    • 位与(&)与逻辑与(&&)的区别
  • 嵌入式代码里常写 SET_BIT(reg, 3) 这类宏,解释它的实现和注意点。高频嵌入式 · medium

    要点:SET_BIT(reg,n) = (reg) | (1u << (n)):1u 左移 n 位后与 reg 相或。注意参数必须加括号、用 1u 不用 1(符号位/位宽问题)。

    位操作宏四件套:SET_BIT=(reg)|(1u<<(n));CLEAR_BIT=(reg)&~(1u<<(n));TOGGLE_BIT=(reg)^(1u<<(n)); IS_SET=((reg)>>(n))&1u。三个注意点:① 宏参数与整体都要加括号(否则 SET_BIT(x, a+b) 展开出错); ② 用 1u 而非 1——int 符号位(第 31 位)左移是 UB,unsigned 才干净; ③ 移位位数 0 ≤ n < 位宽(超出是 UB)。寄存器操作常配合 volatile(1.2.2)使用。

    追问:
    • 追问:CLEAR_BIT 为什么是 & ~(...)?
    评分要点:
    • 四宏实现
    • 括号纪律
    • 1u 与位宽边界

⑫ 延伸阅读

  • 《C 程序设计语言》(K&R,第 2 章:运算符与表达式——只引书名);
  • C11 标准草案 N1570 §6.5(表达式:优先级与语法)、§6.5p2(未定义行为条款);
  • 本地手册:man operator 或 GCC 手册的 Operator 章节;
  • 下一章预告:1.2.4 控制结构——用这些表达式写出分支与循环。

迁移训练(migration training)

把本章技能迁移到 Linux嵌入式

说明
运算符语义三平台完全一致(优先级/结合性/短路是语言规则)
位操作宏嵌入式寄存器操作的标准写法,直接复用
需要注意的int 位宽影响移位与溢出边界(1.2.2 sizeof 原则);负数右移的实现定义行为

不变的:全部运算符语义、优先级表、短路、位操作宏;要改的:无——这正是 C 运算符"平台无关"的部分。

内容来源映射

内容部分资料位置标记说明
算术/赋值/关系/逻辑运算符、优先级与结合性、自增自减、三目与逗号运算符第一阶段讲义15.x 节来源正文在原资料基础上重新组织表述,未大段复制原文
自增/自减口诀第一阶段讲义15.x 节(PAGE 218)纠错见勘误 err-7.1-21:口诀写反(前置先自增);本章按正确语义讲述
优先级表第一阶段讲义15.x 节待确认原资料优先级表符号(|、||、& 等)提取丢失需重排;本章优先级表为按 C11 标准(N1570 §6.5)重排
位操作宏(SET/CLEAR/TOGGLE/IS_SET)、求值顺序与 UB 辨析、示例代码与实验补充位操作宏为嵌入式高频工程写法;"优先级≠求值顺序"与 i++ + i++ 的 UB 辨析为资料未覆盖;示例全部重写并本机实测(约 5% 新编)
小练习(含 10 道求值题)/ 章节测验 / 实践任务 / 面试问题 / 延伸阅读补充求值题按 C11 标准重编(原讲义 10 道优先级习题的答案解析存在自相矛盾,不照搬)

本章勘误与更新记录(【纠错】/【更新】)

  • err-7.1-21 · 技术错误 · 出处 PAGE 218

    原文:资料自增口诀写反("前置先赋值后运算")

    正确:前置 ++ 先自增再参与运算;后置 ++ 先参与运算再自增——口诀顺序与资料相反(资料 15.3 节正文与习题解析亦自相矛盾)

    原因:第一步报告 7.1 第 21 条:口诀写反会让学生对 i++/++i 的语义完全颠倒