第十一章:预处理指令
11.1 宏定义与带参宏
宏定义
预处理指令 #define
用于定义宏,本质是文本替换,无类型检查。
类型 | 语法 | 示例 | 说明 |
无参宏 | #define 宏名 替换文本 | #define PI 3.14159 | 替换所有 PI 为 3.14159 |
带参宏 | #define 宏名(参数) 替换文本 | #define SQUARE(x) ((x)*(x)) | 参数需用括号包裹,避免优先级错误 |
示例:带参宏的陷阱
#define SQUARE(x) x * x
int result = SQUARE(3 + 2); // 展开为 3 + 2 * 3 + 2 = 11(错误)
// 正确写法:
#define SQUARE(x) ((x) * (x)) // 正确结果:25
注意事项:
• 避免宏参数多次求值(如 SQUARE(i++)
导致 i
自增两次)。
• 使用 #undef
取消宏定义:#undef SQUARE
。
11.2 条件编译(#ifdef/#ifndef/#endif)
条件编译根据预定义条件决定是否编译代码块。
指令 | 功能 | 示例 |
#ifdef | 如果宏已定义,则编译后续代码 | #ifdef DEBUG ... #endif |
#ifndef | 如果宏未定义,则编译后续代码 | #ifndef HEADER_H ... #endif |
#if | 根据表达式结果编译代码(支持逻辑运算) | #if (VERSION > 10) ... #endif |
#elif / #else | 多分支条件编译 | #if A ... #elif B ... #else ... #endif |
应用场景:
防止头文件重复包含:
// header.h
#ifndef HEADER_H
#define HEADER_H
// 头文件内容
#endif
调试模式开关:
#define DEBUG 1
#ifdef DEBUG
printf("Debug信息: x=%d\n", x);
#endif
跨平台兼容:
#if defined(_WIN32)
// Windows平台代码
#elif defined(__linux__)
// Linux平台代码
#endif
11.3 文件包含优化策略
头文件设计原则:
- 自包含性:头文件应包含自身依赖的其他头文件。
- 最小化依赖:仅包含必要的内容,减少编译时间。
- 防卫式声明:使用
#ifndef
或 #pragma once
防止重复包含。
示例:标准头文件结构
// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
#include <stdint.h> // 依赖的头文件
// 函数声明
int add(int a, int b);
double sqrt(double x);
#endif
优化技巧:
• 前向声明:在头文件中用 struct MyStruct;
代替包含完整定义,减少依赖。
• 分离声明与实现:头文件放声明(.h
),源文件放定义(.c
)。
11.4 预定义宏(__DATE__
, __LINE__
等)
编译器预定义的宏,提供编译环境和代码位置信息。
宏 | 描述 | 示例输出 |
__DATE__ | 编译日期(字符串) | "Jun 15 2023" |
__TIME__ | 编译时间(字符串) | "14:30:45" |
__FILE__ | 当前文件名(字符串) | "main.c" |
__LINE__ | 当前行号(整型) | 42 |
__func__ | 当前函数名(C99标准) | "main" |
__STDC__ | 是否遵循ANSI C标准(通常为1) | 1 |
应用示例:调试日志
#define LOG(msg) printf("[%s:%d] %s: %s\n", __FILE__, __LINE__, __func__, msg)
void func() {
LOG("函数启动"); // 输出:[main.c:20] func: 函数启动
}
编译器特有宏:
• __GNUC__
:GCC编译器版本(如 #if __GNUC__ >= 7
)。
• _MSC_VER
:MSVC编译器版本(如 #if _MSC_VER >= 1900
)。
总结
- 宏的利弊:
• 优点:代码复用、条件编译、简化复杂表达式。
• 缺点:调试困难(无类型检查)、易引发副作用。
- 条件编译核心作用:
• 提升代码可移植性,灵活适配不同环境。
- 预定义宏价值:
• 增强调试信息,实现自动化日志和版本控制。
最佳实践:
• 优先使用 const
常量代替无参宏(如 const double PI = 3.14159;
)。
• 复杂逻辑用函数或内联函数替代带参宏,避免副作用。
• 头文件使用 #pragma once
(非标准但广泛支持)或 #ifndef
防卫。
预处理指令是C语言灵活性的重要体现,合理使用可显著提升代码质量和维护效率!