#define 是 C 语言中一个预处理指令(Preprocessor Directive),它不是 C 语言的语句,而是在代码被正式编译之前,由预处理器处理的指令。

#define 主要有两个核心用途:
- 定义宏
- 定义常量
定义常量
这是 #define 最常见的用途之一,用于定义一个不会改变的常量值。
语法
#define <常量名> <值>
示例
#include <stdio.h>
#define PI 3.14159
#define MAX_STUDENTS 30
#define DEBUG_MODE 1
int main() {
double radius = 5.0;
double area = PI * radius * radius;
printf("半径为 %.2f 的圆的面积是: %.2f\n", radius, area);
if (MAX_STUDENTS > 25) {
printf("班级人数超过25人,\n");
}
#if DEBUG_MODE
printf("调试信息:程序正在运行中...\n");
#endif
return 0;
}
工作原理
在编译之前,预处理器会扫描整个源代码文件,当它看到 #define PI 3.14159 时,它会查找代码中所有出现的 PI,并将其直接替换为 14159。
double area = PI * radius * radius; 这一行在预处理后,实际上会变成 double area = 3.14159 * radius * radius;。

优点:
- 可读性强:使用
MAX_STUDENTS比直接使用数字30更有意义。 - 易于维护:如果需要修改最大学生人数,只需在
#define那里修改一次,所有用到它的地方都会自动更新,无需在整个代码中搜索替换。
定义宏
这是 #define 更强大但也更复杂的用法,宏可以像函数一样工作,但它在预处理阶段进行文本替换,而不是在运行时调用。
语法
#define <宏名>(<参数1>, <参数2>, ...) <替换文本>
示例 1:不带参数的宏(类似于常量)
#define VERSION "1.2.3"
这和定义常量是一样的。
示例 2:带参数的宏(函数宏)
#include <stdio.h>
// 定义一个计算圆面积的宏
#define CIRCLE_AREA(r) (3.14159 * (r) * (r))
// 定义一个求最大值的宏
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
double radius = 5.0;
// 预处理后变成: double area = (3.14159 * (5.0) * (5.0));
double area = CIRCLE_AREA(radius);
printf("圆的面积是: %.2f\n", area);
int x = 10, y = 20;
// 预处理后变成: int max_val = ((10) > (20) ? (10) : (20));
int max_val = MAX(x, y);
printf("最大值是: %d\n", max_val);
return 0;
}
工作原理
对于 MAX(x, y),预处理器会找到 MAX(a, b) 的定义,然后将 a 替换为 x,b 替换为 y,并将整个 ((a) > (b) ? (a) : (b)) 替换到 MAX(x, y) 的位置。

为什么宏定义中的参数和整个表达式都要用括号括起来? 这是一个非常重要的技巧,可以防止因运算符优先级导致的错误。
反例:
假设我们没有用括号定义 MAX:
#define MAX_WRONG(a, b) a > b ? a : b
如果我们这样调用它:
int result = MAX_WRONG(1 + 2, 3 * 4);
预处理器会将其替换为:
int result = 1 + 2 > 3 * 4 ? 1 + 2 : 3 * 4;
由于 > 的优先级高于 和 ,它实际上被解释为:
int result = 1 + (2 > 3) * 4 ? 1 + 2 : 3 * 4;
这显然不是我们想要的结果(2 > 3 为 0,表达式变为 1 + 0 * 4 ...)。
正确的做法(使用括号):
#define MAX(a, b) ((a) > (b) ? (a) : (b))
再次调用 MAX_WRONG(1 + 2, 3 * 4),它会被替换为:
int result = ((1 + 2) > (3 * 4) ? (1 + 2) : (3 * 4));
这样,括号确保了表达式的计算顺序完全符合我们的预期。
#define 与 const 的比较
在现代 C 语言(C99 及以后)和 C++ 中,我们更推荐使用 const 关键字来定义常量,而不是 #define。
| 特性 | #define |
const |
|---|---|---|
| 类型 | 无类型,预处理器只做简单的文本替换,不关心类型。 | 有类型,编译器会进行类型检查。 |
| 作用域 | 无作用域概念,从定义处到文件末尾都有效,除非被 #undef 取消。 |
有作用域(遵循变量作用域规则,如函数内、函数外等)。 |
| 调试 | 困难,因为宏在预处理阶段就被替换掉了,调试时看不到宏名,只能看到替换后的代码。 | 容易。const 变量在符号表中存在,调试器可以显示其名称和值。 |
| 安全性 | 不安全,简单的文本替换可能导致意想不到的副作用。SQUARE(a++) 会被替换成 (a++) * (a++),导致 a 被增加两次。 |
安全。const 变量就像一个只读变量,不会有文本替换的副作用。 |
| 内存占用 | 不占用内存,它只是编译时的文本。 | 占用内存。const 变量会被分配内存(虽然编译器可能会优化)。 |
示例:使用 const 定义常量
#include <stdio.h>
int main() {
const double PI = 3.14159;
const int MAX_STUDENTS = 30;
double radius = 5.0;
double area = PI * radius * radius; // 完全没问题
// PI = 4.0; // 错误!不能修改 const 变量
return 0;
}
#define 的现代用法
尽管 const 更安全,但 #define 在某些特定场景下仍然非常有用:
-
条件编译:这是
#define无法被替代的核心领域。#define DEBUG 1 int main() { #if DEBUG printf("调试模式:正在检查变量...\n"); #else printf("发布模式:运行已优化,\n"); #endif return 0; }通过在编译时定义
DEBUG(gcc -D DEBUG main.c),可以轻松地在调试和发布版本之间切换代码。 -
包含头文件:
#include本质上也是一个#define的应用,它用指定文件的内容替换掉#include指令。 -
防止头文件重复包含:
#ifndef MY_HEADER_H #define MY_HEADER_H // 头文件内容... #endif
这是 C 语言项目中非常标准的写法,确保一个头文件在一个编译单元中只被包含一次。
| 特性 | |
|---|---|
| 是什么 | C 语言的预处理指令,在编译前进行文本替换。 |
| 主要用途 | 定义常量(#define PI 3.14)定义宏( #define SQUARE(x) ((x)*(x))) |
| 优点 | 灵活,可用于条件编译,文本替换可能带来微小的性能优势。 |
| 缺点 | 无类型、无作用域、难以调试、不安全(有副作用)。 |
| 现代建议 | 对于定义常量,优先使用 const,对于定义宏,优先使用 inline 函数(它既有宏的效率,又有函数的安全性和可调试性)。 |
| 必用场景 | 条件编译(#if, #ifdef)和防止头文件重复包含(#ifndef)。 |
#define 是 C 语言的一个强大工具,但需要谨慎使用,对于简单的常量,const 是更好的选择,对于需要代码替换的场景,要特别注意括号的使用,以避免优先级问题。
