网络公司手机网站模板,.net 网站中多线程,淘宝网站建设方式,重庆企业网站推广方案当你在编译项目时,你可能希望编译器完全按照你编写的方式编译每一个代码文件,当事实并非如此。
相反,在编译之前,每一个.cpp文件都会经历一个预处理的阶段,在此阶段中,称为预处理器的程序对代码文件的文本进行各种更改.
预处理器实际上不会以任何方式修改原始代码文件,预处理…当你在编译项目时,你可能希望编译器完全按照你编写的方式编译每一个代码文件,当事实并非如此。
相反,在编译之前,每一个.cpp文件都会经历一个预处理的阶段,在此阶段中,称为预处理器的程序对代码文件的文本进行各种更改.
预处理器实际上不会以任何方式修改原始代码文件,预处理器所做的更改要么临时发生在内存中,要么使用临时文件。 从历史角度看,预处理器是与编译器分开的程序,但在现代编译器中,预处理器可以直接构建到编译器本身中。 预处理器所做的大部分工作都是相当无趣的,例如,它删除注释,并确保每个代码文件以换行符结束。然而,预处理器确实有一个非常重要的作用:处理#include指令; 但预处理器完成对代码文件的处理时,结果称为 翻译单元 该翻译单元随后由编译器编译。 预处理、编译、链接的整个过程称为翻译; 预处理器指令
当预处理器运行时,它会从上到下的扫描代码文件,插在预处理指令。
预处理指令 通常称为指令,是以#符号开头并以换行符结尾的指令。这些指令告诉预处理器执行某些文本创作任务。
请注意,预处理器不理解c语法,相反,它有自己的指令语法,只能说类似c语法。 #Include
想必你已经看到了*#include指令的实际应用通常是 #include 。当您#include*文件时预处理器会将 #include 指令替换为所包含文件的内容。然后对包含的内容进行预处理这可能会导致递归地预处理额外的 #include然后对文件的其余部分进行预处理。
考虑下面的程序:
#includeiostreamint main()
{std::cout Hello,cpp\n;return 0;
}当预处理器在此程序上运行时,预处理器将用名为“iostream”文件的v内容替换#includeiostream然后预处理包含的内容和文件的其余部分。 每个翻译单元通常由单个代码 (.cpp) 文件和它 #include 的所有头文件组成递归应用因为头文件可以 #include 其他头文件。 宏定义
#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;
} 预处理器将上面的内容转换为一下内容:
// The contents of iostream are inserted here
int main()
{std::cout My name is: Alex \n;return 0;
}运行时会打印输出My name is: Alex 。
使用带有替换文本的类对象宏在 C 中作为将名称分配给文字的方法。这不再是必要的因为 C 中有更好的方法。现在大多数带有替换文本的类似对象的宏只能在遗留代码中看到我们建议尽可能避免它们。 除非不存在可行的替代方案否则请避免使用带有替换文本的宏。 没有替换文本的类对象宏
类似对象的宏也可以在没有替换文本的情况下定义。
#define USE_YEN这种形式的宏的工作方式与你期望的一样:这种形式的宏的工作方式与您可能期望的一样标识符的大多数后续出现都被删除并被替换为空
#include iostream
#define MY_NAME
int main()
{int x MY_NAME 5;std::cout x \n;return 0;
}上面的宏在代码执行之后会使用空来替换掉对应的位置,导致最终的输出结果为5;这看起来并没有什么作用,确实,这也不是该宏的通常用途,具体会在后面的内容中进行讨论。
条件编译
条件编译预处理器指令允许你指定在什么条件下不去编译某些代码块。有很多不同的条件编译指令,但我们这里只会介绍一些最常用的:
#ifdef#ifndef#endif
#ifdef预处理器指令允许预处理器检查是否已存在指定的标识符,如果是,则会编译#ifdef和#endif之间的代码,否则忽略该代码。
考虑下面的程序:
#include iostream
#define PRINT_JOE
int main()
{
#ifdef PRINT_JOEstd::cout Joe\n;
#endif#ifdef PRINT_BOBstd::cout Bob\n;
#endifreturn 0;
}因为 PRINT_JOE 已被 #define std::cout Joe\n行将被编译。由于 PRINT_BOB 尚未#define因此std::cout Bob\n行将被忽略。所以程序将打印Joe
#ifndef与*#ifdef相反它允许你检查标识符是否尚未*#define 。
#include iostreamint main()
{
#ifndef PRINT_BOBstd::cout Bob\n;
#endifreturn 0;
}该程序打印“Bob”因为 PRINT_BOB 从未被*#define*过。
除了#ifdef PRINT_BOB和#ifndef PRINT_BOB之外您还将看到#if defined(PRINT_BOB)和#if !defined(PRINT_BOB) 。它们的作用相同但使用稍微更 C 风格的语法。 if 0
条件编译的一种更常见的用法是使用#if0来排除代码块的编译:
#include iostreamint main()
{std::cout Joe\n;#if 0 // 此处开始的内容不会被编译std::cout Bob\n;std::cout Steve\n;
#endif // 编译排除结束点return 0;
}上面的代码只打印“Joe”因为“Bob”和“Steve”被*#if 0*预处理器指令排除在编译之外。这提供了一种便捷的方式来“注释掉”包含多行注释的代码由于多行注释是不可嵌套的因此无法使用另一个多行注释来注释掉
#include iostreamint main()
{std::cout Joe\n;#if 0 std::cout Bob\n;/* Some* multi-line* comment here*/std::cout Steve\n;
#endif return 0;
}要暂时重新启用#if 0中包含的代码您可以将#if 0更改为#if 1
#include iostreamint main()
{std::cout Joe\n;#if 1 // always true, so the following code will be compiledstd::cout Bob\n;/* Some* multi-line* comment here*/std::cout Steve\n;
#endifreturn 0;
}其他预处理器中的宏替换
现在你可能想知道,前面提到的没有替换文本的类对象宏的用法:
#define PRINT_JOEint main()
{
#ifdef PRINT_JOEstd::cout Joe\n; // will be compiled since PRINT_JOE is defined
#endifreturn 0;
}既然我们将PRINT_JOE定义为空那么预处理器为什么不将*#ifdef PRINT_JOE中的PRINT_JOE*替换为空并从编译中排除输出语句呢
在大多数情况下当你在某些预处理器命令中使用宏标识符时不会进行宏替换。这意味着预处理器不会将这些宏标识符替换为它们的定义内容。这是因为这些命令本身就是用于控制代码的编译过程而不是直接进行代码替换。
#ifdef 只是检查宏是否存在 #ifdef即“如果已定义”并不会关心宏的值是什么。它仅仅检查宏是否已经定义。只要你写了 #define PRINT_JOE无论是空的还是有值的宏 PRINT_JOE 就被认为是已定义的预处理器就会编译 #ifdef 和 #endif 之间的代码。
这就是为什么即使 PRINT_JOE 是空的它依然会导致 std::cout Joe\n; 被编译的原因。 这一规则至少有一个例外大多数形式的#if和#elif在预处理器命令中会进行宏替换。 #include iostream
#define MAX 100
#if MAX 50int x MAX;
#endif
int main()
{std::cout x \n;return 0;
}指令在编译之前被解析从上到下逐个文件地解析。
考虑下面程序:
#include iostreamvoid foo()
{
#define MY_NAME Alex
}int main()
{std::cout My name is: MY_NAME \n;return 0;
}尽管看起来*#define MY_NAME “Alex”是在函数foo内定义的但预处理器并不理解函数等 C 概念。因此该程序的行为与在函数foo之前或之后定义#define MY_NAME “Alex” 的程序相同。为了避免混淆你通常需要在函数外部#define 标识符。
由于 #include 指令将 #include 指令替换为包含文件的内容因此 #include 可以将指令从包含文件复制到当前文件中。然后将按顺序处理这些指令。
例如以下示例的行为也与前面的示例相同
Alex.h
#define MY_NAME Alexmain.cpp
#include iostream
#include Alex.hint main()
{std::cout My name is: MY_NAME \n;return 0;
}在一个文件中定义的指令不会对其他文件产生任何影响除非它们被 #included 到另一个文件中。例如
function.cpp
#include iostreamvoid doSomething()
{
#ifdef PRINTstd::cout Printing!\n;
#endif
#ifndef PRINTstd::cout Not printing!\n;
#endif
}Main.cpp
#include iostreamvoid doSomething();#define PRINT
int main()
{doSomething();return 0;
}尽管 PRINT 是在main.cpp中定义的但这不会对function.cpp中的任何代码产生任何影响PRINT 仅是从定义点到 main.cpp 末尾的#define)。 下篇预告:头文件