预处理器是一个很好的思想,即一个在程序编译之前提前独立运行的一段代码。当预处理器运行的时候,他会从code的头到尾寻找特殊指令。特殊指令一般指的是以#开头的,回车结束的代码(不是;号结尾)。有非常多的特殊指令符,下面会一一介绍。
预处理器并不是智能的,他不理解C++语法,她只是在编译器运行之前操作脚本,然后预处理器的结果输出给编译器。预处理器并不会修改代码,相反,预处理器做的所有操作都是存在临时内存里的。
include
之前你已经见过#include的预处理指令了,他就是将文件copy到引用的地方过来。当你需要在很多地方引用同一个文件的时候,这个预处理命令是非常有效的。
#inlcude云处理命令有两个格式:
#inckude <>
该格式表示include的是标准函数库里的文件,如C++标准库。
#include ""
该格式表示引用的是你自己写的头文件,一般情况下会检查当前文件,如果没有就去其他路经查找。
宏定义:
预处理器可以定义一个宏,宏定义了一个特定的输入序列(标识符)转化成特定的输出序列(文本文字)的规则。
有两种基本类型的宏:函数替换和对象替换
函数替换宏的功能类似于函数,也有类似的目的。我们不做讨论,而且他们的使用通常是危险的,而且都可以被内联函数替换。
对象替换宏有两种定义方式:
#define identifier
#define identifier substitution_text
上一行的声明并没有替换成文本,但是下一行替换了。因为这是宏声明,所以没有;号。
有替换文本的对象替换宏
当预处理器碰到该指令会把所有的对象替换成相应的文本,一般文本都是使用大写字母,下划线隔开。
看一下下面的例子:
#define MY_FAVORITE_NUMBER 9
std::cout << "My favorite number is: " << MY_FAVORITE_NUMBER << std::endl;
预处理器会将该行转换成:
std::cout << "My favorite number is: " << 9 << std::endl;
当执行的时候就会输出:My favorite number is 9
我们在2.9部分会详细讨论该种用法。
没有替换文本的对象替换宏:
如下面的例子:
#define USE_YEN
像这种格式的宏,你可能会想:一种将来出现的情况,标识符被删除了或者被任何其他的标识符替代了。
这个可能非常没有意义,因为宏是用来做文本替换的。然而这并不是这种格式的使用的地方,这个我们马上就会介绍。
与替换文本的宏一样,该种格式的宏通常也是被认可的。
条件编译:
条件编译预处理器指令允许你在特定情况下编译或者不编译一段代码。我们一般碰到的条件编译有#ifdef #ifndef #endif
#ifdef 表示让处理器检查后面定义的值是饭否被#define定义过,如果定义过,那么就会编译#ifdef 到 #endif之间的代码。如果没有定义,就不编译该部分。
参考下面的例子:
#define PRINT_JOE
#ifdef PRINT_JOE
std::cout << "Joe" << std::endl;
#endif
#ifdef PRINT_BOB
std::cout << "Bob" << std::endl;
#endif
上面的例子会输出:Joe。因为define了PRINT_JOE,所以走第一个分支。同样如果是下面的代码:
#define PRINT_JOE
#ifndef PRINT_JOE
std::cout << "Joe" << std::endl;
#endif
#ifndef PRINT_BOB
std::cout << "Bob" << std::endl;
#endif
该例子就会输出Bob,因为PRINT_BOB没有定义。
条件编译经常用到的地方是头文件警卫的作用,这个在下一节会详细介绍。
你可能会有疑惑:
#define PRINT_JOE
#ifdef PRINT_JOE
// ...
既然我们定义PRINT_JOE什么都没有,为什么在#ifdef中没有把PRINT_JOE替换成什么都没有呢?宏只会对普通代码起作用,其他的预处理器命令不做处理。因此#ifdef中的PRINT_JOE是独立的。
例如:
#define FOO 9 // Here's a macro substitution
#ifdef FOO // This FOO does not get replaced because it’s part of another preprocessor directive
std::cout << FOO; // This FOO gets replaced with 9 because it's part of the normal code
#endif
定义的范围:
指令在编译前得到解析,从文件的头到尾。一旦预处理器完成之后,所有的预处理指令就废弃了。
这意味着指令在定义到文件结束是有效的。在同一个代码中的指令不会对同项目中的其他文件产生影响。
考虑下面的例子:
function.cpp
#include <iostream>
void doSomething()
{
#ifdef PRINT
std::cout << "Printing!";
#endif
#ifndef PRINT
std::cout << "Not printing!";
#endif
}
main.cpp
void doSomething(); // forward declaration for function doSomething()
int main()
{
#define PRINT
doSomething();
return 0;
}
上面的例子会打印:Not printing! 因为main中定义的PRINT对function中是无效的。
最后,在一个头文件中的指令的定义可以被#include进很多文件中。这提供了一个方法,可以在一个地方提供指令,同时在多个文件中使用。下一节我们会看到很多例子。