乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

预处理指令详解

定义与作用

预处理(Preprocessing)是 C++ 编译流程的第 4 阶段——在真正的编译之前,由预处理器对源文件进行文本级别的处理。预处理器理解一套独立于 C++ 语法的指令语言,以 # 开头。

#include <iostream>          // 包含头文件
#define MAX_SIZE 100         // 宏定义
#ifdef DEBUG                 // 条件编译
    std::cout << "Debug mode" << std::endl;
#endif
指令功能
#include包含头文件内容
#define / #undef定义 / 取消宏
#if / #ifdef / #ifndef / #else / #elif / #endif条件编译
#error产生编译错误
#pragma实现特定指令
#line控制行号和文件名
# / ##字符串化 / 连接运算符

核心原理

翻译阶段中预处理的位置

#include 搜索路径差异

include guard vs #pragma once

宏展开流程

完整示例

示例一:宏定义与多次求值陷阱

场景说明:大翔为飞翔科技编写跨平台工具宏,展示宏的常见使用与陷阱。

#include <iostream>
#include <string>

// ---- 基本宏定义 ----
#define FEIXIANG_VERSION "2.0.0"
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))

// 宏中的 # 运算符:字符串化
#define TO_STRING(x) #x

// 宏中的 ## 运算符:连接
#define CONCAT(a, b) a##b
#define MAKE_FUNC(name) void name##_impl()

// ---- 多次求值陷阱 ----
int counter = 0;

int getNext() {
    return ++counter;  // 每次调用都有副作用
}

// ---- 条件编译 ----
#define FEATURE_LOGGING     // 打开日志功能
// #define FEATURE_CACHE    // 缓存功能(注释掉以禁用)
#define LOG_LEVEL 2

// ---- #error 使用 ----
#if LOG_LEVEL > 3
    #error "LOG_LEVEL 不能超过 3"
#endif

int main() {
    std::cout << "===== 飞翔科技预处理宏演示 =====" << std::endl;

    // ---- 基本宏 ----
    std::cout << "\n[基本宏]" << std::endl;
    std::cout << "版本: " << FEIXIANG_VERSION << std::endl;
    std::cout << "MAX(3, 7) = " << MAX(3, 7) << std::endl;
    std::cout << "SQUARE(5) = " << SQUARE(5) << std::endl;

    // ---- # 字符串化 ----
    std::cout << "\n[# 字符串化运算符]" << std::endl;
    std::cout << "TO_STRING(feixiang) = " << TO_STRING(feixiang) << std::endl;
    std::cout << "TO_STRING(3.14) = " << TO_STRING(3.14) << std::endl;

    // ---- ## 连接运算符 ----
    std::cout << "\n[## 连接运算符]" << std::endl;
    int CONCAT(my, Var) = 42;
    std::cout << "CONCAT(my, Var) = myVar = " << myVar << std::endl;

    // ---- 多次求值陷阱 ----
    std::cout << "\n[陷阱: 多次求值]" << std::endl;
    counter = 0;
    int result = MAX(getNext(), getNext());
    // 展开为: ((getNext()) > (getNext()) ? (getNext()) : (getNext()))
    std::cout << "MAX(getNext(), getNext()) = " << result << std::endl;
    std::cout << "counter 被调用了 " << counter << " 次!(而不是期望的 2 次)"
              << std::endl;

    // ---- 条件编译 ----
    std::cout << "\n[条件编译]" << std::endl;

#ifdef FEATURE_LOGGING
    std::cout << "  日志功能: 已启用" << std::endl;
#else
    std::cout << "  日志功能: 已禁用" << std::endl;
#endif

#ifdef FEATURE_CACHE
    std::cout << "  缓存功能: 已启用" << std::endl;
#else
    std::cout << "  缓存功能: 已禁用" << std::endl;
#endif

#if LOG_LEVEL >= 2
    std::cout << "  日志级别: " << LOG_LEVEL << " (详细)" << std::endl;
#else
    std::cout << "  日志级别: " << LOG_LEVEL << " (简要)" << std::endl;
#endif

    // ---- 预定义宏 ----
    std::cout << "\n[预定义宏]" << std::endl;
    std::cout << "  文件名: " << __FILE__ << std::endl;
    std::cout << "  行号: "   << __LINE__ << std::endl;
    std::cout << "  日期: "   << __DATE__ << std::endl;
    std::cout << "  时间: "   << __TIME__ << std::endl;
    std::cout << "  C++ 标准: " << __cplusplus
              << " (";
#if __cplusplus >= 202002L
    std::cout << "C++20";
#elif __cplusplus >= 201703L
    std::cout << "C++17";
#elif __cplusplus >= 201402L
    std::cout << "C++14";
#elif __cplusplus >= 201103L
    std::cout << "C++11";
#endif
    std::cout << ")" << std::endl;

    return 0;
}

预期输出:

===== 飞翔科技预处理宏演示 =====

[基本宏]
版本: 2.0.0
MAX(3, 7) = 7
SQUARE(5) = 25

[# 字符串化运算符]
TO_STRING(feixiang) = feixiang
TO_STRING(3.14) = 3.14

[## 连接运算符]
CONCAT(my, Var) = myVar = 42

[陷阱: 多次求值]
MAX(getNext(), getNext()) = 3
counter 被调用了 4 次!(而不是期望的 2 次)

[条件编译]
  日志功能: 已启用
  缓存功能: 已禁用
  日志级别: 2 (详细)

[预定义宏]
  文件名: C:\Users\AOXIANG\Desktop\Cpp\指南\01_C++ 概述与开发环境\预处理指令详解.md
  行号: XX
  日期: Jun 14 2026
  时间: XX:XX:XX
  C++ 标准: 201103L (C++11)

逐段分析:

  • MAX(a,b) 中用 ((a) > (b) ? (a) : (b)) 包裹每个参数——防止运算符优先级问题(如 MAX(2, 3) * 5 展开后 (2)>(3)?(2):(3)*5 导致错误)
  • 多次求值陷阱:MAX(getNext(), getNext()) 展开后 getNext() 可能被调用多次——> 一次、? 分支一次、: 分支一次,共 3-4 次。这是宏 vs 内联函数的经典差异
  • # 运算符将宏参数转为字符串字面量——常用于日志和断言
  • ## 运算符连接两个标记——常用于生成变量名或函数名
  • #ifdef / #ifndef 检查宏是否定义,#if 检查宏的值——#if LOG_LEVEL >= 2 可以在编译期根据数值决定是否保留代码
  • __cplusplus 是 C++ 标准版本宏——C++11 为 201103L,C++14 为 201402L,C++17 为 201703L

示例二:用 constexpr 和内联函数替代宏

场景说明:展示现代 C++ 中如何用语言特性替代宏,避免宏的副作用。

#include <iostream>
#include <string>
#include <algorithm>

// ---- 替代方案 1:constexpr 替代宏常量 ----
// 宏: #define MAX_BUFFER_SIZE 1024
constexpr int maxBufferSize = 1024;

// ---- 替代方案 2:constexpr 函数替代宏函数 ----
// 宏: #define SQUARE(x) ((x)*(x))
template <typename T>
constexpr T square(const T& x) {
    return x * x;  // 无多次求值问题
}

// ---- 替代方案 3:内联模板函数替代带副作用的宏 ----
// 宏: #define MAX(a,b) ((a)>(b)?(a):(b))
template <typename T>
constexpr const T& maxof(const T& a, const T& b) {
    return (a > b) ? a : b;
}

// ---- 替代方案 4:constexpr 替代条件编译常量 ----
constexpr bool enableLogging = true;
constexpr bool enableCaching = false;
constexpr int logLevel = 2;

template <bool Enable>
void conditionalLog(const std::string& msg) {
    if constexpr (Enable) {  // C++17 if constexpr
        std::cout << "[LOG] " << msg << std::endl;
    }
    // Enable 为 false 时,整个分支不会被编译
}

int main() {
    std::cout << "===== 用语言特性替代宏 =====" << std::endl;

    // ---- constexpr 常量 ----
    std::cout << "\n[constexpr 替代宏常量]" << std::endl;
    int buffer[maxBufferSize];  // 可用于数组边界
    std::cout << "maxBufferSize = " << maxBufferSize << std::endl;

    // ---- constexpr 函数 ----
    std::cout << "\n[constexpr 替代宏函数]" << std::endl;
    std::cout << "square(5) = " << square(5) << std::endl;
    std::cout << "square(2 + 3) = " << square(2 + 3) << std::endl;

    // 对比:多次求值不会发生
    int counter2 = 0;
    auto getVal = [&]() { return ++counter2; };

    // 宏版本会导致多次调用(见示例一)
    // 函数版本:每个参数只求值一次
    int a = getVal();
    int b = getVal();
    int result = maxof(a, b);  // safe: a 和 b 各只求值一次
    std::cout << "maxof(val1, val2): 每个参数只求值 1 次,counter2 = "
              << counter2 << std::endl;

    // ---- if constexpr 替代条件编译 ----
    std::cout << "\n[if constexpr 替代 #ifdef]" << std::endl;

    conditionalLog<enableLogging>("日志功能已启用");
    conditionalLog<enableCaching>("缓存功能已启用");  // 不会被编译

    if constexpr (logLevel >= 2) {
        std::cout << "日志级别: " << logLevel << " (详细模式)" << std::endl;
    }

    // ---- inline 替代宏字符串化 ----
    // 宏: #define TO_STRING(x) #x
    std::cout << "\n[内联替代 # 运算符]" << std::endl;
    std::cout << "直接使用 C++ 字符串: " << "feixiang" << std::endl;

    return 0;
}

预期输出:

===== 用语言特性替代宏 =====

[constexpr 替代宏常量]
maxBufferSize = 1024

[constexpr 替代宏函数]
square(5) = 25
square(2 + 3) = 25

[if constexpr 替代 #ifdef]
[LOG] 日志功能已启用
日志级别: 2 (详细模式)

[内联替代 # 运算符]
直接使用 C++ 字符串: feixiang

逐段分析:

  • constexpr 常量完全替代 #define 常量——拥有类型安全和作用域,可用于数组边界等编译期常量场景
  • constexpr 函数替代宏函数——参数只求值一次,有类型检查,有运算符优先级保证
  • if constexpr(C++17)替代 #ifdef / #if——条件在编译期求值,false 分支不会被编译,但语法上仍然是合法的 C++ 代码(编辑器/IDE 友好)
  • 内联模板函数替代 MAX(a,b) 宏——maxof(a, b) 中每个参数只求值一次,不存在多次求值陷阱
  • 现代 C++ 哲学:能用语言特性就不用预处理器——constexpr / inline / if constexpr 提供了编译期优化能力,同时保留类型安全和可调试性

易错场景与面试考点

易错场景

场景错误表现正确做法
宏函数参数未加括号运算符优先级出错每个参数和整体都加括号
宏函数中参数有副作用参数被多次求值用 constexpr / inline 函数替代
#include "..." vs <...> 混淆找不到头文件项目内用 "",系统库用 <>
include guard 宏名冲突两个头文件用相同宏名,一个被跳过使用唯一命名(如 PROJECT_PATH_FILE_H)
#define 污染全局命名空间名称冲突用 constexpr 或 enum 替代常量宏

常见面试问题

  1. #include "file.h" 和 #include <file.h> 的区别?——前者从当前文件所在目录开始搜索,找不到时回退到系统路径;后者直接从系统路径搜索。前者用于项目内部头文件,后者用于标准库和第三方库。

  2. include guard 和 #pragma once 如何选择?——include guard 是标准 C++,100% 可移植;#pragma once 几乎所有编译器支持,更简洁但非标准。大型项目中可以两者兼具(兼容性 + 优化提示)。

  3. 宏的 # 和 ## 运算符是做什么的?——# 将参数转为字符串字面量(字符串化);## 将两个标记连接为一个标记(标记粘贴)。常用于生成变量名、函数名或错误消息。

  4. 为什么现代 C++ 推荐用 constexpr 替代 #define?——constexpr 有类型安全、作用域、可调试(带符号信息),且是真正的 C++ 语言构造,参与重载决议和模板推导。宏只是文本替换,没有类型、没有作用域、不可调试。

  5. __cplusplus 宏的值代表什么?——C++ 标准版本标识:C++11 = 201103L、C++14 = 201402L、C++17 = 201703L、C++20 = 202002L、C++23 = 202302L。

小结

  • #include 区分本地("")和系统(<>)搜索路径
  • #define 宏函数必须用括号保护参数,警惕多次求值陷阱
  • #if / #ifdef / #ifndef 实现条件编译——编译期排除平台特定代码
  • include guard(#ifndef + #define + #endif)是标准可移植的头文件保护方式
  • # 字符串化和 ## 连接是宏特有的能力,其他场景优先考虑语言特性替代
  • 预定义宏:__LINE__、__FILE__、__DATE__、__TIME__、__cplusplus
  • 现代 C++ 哲学:constexpr 替代常量宏、inline/constexpr 函数替代宏函数、if constexpr 替代条件编译
上一页
开发环境配置