知识点 08 · 预处理与宏¶
本页最后更新于 ,内容可能过时
说明 · 预处理指令与宏的三条黄金法则¶
预处理(# 开头)发生在编译之前,做的是纯文本层面的事:
| 指令 | 作用 |
|---|---|
#include |
把 stdio.h 等头文件内容原样插进来 |
#define / #undef |
宏定义 / 取消宏定义(宏展开 = 文本替换) |
#if #ifdef #ifndef #else #endif |
条件编译:按条件决定哪段代码参与编译(#ifndef X … #endif 就是"头文件卫士"的写法) |
宏是"文本替换",不是函数调用! 宏的参数在展开时直接原样代入,不会自动加括号。
宏的三条黄金法则
- 每个参数都加括号:
((x) * (x))而不是x * x; - 整个表达式再加一层括号:
(x + 1)而不是x + 1; - 能不用宏就不用,优先
static inline函数或const常量。
原因:宏没有类型检查;参数会被求值多次(SQUARE(i++) 会让 i 自增两次);
而 static inline 函数有类型检查、参数只求值一次,且同样没有函数调用开销。
1 · 宏展开陷阱 · 函数式宏必须加括号¶
以下程序的运行结果是?
- A. 25
- B. 13
- C. 9
- D. 11
答案
D(11)
解析
宏展开是纯文本替换:
按优先级算:2 + (3*2) + 3 = 2 + 6 + 3 = 11。✅(如果它是函数,答案才会是 25。)
正确写法必须给参数和整体都加括号:
为什么"两层括号"缺一不可?(这是本题的评分点)
| 写法 | SQUARE(2+3) |
结果 | 够不够用 |
|---|---|---|---|
#define M(x) x*x |
2+3*2+3 |
11 ❌ | 错 |
#define M(x) (x)*(x) |
(2+3)*(2+3) |
25 ✅ | 本题够用,但换个场景仍翻车:100/M(10) → 100/(10)*(10) = 100,而不是 1 |
#define M(x) ((x)*(x)) |
((2+3)*(2+3)) |
25 ✅ | 永远安全 |
- 参数加括号:防止
2+3被乘法的优先级"劈开"(本题就是死在这里)。 - 整体加括号:防止宏的结果被外层运算符抢走优先级(如上面
100/M(10)的例子)。 2 + 3 * 2 + 3的读法:*比+优先级高,所以先算3*2,再从左到右加:2+6+3 = 11。
易错:选 A)25 —— 那是把它当函数算的结果(材料上只给出 D 与展开式,其余选项未逐项解释)。
宏没有类型、没有优先级保护,参数和整体都要加括号,两层一层都不能少。
2 · 宏展开陷阱(第二道)· 展开后完全按运算符优先级走¶
以下宏的输出结果是?
- A. 14
- B. 11
- C. 18
- D. 7
答案
B(11)
解析
这是同一个考点的第二道,只是陷阱从"参数被劈开"(SQUARE(2+3) → 2+3*2+3)换成了"宏的尾巴被外面的运算符抢走":
宏是纯文本替换,展开后的表达式按 C 语言自己的运算符优先级走:* 比 + 高,所以先算 4 * 2 = 8,再算 3 + 8 = 11。✅
选项对照:
- A)14 —— 把宏当成函数算的结果:
(3+4)*2 = 14。这是最诱人的错误答案。 - B)11 —— 正确答案:
3 + 4*2 = 11。 - C. 18、D)7 —— 其余干扰项(材料上只给出了 B 与展开式,未逐项解释)。
正确写法:整体加括号即可(本题参数本身就是单个数字,参数括号可省,但仍建议都写全):
写错时反而看不出问题
改对之后答案就从 11 变成 14 了——宏写错时,结果可能"看起来很正常",这是它最坑的地方。
宏展开后没人替你"兜底",一切按优先级走,所以参数要括号、整体更要括号。
两题对照记忆:
#define SQUARE(x) x*x—— 参数2+3被劈开(2+3*2+3);#define ADD(x,y) x+y—— 整个宏 被外面的*2咬住(3+4*2)。
一个是"参数不加括号",一个是"整体不加括号",正好是黄金法则的前两条各踩一次。