预处理器(preprocessor)在真正编译之前处理以 # 开头的指令。它可以定义符号、按条件保留代码,以及把头文件内容引入当前源文件。预处理只是文本层面的准备阶段,不理解变量类型。
宏
宏把一个名字替换为一段文本。表示常量的宏通常使用全大写名称。
#include <stdio.h>
#define PI 3.1415926
double circle_area(double radius) {
return PI * radius * radius;
}
int main(void) {
double radius = 3.0;
printf("area = %.2f\n", circle_area(radius));
return 0;
}函数式宏
函数式宏看起来像函数调用,但它不会创建栈帧,也不会进行类型检查。参数可能是一整段表达式,所以参数与最终结果都应该加括号。还要避免把 i++ 这类有副作用的表达式传给会重复使用参数的宏。
#include <stdio.h>
#define SQUARE(x) ((x) * (x))
int main(void) {
int n = SQUARE(3 + 1);
printf("n = %d\n", n);
return 0;
}知识测验
题目
为什么 SQUARE(x) 的参数和整个结果都需要括号?
互动实验
宏展开传送带
访问权限免费
一步步替换 SQUARE 的两个参数,再观察括号如何决定计算顺序。预处理器只替换文本,不计算数值。
宏定义 · 替换规则#define SQUARE(x) ((x) * (x))
01 / 预处理:替换文本
识别宏与实参SQUARE(3 + 1)
SQUARE 是宏名。括号内的整段文本 3 + 1 是实参,此时还没有做加减法。
1 / 8
条件编译
#if 根据条件决定保留哪一段源代码,#else 提供条件不成立时的另一段代码,#endif 结束这一组条件编译。它们在编译前生效,不是程序运行时的 if/else。
下面的圆形面积计算器有两种构建模式:调试版显示半径和中间结果,正式版只显示用户需要的面积。修改 DEBUG 后重新编译,就能切换输出。
#include <stdio.h>
#define PI 3.1415926
#define DEBUG 1
int main(void) {
double radius = 3.0;
double area = PI * radius * radius;
#if DEBUG
printf("[DEBUG] radius = %.2f, area = %.2f\n", radius, area);
#else
printf("Area: %.2f\n", area);
#endif
return 0;
}DEBUG 为 1 时,预处理器保留 #if 分支,输出 [DEBUG] radius = 3.00, area = 28.27;改为 0 并重新编译后,只保留 #else 分支,输出 Area: 28.27。未选中的分支不会进入编译结果。
#ifdef 和 #ifndef 则检查宏是否已定义;下一节会用 #ifndef 保护头文件。
知识测验
题目
当 DEBUG 为 0 时,#if DEBUG 中的代码会怎样?
动手练习
可切换调试的圆形计算器
编写一个可切换调试信息的圆形计算器。
- 定义 PI、SQUARE(x) 和 DEBUG。
- 读取半径,计算面积以及直径的平方。
- 仅当 DEBUG 为 1 时输出原始半径。
- 把 DEBUG 改成 0,确认调试输出消失。