一句话概括
#define 是 C 语言中的预处理器指令,它主要用来做两件事:

- 定义宏:用一个简单的标识符(名字)来代表一段代码或一个值。
- 条件编译:根据条件决定编译哪些代码块。
详细解释
#define 不是 C 语言的语句,它在代码被编译器编译之前,由一个叫做预处理器 的程序进行处理,预处理器会扫描你的代码,找到所有以 开头的指令,并根据指令修改你的源代码文件,然后再把修改后的文件交给编译器。
这个过程可以想象成“文本替换”,但它的功能远不止于此。
主要用途 1:定义宏
这是 #define 最常见的用法,它又可以细分为两种。
1 定义符号常量
这是最简单、最经典的用法,用来给一个固定的值起一个有意义的名字。

语法:
#define <宏名> <值或表达式>
示例:
#include <stdio.h>
// 定义一个符号常量 PI
#define PI 3.14159
// 定义一个符号常量 BUFFER_SIZE
#define BUFFER_SIZE 1024
int main() {
double radius = 5.0;
double area = PI * radius * radius; // 预处理器会把这里的 PI 替换成 3.14159
printf("半径为 %.2f 的圆的面积是: %.2f\n", radius, area);
char buffer[BUFFER_SIZE]; // 预处理器会把这里的 BUFFER_SIZE 替换成 1024
printf("缓冲区大小为: %d\n", BUFFER_SIZE);
return 0;
}
预处理器处理后的效果(概念上):
预处理器在编译之前,会把你的代码文件 main.c 变成类似下面这样:
// ... (头文件内容) ...
int main() {
double radius = 5.0;
double area = 3.14159 * radius * radius; // PI 被替换了
printf("半径为 %.2f 的圆的面积是: %.2f\n", radius, area);
char buffer[1024]; // BUFFER_SIZE 被替换了
printf("缓冲区大小为: %d\n", 1024); // 注意:这里的 BUFFER_SIZE 也被替换了
return 0;
}
优点:

- 可读性强:
PI比14159更容易理解。 - 易于维护:如果需要修改这个常量(
PI的精度),只需在#define的地方修改一次,所有用到它的地方都会自动更新,非常方便。 - 避免“魔法数字”:直接在代码中写
14159这样的数字被称为“魔法数字”(Magic Number),别人不知道它的含义,维护起来很困难,使用#define可以消除魔法数字。
注意:
#define定义的常量没有类型,它只是纯粹的文本替换,而const定义的常量(如const double PI = 3.14159;)是有类型的,编译器会对其进行类型检查,在现代 C 编程中,对于有类型的常量,更推荐使用const。
2 定义带参数的宏(宏函数)
#define 还可以定义看起来像函数的宏,它接受参数,并在使用时进行替换。
语法:
#define <宏名>(参数列表) <替换的代码体>
示例:
#include <stdio.h>
// 定义一个宏 MAX,用于比较两个数的大小
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
int x = 10;
int y = 20;
int z = MAX(x, y); // 预处理器会替换成: ((x) > (y) ? (x) : (y))
printf("x 和 y 中较大的数是: %d\n", z);
return 0;
}
预处理器处理后的效果(概念上):
int main() {
int x = 10;
int y = 20;
int z = ((x) > (y) ? (x) : (y)); // MAX 被替换了
printf("x 和 y 中较大的数是: %d\n", z);
return 0;
}
重要提醒(宏的陷阱): 宏是简单的文本替换,它不关心语法,也不进行计算。参数必须用括号 括起来,否则很容易出错。
看一个经典的错误例子:
#define SQUARE(x) (x * x) // 错误的写法!
int main() {
int a = 5;
int result = SQUARE(a + 1); // 预处理器替换成: (a + 1 * a + 1)
// 计算结果是 5 + 1 * 5 + 1 = 11,而不是预期的 36 (6*6)
printf("结果是: %d\n", result); // 输出 11
return 0;
}
正确的写法:
#define SQUARE(x) ((x) * (x)) // 正确的写法!
int main() {
int a = 5;
int result = SQUARE(a + 1); // 预处理器替换成: ((a + 1) * (a + 1))
// 计算结果是 ((5 + 1) * (5 + 1)) = 36
printf("结果是: %d\n", result); // 输出 36
return 0;
}
即使这样,宏依然有副作用。MAX(i++, j++) 会被替换成 ((i++) > (j++) ? (i++) : (j++)),导致 i 和 j 被多次自增,结果不可预测,在可能的情况下,优先使用真正的函数。
主要用途 2:条件编译
#define 还可以配合其他预处理器指令(如 #ifdef, #ifndef, #if, #else, #endif)来实现条件编译,这意味着你可以根据某些条件来决定编译哪些代码,忽略哪些代码。
常见用法:
1 防止头文件被重复包含
这是一个非常重要的技巧,可以避免在同一个编译单元(一个 .c 文件)中多次包含同一个头文件导致的错误。
// my_header.h #ifndef MY_HEADER_H // "MY_HEADER_H" 这个宏没有被定义 #define MY_HEADER_H // 那么就定义它 // 这里是头文件的实际内容,比如函数声明、结构体定义等 void my_function(); #endif // 结束条件块
- 第一次包含
my_header.h时,MY_HEADER_H未定义,#ifndef条件为真,于是定义MY_HEADER_H并编译头文件内容。 - 如果后续代码又包含了
my_header.h,#ifndef条件为假(因为MY_HEADER_H已经定义了),预处理器会直接跳到#endif,从而避免了内容的重复编译。
2 调试和发布版本的切换
你可以通过定义一个宏来控制代码的编译,方便在调试和发布版本之间切换。
// 在编译命令中定义宏, gcc -DDEBUG=1 my_program.c
// 如果不定义,DEBUG 宏就不存在
#include <stdio.h>
void process_data() {
// ... 正常的数据处理逻辑 ...
#ifdef DEBUG // 如果定义了 DEBUG 宏
printf("调试信息: 进入 process_data 函数,\n");
// ... 一些额外的调试代码 ...
#endif // 结束条件块
// ... 更多处理逻辑 ...
}
int main() {
process_data();
return 0;
}
- 如果你在编译时定义了
DEBUG,#ifdef DEBUG和#endif之间的调试代码就会被编译进去。 - 如果没有定义
DEBUG,这部分代码就会被完全忽略,最终生成的可执行文件更小、效率更高。
#define 与 const 的对比
| 特性 | #define (宏) |
const (常量) |
|---|---|---|
| 本质 | 预处理器指令,进行文本替换 | 关键字,定义一个只读变量 |
| 类型 | 无类型,只是文本 | 有类型,编译器会进行类型检查 |
| 作用域 | 从定义到文件末尾(全局) | 遵循变量作用域(块级或全局) |
| 调试 | 预处理后就消失了,调试时看不到 | 存在于符号表中,调试时可见 |
| 安全性 | 不安全,容易产生副作用和意想不到的替换 | 安全,有类型检查,副作用更可控 |
| 内存占用 | 通常不占用内存(文本替换) | 会占用内存(作为变量存储) |
| 应用场景 | 头文件保护、条件编译、简单的代码片段 | 定义真正的、有类型的、需要作用域的常量 |
#define 是 C 语言一个非常强大且底层的工具,虽然在现代 C++ 和现代 C 编程中,许多 #define 的功能可以被 const、constexpr、inline 等更安全、更强大的特性替代,但 #define 在头文件保护和条件编译方面仍然是不可替代的。
理解 #define 的工作原理,特别是它的“文本替换”本质,对于编写健壮、可维护的 C 代码至关重要。
