运算符与优先级
掌握全部运算符与优先级/结合性、短路求值与位操作宏,避开"优先级≠求值顺序"的陷阱
- 来源
- 补充
- 纠错
- 待确认
标记说明:【来源】来自上传资料 · 【补充】课程新编 · 【纠错】按勘误表修正 · 【更新】过时内容已现代化 · 【待确认】无法可靠还原
完成标准(本章)
- 📖 已阅读:滚动 ≥ 80% 且有效阅读 ≥ 180 秒
- ✏️ 已练习:小练习正确率 ≥ 60%
- 📝 已通过测验:分数 ≥ 60 分
- 🛠️ 已掌握还需完成实践任务
1.2.3 运算符与优先级
本章来源:运算符分类、优先级与结合性、自增自减、三目与逗号来自《第一阶段讲义》15.x 节【来源】,经重新组织表述;自增口诀已【纠错】(err-7.1-21:前置 ++ 先自增再参与运算,资料口诀写反);原资料优先级表符号提取丢失【待确认】——本章优先级表按 C11 标准(N1570 §6.5)重排;位操作宏、"优先级≠求值顺序"与未定义行为辨析、示例与练习为新编【补充】(约 5%)。平台适用性 universal。
① 学习目标
- 说出优先级与结合性各自解决什么问题,并会查表而不会背表出错;
- 用四个位操作宏(置位/清位/取反/判位)完成寄存器位操作;
- 说出
&&/||的短路语义,并用它写出p != NULL && *p > 0这类安全写法; - 区分优先级(决定表达式结构)与求值顺序(操作数的求值先后,多数未指定);
- 说清为什么
i++ + i++是未定义行为(UB),并养成"一个表达式里同一变量最多改一次"的习惯; - 正确说出前置/后置 ++ 的语义(前置先自增再参与运算)。
② 前置知识
- 必选: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 & 2 与 1 && 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` = ?(* 与 / 同级左结合)(单选)
◌ 未作答
⑨ 章节测验
章节测验
⑩ 实战任务
实践任务
完成 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 的语义完全颠倒