第28篇 预处理详解
目录
一、预定义符号与#define常量定义
1. 预定义符号
2. #define定义常量
⚙️ 二、宏定义机制与副作用
1. 宏定义语法
2. 宏参数副作用
3. 宏替换规则
4. 宏与函数对比
5. #和##运算符
🏷️ 三、命名约定与#undef指令
1. 命名约定
2. #undef指令
🛠️ 四、命令行定义与条件编译
1. 命令行定义
2. 条件编译
📂 五、头文件包含与防重复包含
1. 包含方式
2. 嵌套包含问题
📜 六、其他预处理指令
一、预定义符号与#define常量定义
1. 预定义符号
C语言内置了若干预定义符号,在预处理阶段直接处理,无需定义即可使用:
__FILE__:当前编译的源文件名__LINE__:当前行号__DATE__:文件编译日期__TIME__:文件编译时间__STDC__:若编译器遵循ANSI C标准,值为1,否则未定义
2. #define定义常量
- 基本语法:
#define name stuff - 示例:
#define MAX 1000:定义数值常量#define reg register:为关键字创建简短别名#define do_forever for(;;):用形象符号替换实现#define DEBUG_PRINT printf("file:%s\tline:%d\tdate:%s\ttime:%s\n", __FILE__, __LINE__, __DATE__, __TIME__):多行定义需使用反斜杠\续行
- 注意事项:定义标识符时不建议在末尾加分号,否则可能导致语法错误。例如:
#define MAX 1000; // 错误示例 if (condition) max = MAX; // 替换后变成max = 1000;;,导致if和else之间出现两条语句 else max = 0;
二、宏定义机制与副作用
1. 宏定义语法
- 声明方式:
#define name(parament-list) stuff - 注意:参数列表的左括号必须与宏名紧邻,否则会被解释为stuff的一部分。
- 示例:
#define SQUARE(x) x * x - 陷阱与修复:
- 问题1:
SQUARE(a + 1)替换后变成a + 1 * a + 1,运算顺序错误。- 修复:
#define SQUARE(x) (x) * (x)
- 修复:
- 问题2:
#define DOUBLE(x) (x) + (x),在10 * DOUBLE(a)中替换为10 * (a) + (a),乘法优先级导致错误。- 修复:
#define DOUBLE(x) ((x) + (x))
- 修复:
- 问题1:
- 原则:数值表达式宏定义应在参数和整体表达式两边都加括号,避免运算符优先级问题。
2. 宏参数副作用
- 副作用定义:表达式求值时产生的永久性效果(如
x++)。 - 危险示例:
#define MAX(a, b) ((a) > (b) ? (a) : (b)) z = MAX(x++, y++); // 替换后:((x++) > (y++) ? (x++) : (y++)) // 结果:x和y可能被多次自增,导致不可预测结果
3. 宏替换规则
- 调用宏时,先检查参数是否包含
#define定义的符号,若有则先替换。 - 替换文本插入原位置,宏参数被值替换。
- 再次扫描结果文件,重复上述过程。
- 注意:宏参数和定义中可包含其他宏,但宏不能递归;字符串常量内容不被搜索。
4. 宏与函数对比
| 属性 | #define宏 | 函数 |
|---|---|---|
| 代码长度 | 每次使用都插入代码,程序长度增长 | 代码只出现一次,调用同一份代码 |
| 执行速度 | 更快(无调用开销) | 有调用和返回开销,稍慢 |
| 操作符优先级 | 需加括号避免优先级问题 | 参数求值一次,结果可预测 |
| 副作用参数 | 多次计算可能导致不可预料结果 | 参数求值一次,结果可控 |
| 参数类型 | 类型无关,适用于任何合法类型 | 参数类型相关,需声明特定类型 |
| 调试 | 不方便调试 | 可逐语句调试 |
| 递归 | 不能递归 | 可递归 |
- 宏的特殊能力:参数可出现类型(如
#define MALLOC(num, type) (type*)malloc(num * sizeof(type))),函数无法做到。
5. #和##运算符
- #运算符:将宏参数转换为字符串字面量(字符串化)。
#define PRINT(n) printf("the value of "#n" is %d", n) PRINT(a); // 替换为printf("the value of " "a" " is %d", a) - ##运算符:连接两边符号,创建新标识符(记号粘合)。
#define GENERIC_MAX(type) type type##_max(type x, type y) { return (x>y?x:y); } GENERIC_MAX(int) // 生成int int_max(int x, int y) { ... }
三、命名约定与#undef指令
1. 命名约定
- 宏名:全部大写(如
MAX_SIZE) - 函数名:不全大写(如
getMax)
2. #undef指令
- 作用:移除已定义的宏。
- 语法:
#undef NAME - 用途:重新定义宏前需先移除旧定义。
四、命令行定义与条件编译
1. 命令行定义
- 功能:编译时在命令行定义符号,用于生成程序不同版本。
- 示例:
gcc -D ARRAY_SIZE=10 program.c(Linux环境)
2. 条件编译
- 用途:选择性编译代码(如调试代码)。
- 常见指令:
#if 常量表达式 ... #endif#if ... #elif ... #else ... #endif(多分支)#ifdef symbol/#ifndef symbol(判断是否定义)- 嵌套指令
#if defined(OS_UNIX) #ifdef OPTION1 unix_version_option1(); #endif #ifdef OPTION2 unix_version_option2(); #endif #elif defined(OS_MSDOS) #ifdef OPTION2 msdos_version_option2(); #endif #endif
五、头文件包含与防重复包含
1. 包含方式
- 本地文件包含:
#include "filename"- 查找策略:先在源文件所在目录查找,未找到再去标准路径。
- 库文件包含:
#include <filename.h>- 查找策略:直接去标准路径查找。
- 标准路径:
- Linux:
/usr/include - VS:
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
- Linux:
2. 嵌套包含问题
- 问题:头文件被重复包含会导致内容被多次拷贝,增加编译压力。
- 解决方案:
- 条件编译防重:
#ifndef __TEST_H__ #define __TEST_H__ // 头文件内容 #endif - #pragma once:避免重复引入(推荐)。
- 条件编译防重:
六、其他预处理指令
#error:生成错误信息#pragma:设置编译器状态(如#pragma pack()用于结构体对齐)#line:修改行号信息
