C++ 预处理器
一、预处理器 基础概念
1.1 编译四大阶段
- 预处理 :
#指令处理、宏替换、头文件展开、注释删除、条件裁剪 - 编译:语法检查、词法分析、生成汇编
- 汇编:汇编转机器码目标文件
- 链接:合并目标文件、解析外部符号
1.2 预处理器核心规则
- 所有预处理指令以
#开头 #前允许空白符(空格、制表符),不能有有效代码- 预处理语句无需分号结尾,加分号会被一并替换,引发bug
- 本质:纯文本无脑替换,无类型检查、无语法校验、无作用域
- 跨行宏:使用反斜杠
\换行连接
1.3 常用预处理指令总览
| 指令 | 作用 |
|---|---|
#include |
引入头文件 |
#define |
定义宏、常量、宏函数 |
#undef |
取消宏定义 |
#ifdef / #ifndef / #endif |
基础条件编译 |
#if / #else / #elif |
表达式条件编译 |
#line |
修改行号与文件名 |
#error |
主动抛出编译错误 |
#pragma |
编译器自定义指令(跨编译器) |
#using |
引入托管代码(C++/CLI 专用) |
二、#include 头文件包含
2.1 两种包含方式
<头文件>:系统库头文件,优先搜索系统库路径
cpp
#include <iostream>
#include <cstring>
"头文件":自定义头文件,优先搜索当前项目目录
cpp
#include "test.h"
2.2 底层机制
预处理阶段直接将头文件内容完整复制粘贴 到当前源码,
反复包含会造成头文件重复定义,必须配合头文件保护。
三、#define 宏定义(重点)
3.1 无参宏定义
语法
cpp
#define 宏名 替换文本
作用
全局文本替换,常用于定义常量、固定配置、魔法数字消除。
实战示例
cpp
#define MAX_NUM 100
#define AUTHOR "DevEngineer"
#define ENTER '\n'
cout << MAX_NUM << ENTER;
取消宏定义
cpp
#undef MAX_NUM
取消后该宏不再生效,可用于局部隔离宏作用域。
3.2 带参数宏(宏函数)
语法
cpp
#define 宏名(参数1,参数2) 替换表达式
基础示例
cpp
#define SQR(x) x * x
❌ 经典坑点:运算符优先级
cpp
// 调用:SQR(3+2)
// 替换后:3+2 * 3+2 等价于 3+6+2 = 11,逻辑错误
✅ 安全写法:参数整体加括号
cpp
#define SQR(x) ((x) * (x))
#define MAX(a,b) (((a)>(b))?(a):(b))
#define MIN(a,b) (((a)<(b))?(a):(b))
3.3 多行宏定义
使用反斜杠 \ 实现跨行书写,适合复杂日志、代码块宏:
cpp
#define LOG_INFO() \
cout << "[INFO] " << __FILE__ \
<< " line:" << __LINE__ << endl;
3.4 宏 致命缺陷(工程必看)
-
纯文本替换,无类型检查,极易引发隐式错误
-
不具备作用域,全局生效,命名冲突严重
-
宏函数会引发双重计算
cpp#define SQR(x) ((x)*(x)) // 调用 SQR(i++) 会导致 i 自增两次 -
无法调试,无符号信息,断点无法进入
-
破坏代码可读性,语法隐晦
3.5 现代 C++ 替代方案
- 常量宏 → 改用
const / constexpr - 简单宏函数 → 改用
inline 内联函数 - 批量条件控制 → 保留条件编译宏
四、# 与 ## 预处理运算符(高阶)
4.1 # 字符串化运算符
作用:将宏参数直接转换为 C++ 字符串常量。
语法 & 案例
cpp
#define STR(x) #x
cout << STR(Hello C++);
// 替换结果:cout << "Hello C++";
组合实战:变量名+变量值打印
cpp
#define PRINT_VAR(val) cout << #val " = " << val << endl;
int age = 25;
PRINT_VAR(age);
// 输出:age = 25
4.2 ## 令牌连接运算符
作用:拼接两个标识符,合成一个新变量名/函数名。
基础案例
cpp
#define CAT(a,b) a##b
int num10 = 999;
cout << CAT(num,10);
// 替换:num10 输出 999
工程用途
批量生成变量、批量函数名、寄存器地址映射、硬件开发常用。
五、条件编译 完整语法体系
按需裁剪编译代码,不满足条件的代码直接丢弃,不参与编译。
5.1 指令分类
- 宏判断:
#ifdef/#ifndef/#elifdef - 表达式判断:
#if/#elif - 结束标记:
#endif - 取反判断:
#if !defined(宏名)
5.2 语法1:#ifdef / #ifndef
cpp
// 宏已定义则编译
#ifdef DEBUG
cout << "调试模式开启" << endl;
#endif
// 宏未定义则编译
#ifndef NDEBUG
// 调试代码
#endif
5.3 语法2:#if 表达式
支持常量表达式、逻辑运算:
cpp
#define VERSION 2
#if VERSION == 1
// 版本1逻辑
#elif VERSION == 2
// 版本2逻辑
#else
// 兜底逻辑
#endif
5.4 复合条件:defined()
cpp
#if defined(WIN32) && defined(DEBUG)
// Windows 调试专属代码
#endif
5.5 万能代码屏蔽:#if 0
快速注释大块代码,优于多行注释:
cpp
#if 0
// 整块代码永久禁用
void test()
{
// ...
}
#endif
5.6 核心实战1:头文件防止重复包含
cpp
#ifndef _MYHEADER_H_
#define _MYHEADER_H_
// 类定义、函数声明、结构体、全局变量
#endif // _MYHEADER_H_
原理:第一次包含定义宏,后续包含直接跳过内容。
5.7 核心实战2:跨平台兼容
cpp
#ifdef _WIN32
#include <windows.h>
#define OS_NAME "Windows"
#elif __linux__
#include <unistd.h>
#define OS_NAME "Linux"
#elif __APPLE__
#define OS_NAME "MacOS"
#endif
5.8 核心实战3:调试日志开关
cpp
// 上线时注释该行,关闭所有日志
#define DEBUG_MODE
#ifdef DEBUG_MODE
#define LOG cout
#else
#define LOG 0 && cout
#endif
六、特殊预处理指令
6.1 #error 主动编译报错
满足条件时强制终止编译,抛出自定义错误信息:
cpp
#ifndef CPLUSPLUS
#error 必须使用 C++ 编译器编译
#endif
6.2 #line 修改行号与文件名
用于日志、报错信息自定义行号:
cpp
// 修改当前行号为 100,文件名为 test.cpp
#line 100 "test.cpp"
6.3 #pragma 编译器指令
编译器扩展指令,不通用,常用场景:
cpp
// 禁止头文件重复包含(微软编译器)
#pragma once
// 忽略指定警告
#pragma warning(disable:4996)
#pragma once是现代头文件保护简化写法,替代#ifndef方案。
七、C++ 标准预定义宏(内置)
编译器全局预设,无需手动定义,直接使用:
| 预定义宏 | 含义 |
|---|---|
__LINE__ |
当前代码行号 |
__FILE__ |
当前源文件完整路径 |
__DATE__ |
编译日期:Mmm dd yyyy |
__TIME__ |
编译时间:hh:mm:ss |
__cplusplus |
C++ 标准版本号,C语言中未定义 |
__STDC__ |
是否标准C编译器 |
实战:快速定位错误代码行
cpp
cout << "文件:" << __FILE__
<< " 行号:" << __LINE__
<< " 编译时间:" << __TIME__ << endl;
八、宏 VS const VS inline 深度对比
| 特性 | #define 宏 | const/constexpr | inline 内联函数 |
|---|---|---|---|
| 类型检查 | 无 | 强类型检查 | 强类型检查 |
| 作用域 | 全局 | 局部/类内/全局 | 遵循函数作用域 |
| 调试支持 | 不可调试 | 可调试 | 可调试 |
| 安全等级 | 低,易出错 | 高 | 高 |
| 运行效率 | 纯替换,效率高 | 编译期常量,效率高 | 展开调用,效率高 |
| 推荐场景 | 条件编译、跨平台、日志宏 | 固定常量 | 短小高频函数 |
工程开发原则
- 单纯常量:弃宏,用 constexpr
- 工具类短小函数:弃宏函数,用 inline
- 跨平台、调试开关、头文件保护:保留预处理条件编译
九、预处理 总结
- 预处理发生在编译最前端,核心是文本替换+代码裁剪;
#define宏灵活但不安全,现代 C++ 尽量弱化使用;- 条件编译是大型项目、跨平台、版本管理的核心手段;
#字符串化、##拼接,用于高阶宏编程;- 内置预定义宏可快速实现日志埋点、代码定位;
- 头文件保护、跨平台适配、调试控制是预处理器最高频业务场景。