前言
在编译之前,每个代码 (.cpp) 文件都会经历预处理阶段。在此阶段,称为预处理器的程序对代码文件的文本进行各种更改。
预处理器实际上不会以任何方式修改原始代码文件,相反,预处理器所做的所有更改要么临时发生在内存中,要么使用临时文件。
预处理、编译、链接的整个过程称为翻译。
预处理器指令
当预处理器运行时,它会扫描代码文件(从上到下),查找预处理器指令。
预处理器指令(通常简称为指令)是以 # 符号开头并以换行符(而不是分号)结尾的指令。
下面介绍一些最常见的预处理器指令:
#Include
#include 文件时,预处理器会将 #include 指令替换为所包含文件的内容。然后对包含的内容进行预处理(这可能会导致以递归方式预处理其他 #include),然后对文件的其余部分进行预处理。
示例:
#include <iostream>
int main()
{
std::cout << "Hello, world!\n";
return 0;
}
然后,我们执行编译预处理指令:
g++ -E main.cpp -o main.ii
输出预处理main.ii文件:
一旦预处理器完成对代码文件和所有 #included 内容的处理,结果就称为转换单元。转换单元是发送到编译器进行编译的内容。
翻译单元既包含代码文件中的已处理代码,也包含所有 #included 文件中已处理的代码。
宏定义
#define 指令可用于创建宏。在 C++ 中,宏是定义如何将输入文本转换为替换输出文本的规则。
宏有两种基本类型:类对象宏
和类函数宏
。
类函数宏
的行为类似于函数,它们的使用通常被认为是不安全的。
#define identifier
#define identifier substitution_text
带有替换文本的类似对象的宏
#include <iostream>
#define MY_NAME "Alex"
int main()
{
std::cout << "My name is: " << MY_NAME << '\n';
return 0;
}
不带替换文本的类似对象的宏
#define USE_YEN
条件编译
条件编译预处理器指令允许您指定在什么条件下编译或不编译某些内容。
有相当多的不同条件编译指令,但我们只介绍迄今为止使用最多的三个指令:#ifdef
、#ifndef
和 #endif
。
**#ifdef **
#ifdef 预处理器指令
允许预处理器检查标识符之前是否已 #defined
。如果是这样,则编译 #ifdef 和匹配 #endif 之间的代码。否则,将忽略该代码。
程序示例:
#include <iostream>
#define PRINT_JOE //预处理器指令 PRINT_JOE
int main()
{
#ifdef PRINT_JOE // 匹配,因为PRINT_JOE已经定义
std::cout << "Joe\n"; //
#endif
#ifdef PRINT_BOB // 不匹配,因为PRINT_BOB没有定义
std::cout << "Bob\n";
#endif
return 0;
}
输出:
#ifndef
#ifndef
与 #ifdef
相反,从字面就可以看出来,因为多个n(not)。因为它允许您检查标识符是否尚未 #define
。
#include <iostream>
int main()
{
#ifndef PRINT_BOB
std::cout << "Bob\n";
#endif
return 0;
}
输出:
#if 0
条件编译的另一种常见用法是使用 #if 0
将代码块排除在编译之外(就像它在注释块中一样):
#include <iostream>
int main()
{
std::cout << "Joe\n";
#if 0 // Don't compile anything starting here
std::cout << "Bob\n";
std::cout << "Steve\n";
#endif // until this point
return 0;
}
输出:
要暂时重新启用已包装在中的代码,可以将 更改为 :#if 1
。