C++编程基础(九):预处理指令

文章目录

一、预处理概述

在 C++ 代码被编译成机器指令之前,会先进行一次预处理 。预处理器处理所有以 # 开头的指令。它的工作本质上是文本替换:在编译器介入之前,把代码"整理"好。

主要功能:

  • 文件包含:把头文件的内容复制到源文件中 (#include)。
  • 宏定义:字符串替换 (#define)。
  • 条件编译:决定哪些代码参与编译,哪些被忽略 (#ifdef, #ifndef)。

二、文件包含与代码组织

为了方便维护,C++ 项目通常将声明放入头文件 (.h/.hpp),将实现放入源文件(.cpp)。预处理器负责将它们组装在一起。

1. #include 的两种写法

  • #include <filename>
    • 引用标准库系统头文件
    • 编译器去系统环境变量指定的目录 查找(如 <iostream>, <vector>)。
  • #include "filename"
    • 引用用户自定义头文件
    • 编译器优先在当前项目目录 查找,找不到再去系统目录找。

2. 头文件重复包含问题

如果 A.h 包含了 B.h,而 C.cpp 同时包含了 A.hB.h,那么 B.h 的内容会被复制两次。这会导致"重定义"错误。 我们需要使用头文件保护

方案 A:标准宏卫兵 (兼容性好)

利用条件编译指令来防止重复复制。

cpp 复制代码
// math_utils.h
#ifndef MATH_UTILS_H  // 1. 检查宏是否未定义
#define MATH_UTILS_H  // 2. 如果没定义,则定义它

// ... 声明内容 ...
int add(int a, int b);

#endif // 3. 结束检查

方案 B:使用#pragma once (简洁,效率高)

  • 放在文件开头位置。
  • #pragma once 不涉及宏定义,效率更高。
  • 非标准但被绝大多数现代编译器支持。
cpp 复制代码
// math_utils.h
#pragma once

// ... 声明内容 ...

方案 C:使用 _Pragma 操作符 (C99/C++11)

cpp 复制代码
_Pragma("once")
// ... 头文件内容 ...

_Pragma 可以和宏搭配使用,功能更强。

三、宏定义 (#define)

C++源程序中允许用一个标识符来表示一个字符串 ,称为"宏"。被定义为宏的标识符称为"宏名"。在编译预处理时,对程序中所有出现的宏名,都用宏定义中的字符串去代换,这称为宏替换宏展开

宏定义是由源程序中的宏定义命令完成的,宏替换是由预处理程序自动完成的。宏仅仅是单纯的文本替换,不进行类型检查,使用时需格外小心。

1.无参宏(常量宏)

常用于定义魔法数字。但在现代 C++ 中,建议使用 constconstexpr 代替。

cpp 复制代码
#define PI 3.14
// 预处理后,代码中所有的PI都会变成3.14

2.带参宏(宏函数)

类似函数调用,但省去了函数调用的入栈出栈开销。

语法#define 宏名(参数表) 表达式

由于是文本替换,为了防止运算优先级错误,所有参数和整体表达式都必须加括号

cpp 复制代码
// 错误写法
#define SQUARE(x) x * x
// 调用 SQUARE(1+1) -> 1+1 * 1+1 -> 1+1+1 = 3 (错误!)

// 正确写法
#define SQUARE(x) ((x) * (x))
// 调用 SQUARE(1+1) -> ((1+1) * (1+1)) = 4 (正确)

注意 :宏函数没有类型检查,且容易产生副作用(例如传入 i++ 时可能会被执行两次),现代 C++ 推荐使用 inline 函数或 template

3. 取消宏 (#undef)

cpp 复制代码
#define MAX_WIDTH 100
// ... 使用宏 ...
#undef MAX_WIDTH // 从这里开始,MAX_WIDTH 不再有效

四、条件编译

在 C++中,一般情况下,源程序中所有的行都参加编译。但有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是"条件编译"。

条件编译允许我们根据不同的环境生成不同的代码(例如区分 Windows/Linux,或者 Debug/Release 模式)。

1.基本指令

  • #if / #elif / #else / #endif:基于常量表达式判断。
  • #ifdef 标识符:如果定义了该宏。
  • #ifndef 标识符:如果没定义该宏。

2. 实战场景

场景一:跨平台代码

cpp 复制代码
#ifdef _WIN32
    #include <windows.h>
    void clearScreen() { system("cls"); }
#elif __linux__
    #include <unistd.h>
    void clearScreen() { system("clear"); }
#else
    void clearScreen() { /* 不支持的平台 */ }
#endif

场景二:调试开关

通过定义 DEBUG 宏(通常在编译器参数中设置,如 g++ -DDEBUG ...)来控制日志输出。

cpp 复制代码
#ifdef DEBUG
    #define LOG(msg) std::cout << "[DEBUG] " << msg << std::endl
#else
    #define LOG(msg) // 发布版本中,LOG 被替换为空,不产生任何代码
#endif

int main() {
    LOG("程序启动"); // 只有在定义了 DEBUG 时才会输出
    return 0;
}

五、预定义宏

编译器内置了一些特殊的宏,常用于错误排查。

  • __FILE__:当前源文件名(字符串)。
  • __LINE__:当前代码行号(整数)。
  • __DATE__:编译日期。
  • __TIME__:编译时间。
  • __cplusplus:C++ 标准版本号。
相关推荐
凌康ACG8 小时前
Sciter之c++与前端交互(五)
c++·sciter
郝学胜-神的一滴9 小时前
Linux命名管道:创建与原理详解
linux·运维·服务器·开发语言·c++·程序人生·个人开发
晚风(●•σ )10 小时前
C++语言程序设计——11 C语言风格输入/输出函数
c语言·开发语言·c++
恒者走天下11 小时前
秋招落定,拿到满意的offer,怎么提高自己实际的开发能力,更好的融入团队
c++
天若有情67312 小时前
【c++】手撸C++ Promise:从零实现通用异步回调组件,支持链式调用+异常安全
开发语言·前端·javascript·c++·promise
学困昇12 小时前
C++中的异常
android·java·c++
合作小小程序员小小店12 小时前
桌面安全开发,桌面二进制%恶意行为拦截查杀%系统安全开发3.0,基于c/c++语言,mfc,win32,ring3,dll,hook,inject,无数据库
c语言·开发语言·c++·安全·系统安全
Codeking__12 小时前
C++ 11 atomic 原子性操作
开发语言·c++
crescent_悦13 小时前
PTA L1-020 帅到没朋友 C++
数据结构·c++·算法