9
预处理与宏:编译前的文字替换
Preprocessor · #define · #include
所有以 # 开头的行,都是"预处理指令"——它们在真正编译之前跑,本质是文本替换。你写的 #define PI 3.14,预处理阶段会把代码里所有 PI 原样换成 3.14。理解这一点,宏的坑就都好懂了。
#define 宏:对象宏与函数宏
两种宏 + 经典陷阱
#define PI 3.14159 // 对象宏:纯替换
#define SQ(x) ((x) * (x)) // 函数宏:注意每个 x 都要加括号!
// 危险示范:没加括号会怎样?
#define BAD(x) x * x
// BAD(1+2) 会被替换成 1+2*1+2 = 5,而不是 9!这就是为什么要套括号。
double r = 2;
printf("%f\n", PI * SQ(r)); // 3.14159 * 4 = 12.56
谈宏 vs 函数
宏是纯文本替换,没有类型检查、不做计算,容易出括号和副作用的 bug(SQ(i++) 里 i 会自增两次)。
函数有类型检查、会真正算一遍、参数只求值一次。
现代 C 里,能用 static inline 函数或 constexpr(C23)代替的,就少用函数宏。
条件编译与头文件保护
头文件会被多个 .c 文件 #include,如果不保护,同一个声明会被复制好几份,链接时报"重复定义"。头文件保护宏就是干这个的。
头文件保护:每个 .h 文件都该长这样
// student.h
#ifndef STUDENT_H // 如果没定义过 STUDENT_H
#define STUDENT_H // 就定义它,并把下面内容包起来
struct Student { char name[20]; int age; };
#endif // 第二次再 include,STUDENT_H 已定义,整段被跳过
常用预定义宏(调试神器)
printf("文件:%s, 行号:%d, 函数:%s\n",
__FILE__, __LINE__, __func__);
// 自动告诉你这句代码在哪个文件、第几行、哪个函数里——打日志特别好用