预处理是 C 语言编译前的文本替换与条件裁剪阶段,不做语法检查,却是大型项目、跨平台、底层开发必不可少的核心技能。本文把所有高频考点、工程用法一次性整理清楚,新手也能快速掌握。
📑 文章目录
- 预定义符号(内置宏)
- #define 定义常量
- #define 定义宏(带参宏)
- 带有副作用的宏参数
- 宏替换的规则
- 宏与函数的详细对比
字符串化 与 ## 记号粘合
- 命名约定(宏 / 函数区分)
- #undef 取消宏定义
- 命令行定义宏
- 条件编译(调试 / 跨平台必备)
- 头文件的包含与重复包含防护
- 其他预处理指令
- 面试高频考点总结
1. 预定义符号(内置宏)
C 语言自带一批预定义符号,预处理阶段直接展开,可直接使用
__FILE__ // 正在编译的源文件名
__LINE__ // 当前行号
__DATE__ // 编译日期(月 日 年)
__TIME__ // 编译时间(时:分:秒)
__STDC__ // 遵循ANSI C则为1,否则未定义
使用示例:
printf("文件:%s\n行号:%d\n", __FILE__, __LINE__);
2. #define 定义常量
语法:
#define 名字 替换文本
示例:
#define MAX 1000
#define reg register
#define do_forever for(;;)
#define CASE break;case
多行宏用续行符 \:
#define DEBUG_PRINT \
printf("file:%s line:%d\n", __FILE__, __LINE__);
⚠️ **重点:宏定义尽量不要加分号 ;**否则替换后会多出语句,导致语法错误。
3. #define 定义宏(带参宏)
宏 = 带参数的文本替换。语法:
#define 名字(参数列表) 替换内容
注意:左括号必须紧跟名字,不能有空格。
宏的最大坑:优先级问题
错误写法:
#define SQUARE(x) x*x
SQUARE(a+1); → 替换成 a+1*a+1 结果错误
✅ 正确写法:每个参数加括号,整体加括号
#define SQUARE(x) ((x)*(x))
#define DOUBLE(x) ((x)+(x))
4. 带有副作用的宏参数
当参数在宏里被多次使用时,自增 / 自减会引发灾难。
示例:
#define MAX(a,b) ((a)>(b)?(a):(b))
x=5,y=8;
z = MAX(x++, y++);
替换后:
z = ((x++)>(y++)?(x++):(y++));
结果:x=6 y=10 z=9
结论:不要给宏传 x++、y-- 这类带副作用的参数!
5. 宏替换的规则
- 先检查参数是否包含其他宏,有则先替换
- 替换文本插入原位置
- 再次扫描结果,继续替换(直到无可替换)
- 宏不能递归
- 字符串常量中的内容不替换
6. 宏与函数对比(面试必考)
表格
| 对比点 | 宏 | 函数 |
|---|---|---|
| 代码长度 | 每次都展开,会膨胀 | 只存一份,调用执行 |
| 执行速度 | 快(无调用开销) | 稍慢(调用 + 返回) |
| 优先级 | 易出错,必须加括号 | 安全可控 |
| 副作用参数 | 危险 | 安全 |
| 参数类型 | 无类型限制(通用) | 类型严格限定 |
| 调试 | 不可调试 | 可逐行调试 |
| 递归 | 不能 | 可以 |
宏能做到函数做不到的事:把类型当参数传
c
运行
#define MALLOC(num, type) (type*)malloc((num)*sizeof(type))
7. # 字符串化 与 ## 记号粘合
7.1 #:把参数变成字符串
#define PRINT(n) printf("值为:" #n " = %d\n", n)
PRINT(a);
// 替换后:printf("值为:" "a" " = %d\n", a);
7.2 ##:把两个符号粘合成一个标识符
#define GEN_MAX(type) \
type type##_max(type x,type y){ return x>y?x:y; }
GEN_MAX(int); // 生成 int_max
GEN_MAX(float); // 生成 float_max
8. 命名约定
- 宏名全部大写
- 函数名不要全大写便于一眼区分,减少错误。
9. #undef 取消宏定义
#undef 宏名
用于删除已有宏,方便重新定义。
10. 命令行定义宏
编译时指定宏,适合多版本编译:
bash
运行
gcc -D ARRAY_SIZE=10 test.c
代码中直接用:
int arr[ARRAY_SIZE];
11. 条件编译(调试 / 跨平台神器)
按条件保留 / 删除代码,常用指令:
#if // 条件为真编译
#ifdef // 定义过则编译
#ifndef // 没定义则编译
#elif
#else
#endif
示例:调试日志开关
#define DEBUG 1
#if DEBUG
printf("调试信息\n");
#endif
12. 头文件包含与重复包含防护
12.1 两种包含方式
#include "file.h"先找当前目录,再找库路径#include <file.h>直接找库路径
12.2 重复包含问题
多次 #include 会导致内容重复、编译变慢、冲突报错。
✅ 解决方案两种
-
条件编译(最通用)
#ifndef TEST_H
#define TEST_H
// 内容
#endif -
#pragma once(编译器支持)#pragma once
13. 其他预处理指令
#error编译报错#pragma编译选项设置#line重置行号
14. 面试高频考点
- 预处理主要做什么?
- 宏和函数的区别?
- 写宏为什么必须加括号?
#和##的作用?- 条件编译用途?
- 头文件重复包含如何解决?
#include <>和""区别?- 宏参数带副作用会怎样?
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,后续会持续更新 C 语言干货~