Skip to content

C++ 预处理器

C++ 预处理器(Preprocessor)是一个程序,它在源代码传递给实际的编译器 之前 对其进行处理。它操作的是以 # 开头的 指令(Directives)。这些指令指示预处理器执行诸如包含文件、定义宏、条件编译代码等操作。

预处理器指令 不是 C++ 语句;它们不以分号(;)结尾。一行指令之前只能有空白字符。

常见的预处理器指令包括 #include、#define、#ifdef、#ifndef、#if、#else、#elif、#endif、#pragma。

这是最常见的指令。它告诉预处理器将另一个文件(通常是包含声明的头文件)的内容插入到当前源文件的该位置。

  • #include <filename>:在标准系统目录中搜索文件(例如,<iostream>、<vector> 等标准库头文件)。
  • #include "filename":首先在包含当前源文件的目录中搜索,然后才在标准系统目录中搜索(通常用于你自己的项目头文件)。

#define 指令用于创建 宏(Macros)。宏在编译之前执行文本替换。

1. 类对象宏(Symbolic Constants):

#define identifier replacement_text

预处理器会将 identifier 的每个后续出现都替换为 replacement_text。

#include <iostream>
#define PI 3.14159
#define GREETING "Hello, World!"
int main() {
double radius = 5.0;
double area = PI * radius * radius;
std::cout << GREETING << std::endl;
std::cout << "Value of PI: " << PI << std::endl;
std::cout << "Area: " << area << std::endl;
return 0;
}
// Preprocessor output (conceptually):
// 预处理器输出(概念上):
// int main() {
// double radius = 5.0;
// double area = 3.14159 * radius * radius;
// std::cout << "Hello, World!" << std::endl;
// std::cout << "Value of PI: " << 3.14159 << std::endl;
// std::cout << "Area: " << area << std::endl;
// return 0;
// }

现代 C++ 替代方案: 对于常量,优先使用 const 或 constexpr 变量。与宏不同,它们提供类型安全并遵守作用域(Scope)。

const double PI_CONST = 3.14159;
constexpr int MAX_USERS = 100; // Compile-time constant
// 编译时常量

2. 类函数宏(Function-like Macros):

#define MACRO_NAME(param1, param2, ...) replacement_text

这些宏接受参数并根据参数执行文本替换。使用时务必非常小心! 由于意外的展开和缺乏类型检查,它们容易出错。

#include <iostream>
// Potential pitfalls: requires careful parenthesizing!
// 潜在陷阱:需要仔细使用括号!
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
int main() {
int x = 100;
int y = 30;
std::cout << "The minimum is " << MIN(x, y) << std::endl; // Expands to (((x) < (y)) ? (x) : (y))
// 展开为 (((x) < (y)) ? (x) : (y))
// Example of potential error without parentheses in definition:
// 定义中不使用括号可能导致的错误示例:
// #define MULTIPLY(a, b) a * b
// int result = MULTIPLY(2 + 3, 4); // Expands to 2 + 3 * 4 = 14, NOT (2+3)*4 = 20
// 展开为 2 + 3 * 4 = 14,而不是 (2+3)*4 = 20
return 0;
}

现代 C++ 替代方案: 对于类函数行为,优先使用内联函数(Inline Functions)或函数模板(Function Templates)。它们提供类型安全、作用域控制,并且通常更容易调试。

template <typename T>
inline T findMin(const T& a, const T& b) { // Inline function template
// 内联函数模板
return (a < b) ? a : b;
}
int main() {
std::cout << "Min using template: " << findMin(100, 30) << std::endl;
std::cout << "Min using template: " << findMin(5.5, 2.1) << std::endl;
}

预处理器指令允许你根据特定条件选择性地包含或排除代码块进行编译。这对于特定平台代码、调试构建或启用/禁用功能非常有用。

  • #ifdef identifier:如果 identifier 已定义(例如,通过 #define),则包含后续代码。
  • #ifndef identifier:如果 identifier 未 定义,则包含后续代码。
  • #if expression:如果常量 expression 的计算结果为非零(真),则包含后续代码。
  • #else:如果在它之前的 #if、#ifdef、#ifndef 或 #elif 条件为假,则包含此处的代码。
  • #elif expression:一个 “else if” 条件,如果在它之前的 #if 或 #elif 条件为假,则检查此条件。
  • #endif:标记条件编译块(#if、#ifdef、#ifndef)的结束。

示例:调试消息

#include <iostream>
// Define DEBUG macro (perhaps via compiler flag -DDEBUG)
// 定义 DEBUG 宏(可能通过编译器标志 -DDEBUG)
#define DEBUG
int main() {
int x = 5;
#ifdef DEBUG
std::cerr << "Debug: Starting main function." << std::endl;
std::cerr << "Debug: Variable x = " << x << std::endl;
#endif
std::cout << "Performing main task..." << std::endl;
#if defined(DEBUG) && DEBUG > 1 // Check if DEBUG is defined and greater than 1
// 检查 DEBUG 是否已定义且大于 1
std::cerr << "Debug: Verbose logging enabled." << std::endl;
#endif
#ifndef NDEBUG // NDEBUG is often defined for release builds
// NDEBUG 通常在发布构建中定义
std::cout << "Assertion checks might be enabled." << std::endl;
#else
std::cout << "Running in release mode (NDEBUG defined)." << std::endl;
// 在发布模式下运行 (NDEBUG 已定义)
#endif
#ifdef DEBUG
std::cerr << "Debug: Exiting main function." << std::endl;
#endif
return 0;
}

常见用途:头文件中的 包含守卫(Include Guards) 以防止多次包含:

my_header.h
#ifndef MY_HEADER_H
#define MY_HEADER_H
// Header content (declarations, etc.)
// 头文件内容(声明等)
class MyClass { /* ... */ };
#endif // MY_HEADER_H

或者,许多现代编译器支持 #pragma once,它以更简单的语法实现相同的功能,尽管它不是官方标准的一部分。

my_header_pragma.h
#pragma once
// Header content
// 头文件内容
class MyClass2 { /* ... */ };

这些运算符用于 #define 指令内部:

  • #(字符串化运算符,Stringizing Operator): 将宏参数转换为字符串字面值(String Literal)。
  • ##(标记粘贴运算符,Token-Pasting Operator): 将两个标记(标识符、关键字等)连接成一个单独的标记。

示例:

#include <iostream>
#include <string>
#define STRINGIZE(x) #x
#define CONCAT(a, b) a##b
int main() {
// Using Stringize
// 使用字符串化运算符
std::cout << STRINGIZE(Hello C++) << std::endl; // Expands to: std::cout << "Hello C++" << std::endl;
// 展开为: std::cout << "Hello C++" << std::endl;
const char* varNameStr = STRINGIZE(myVariable);
std::cout << "Variable name: " << varNameStr << std::endl; // Outputs: Variable name: myVariable
// 输出: Variable name: myVariable
// Using Concatenate
// 使用标记粘贴运算符
int xy = 100;
std::cout << CONCAT(x, y) << std::endl; // Expands to: std::cout << xy << std::endl; Outputs: 100
// 展开为: std::cout << xy << std::endl; 输出: 100
std::string CONCAT(user, Name) = "Alice"; // Creates variable userName
// 创建变量 userName
std::cout << "User: " << userName << std::endl; // Outputs: User: Alice
// 输出: User: Alice
return 0;
}

尽管有时很有用,但这些运算符可能会使代码变得晦涩难懂,并且在现代 C++ 开发中,与模板或 constexpr 函数等替代方案相比,它们的使用较少。

C++ 定义了几种标准的预定义宏,它们提供了有用的信息:

宏描述
__LINE__整数字面值(Integer Literal):当前源文件中的行号。
__FILE__字符串字面值(String Literal):当前源文件的名称。
__DATE__字符串字面值:源文件编译时的日期(例如,“MMM DD YYYY”)。
__TIME__字符串字面值:源文件编译时的时间(例如,“HH:MM:SS”)。
__cplusplus整数字面值:正在使用的 C++ 标准版本(例如,C++11 为 201103L,C++14 为 201402L,C++17 为 201703L,C++20 为 202002L)。
__func__(并非严格意义上的宏,但类似)字符串字面值:当前函数的名称(在函数内部可用)。

示例:

#include <iostream>
#include <string>
void log_message(const std::string& msg) {
std::cout << "[File: " << __FILE__
<< ", Line: " << __LINE__
<< ", Func: " << __func__ // Use __func__ inside function
// 在函数内部使用 __func__
<< "] " << msg << std::endl;
}
int main() {
std::cout << "Compiling using C++ version: " << __cplusplus << std::endl;
std::cout << "Compiled on: " << __DATE__ << " at " << __TIME__ << std::endl;
log_message("Entering main function.");
return 0;
}

输出将根据使用的编译器、时间和标准版本而有所不同。