目录
-
#define:宏定义
(图片来源网络,侵删)- 定义简单的宏(常量)
- 定义带参数的宏(函数宏)
#undef:取消宏定义
-
条件编译指令
#ifdef/#ifndef:如果已定义 / 如果未定义#if/#elif/#else/#endif:基于表达式的条件判断defined操作符- 一个综合示例
#define:宏定义
#define 是预处理器指令,用于创建宏,宏在编译之前被预处理器替换,它不是 C 语言的真正函数或变量。
a) 定义简单的宏(常量)
这是最常见的用法,用于定义不会改变的常量,增强代码的可读性和可维护性。
语法:

#define MACRO_NAME value
示例:
#include <stdio.h>
#define PI 3.14159
#define MAX_STUDENTS 30
#define PROGRAM_VERSION "1.2.0"
int main() {
double radius = 5.0;
double area = PI * radius * radius;
printf("The area of a circle with radius %.2f is: %.2f\n", radius, area);
printf("This program can handle up to %d students.\n", MAX_STUDENTS);
printf("Current version: %s\n", PROGRAM_VERSION);
return 0;
}
工作原理:
在编译之前,预处理器会扫描所有代码,并将所有出现的 PI 替换为 14159,将 MAX_STUDENTS 替换为 30,编译器实际看到的是:
double area = 3.14159 * radius * radius;
printf("This program can handle up to %d students.\n", 30);
最佳实践:
- 宏名全大写:这是约定俗成的做法,可以很容易地将宏与普通变量区分开。
- 没有分号:定义宏时通常不加分号,因为宏替换是简单的文本替换,如果在
#define PI 3.14159;后面写area = PI * radius;,会被替换成area = 3.14159; * radius;,导致语法错误。
b) 定义带参数的宏(函数宏)
宏可以像函数一样接受参数,用于实现一些简单的、重复的代码块。
语法:
#define MACRO_NAME(param1, param2, ...) expression
示例:
#include <stdio.h>
// 定义一个计算最大值的宏
#define MAX(a, b) ((a) > (b) ? (a) : (b))
// 定义一个计算平方的宏
#define SQUARE(x) ((x) * (x))
int main() {
int x = 10;
int y = 20;
int z = MAX(x, y); // 预处理后变成: int z = ((10) > (20) ? (10) : (20));
printf("The maximum of %d and %d is: %d\n", x, y, z);
printf("The square of %d is: %d\n", x, SQUARE(x)); // 预处理后变成: printf("The square of %d is: %d\n", x, ((10) * (10)));
return 0;
}
⚠️ 重要警告:宏的“副作用”
请注意宏定义中的括号 ,它们至关重要,可以确保运算符优先级正确。
反面例子:
#define SQUARE(x) (x * x) // 错误的写法 int a = 5; int result = SQUARE(a + 1); // 预处理后: (a + 1 * a + 1) -> (5 + 1 * 5 + 1) -> 11,但我们期望的是 (5+1)^2 = 36
正确的写法是 #define SQUARE(x) ((x) * (x)),它会变成 ((a + 1) * (a + 1)),得到正确结果 36。
另一个副作用:参数被多次求值
如果宏的参数是一个有副作用的表达式(如 i++),它会被多次执行。
#define MAX(a, b) ((a) > (b) ? (a) : (b)) int i = 0, j = 1; int max_val = MAX(i++, j++); // i和j都会被递增两次! // 第一次比较: (0 > 1 ? 0 : 1),i=1, j=2 // 结果是1,但max_val=1 // 然后赋值: i++, j++,i=2, j=3 // 最终结果: max_val=1, i=2, j=3 (但通常不是我们想要的)
对于复杂的操作,优先使用内联函数 (inline) 或普通函数。
c) #undef:取消宏定义
如果你想移除一个已经定义的宏,可以使用 #undef。
示例:
#include <stdio.h>
#define PI 3.14
printf("PI is defined as: %f\n", PI); // 输出 3.14
#undef PI // 取消PI的定义
// 下面的代码会报错,因为PI未定义
// printf("PI is now undefined.\n", PI);
条件编译指令
条件编译允许你根据预处理器定义的宏来选择性地编译或忽略代码块,这对于编写跨平台代码、调试和实现不同功能版本非常有用。
a) #ifdef / #ifndef
#ifdef (if defined) 和 #ifndef (if not defined) 是最简单的条件编译指令。
#ifdef MACRO_NAME:MACRO_NAME已经被#define定义,则编译其后的代码块。#ifndef MACRO_NAME:MACRO_NAME没有被#define定义,则编译其后的代码块。
语法:
#ifdef MACRO_NAME
// code to compile if MACRO_NAME is defined
#endif
#ifndef MACRO_NAME
// code to compile if MACRO_NAME is NOT defined
#endif
示例:
#include <stdio.h>
// 假设我们想为调试版本添加一些日志
#define DEBUG_MODE
int main() {
int value = 42;
#ifdef DEBUG_MODE
printf("[DEBUG] Program started.\n");
printf("[DEBUG] The value is: %d\n", value);
#endif
printf("This is the main program logic.\n");
#ifndef RELEASE_MODE // 假设没有定义 RELEASE_MODE
printf("This is a non-release build.\n");
#endif
return 0;
}
编译结果:
当 DEBUG_MODE 被定义时,#ifdef 和 #ifndef 之间的代码会被包含在最终编译的代码中,如果注释掉 #define DEBUG_MODE,这些调试代码就会被完全忽略。
b) #if / #elif / #else / #endif
#if 提供了更强大的条件判断能力,它可以在编译时计算一个整型常量表达式。
语法:
#if constant_expression
// code to compile if expression is true (non-zero)
#elif constant_expression
// code to compile if previous expressions were false and this one is true
#else
// code to compile if all previous expressions were false
#endif
示例:
#include <stdio.h>
#define PLATFORM_WINDOWS 0
#define PLATFORM_LINUX 1
#define PLATFORM_MACOS 2
// 假设我们正在为 Linux 平台编译
#define PLATFORM PLATFORM_LINUX
int main() {
#if PLATFORM == PLATFORM_WINDOWS
printf("Compiling for Windows.\n");
#elif PLATFORM == PLATFORM_LINUX
printf("Compiling for Linux.\n");
#elif PLATFORM == PLATFORM_MACOS
printf("Compiling for macOS.\n");
#else
printf("Unknown platform.\n");
#endif
return 0;
}
c) defined 操作符
#if 指令不能直接使用 #ifdef 的语法,它有一个内置的 defined 操作符,可以检查一个宏是否被定义。
defined 的返回值是 1(如果已定义)或 0(如果未定义)。
语法:
#if defined(MACRO_NAME)
// code to compile if MACRO_NAME is defined
#endif
#if !defined(MACRO_NAME)
// code to compile if MACRO_NAME is NOT defined
#endif
#ifdef vs #if defined:
#ifdef MACRO 完全等价于 #if defined(MACRO)。#if defined 的优势在于它可以在更复杂的表达式中使用。
复杂表达式示例:
#if defined(VERSION_1_0) && !defined(VERSION_2_0)
// 只在定义了 VERSION_1_0 且未定义 VERSION_2_0 时编译
printf("Running legacy code for version 1.0.\n");
#endif
#if (defined(LINUX) || defined(MACOS)) && !defined(WINDOWS)
// 在非 Windows 系统上编译
printf("Using Unix-like specific code.\n");
#endif
综合示例:一个典型的头文件保护
在 C 语言中,一个头文件(.h 文件)可能会被多个源文件(.c 文件)包含,如果不加保护,会导致同一个结构体或函数被重复定义,引发编译错误。
my_header.h
#ifndef MY_HEADER_H // MY_HEADER_H 未被定义...
#define MY_HEADER_H // ...则定义它,并继续编译下面的代码
// --- 头文件内容 ---
// 一个函数的声明
void print_message();
// 一个结构体的定义
typedef struct {
int id;
char name[50];
} User;
// --- 头文件内容结束 ---
#endif // ...否则,跳过所有内容,直到 #endif
main.c
#include <stdio.h>
#include "my_header.h" // 第一次包含,MY_HEADER_H 被定义
#include "my_header.h" // 第二次包含,因为 MY_HEADER_H 已被定义,所以被忽略
int main() {
User user1 = {1, "Alice"};
print_message();
printf("User ID: %d, Name: %s\n", user1.id, user1.name);
return 0;
}
utils.c
#include "my_header.h" // 在另一个文件中包含,同样受到保护
void print_message() {
printf("Hello from the utils module!\n");
}
工作流程:
- 预处理器处理
main.c。 - 遇到
#include "my_header.h",读取my_header.h的内容。 #ifndef MY_HEADER_H为真(因为还没定义),#define MY_HEADER_H被执行,然后头文件中的所有内容(print_message声明和User结构体定义)被插入到main.c中。- 继续扫描
main.c,又遇到#include "my_header.h"。 - 再次读取
my_header.h的内容。 #ifndef MY_HEADER_H为假(因为已经在第一次包含时被定义了),所以预处理器直接跳到#endif,整个头文件的内容被忽略。- 这样,
User结构体和print_message的声明就只被包含了一次,避免了重复定义的错误。
| 指令 | 全称 | 作用 | 示例 |
|---|---|---|---|
#define |
Define | 定义宏(常量或函数式) | #define PI 3.14 |
#undef |
Undefine | 取消宏定义 | #undef PI |
#ifdef |
If Defined | 如果宏已定义,则编译 | #ifdef DEBUG |
#ifndef |
If Not Defined | 如果宏未定义,则编译 | #ifndef HEADER_GUARD |
#if |
If | 如果表达式为真,则编译 | #if (VERSION > 100) |
#elif |
Else If | 如果前面的 #if 为假,且此为真,则编译 |
#elif (VERSION == 100) |
#else |
Else | 如果所有条件都为假,则编译 | #else |
#endif |
End If | 结束一个条件编译块 | #endif |
defined |
Defined | #if 指令中的操作符,检查宏是否定义 |
#if defined(MACRO) |
掌握这些预处理器指令是成为一名熟练的 C 程序员的关键一步,它们是编写健壮、可移植、可维护代码的基石。
