C++ 预处理器

C++ 预处理器

一、预处理器 基础概念

1.1 编译四大阶段

  1. 预处理# 指令处理、宏替换、头文件展开、注释删除、条件裁剪
  2. 编译:语法检查、词法分析、生成汇编
  3. 汇编:汇编转机器码目标文件
  4. 链接:合并目标文件、解析外部符号

1.2 预处理器核心规则

  • 所有预处理指令以 # 开头
  • # 前允许空白符(空格、制表符),不能有有效代码
  • 预处理语句无需分号结尾,加分号会被一并替换,引发bug
  • 本质:纯文本无脑替换,无类型检查、无语法校验、无作用域
  • 跨行宏:使用反斜杠 \ 换行连接

1.3 常用预处理指令总览

指令 作用
#include 引入头文件
#define 定义宏、常量、宏函数
#undef 取消宏定义
#ifdef / #ifndef / #endif 基础条件编译
#if / #else / #elif 表达式条件编译
#line 修改行号与文件名
#error 主动抛出编译错误
#pragma 编译器自定义指令(跨编译器)
#using 引入托管代码(C++/CLI 专用)

二、#include 头文件包含

2.1 两种包含方式

  1. <头文件>:系统库头文件,优先搜索系统库路径
cpp 复制代码
#include <iostream>
#include <cstring>
  1. "头文件":自定义头文件,优先搜索当前项目目录
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 宏 致命缺陷(工程必看)

  1. 纯文本替换,无类型检查,极易引发隐式错误

  2. 不具备作用域,全局生效,命名冲突严重

  3. 宏函数会引发双重计算

    cpp 复制代码
    #define SQR(x) ((x)*(x))
    // 调用 SQR(i++) 会导致 i 自增两次
  4. 无法调试,无符号信息,断点无法进入

  5. 破坏代码可读性,语法隐晦

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 指令分类

  1. 宏判断:#ifdef / #ifndef / #elifdef
  2. 表达式判断:#if / #elif
  3. 结束标记:#endif
  4. 取反判断:#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 内联函数
类型检查 强类型检查 强类型检查
作用域 全局 局部/类内/全局 遵循函数作用域
调试支持 不可调试 可调试 可调试
安全等级 低,易出错
运行效率 纯替换,效率高 编译期常量,效率高 展开调用,效率高
推荐场景 条件编译、跨平台、日志宏 固定常量 短小高频函数

工程开发原则

  1. 单纯常量:弃宏,用 constexpr
  2. 工具类短小函数:弃宏函数,用 inline
  3. 跨平台、调试开关、头文件保护:保留预处理条件编译

九、预处理 总结

  1. 预处理发生在编译最前端,核心是文本替换+代码裁剪
  2. #define 宏灵活但不安全,现代 C++ 尽量弱化使用;
  3. 条件编译是大型项目、跨平台、版本管理的核心手段;
  4. # 字符串化、## 拼接,用于高阶宏编程;
  5. 内置预定义宏可快速实现日志埋点、代码定位;
  6. 头文件保护、跨平台适配、调试控制是预处理器最高频业务场景。
相关推荐
CN-Dust1 小时前
【C++专题】格式化输出与输入
开发语言·c++·算法
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题】【Java基础篇】第19题:HashMap的key如何减少发生哈希冲突
java·开发语言·后端·面试·哈希算法·hash-index·hash
Titan20241 小时前
C++位图学习笔记
c++·笔记·学习
6Hzlia1 小时前
【Hot 100 刷题计划】 LeetCode 148. 排序链表 | C++ 归并排序自顶向下
c++·leetcode·链表
im_AMBER1 小时前
Leetcode 162 除了自身以外数组的乘积 | 接雨水
开发语言·javascript·数据结构·算法·leetcode
是个西兰花2 小时前
C++:异常
开发语言·c++·异常
cpp_25012 小时前
P1873 [COCI 2011/2012 #5] EKO / 砍树
数据结构·c++·算法·题解·二分答案·洛谷·csp
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题】【Java基础篇】第18题:HashMap底层是如何扩容的
java·开发语言·面试·散列表·hash-index·hash
AbandonForce2 小时前
Map类:pair键值对|map的基本操作|operator[]
开发语言·c++·算法·leetcode