1.10预处理器初探

预处理器可以被看作是编译程序时,在编译器之前运行的一个独立程序。预处理器工作时,只是简单地从头至尾扫描代码,寻找其中的预编译(也称预处理)指令。预编译指令是指以#开头,以换行符结尾(而不是分号)的一类特殊指令。预编译指令有多种,比如我们最常见的#include <…>。

预处理器通常比较“愚钝”,它并不懂得C++语法,只能在编译器运行之前对文本进行简单的处理,处理结果最终被发送给编译器。

Includes

#include这个指令前面已见到过多次,当你#include一个文件时,预处理器会将文件中的内容拷贝到#include所在的地方,当你有内容需要被多次放在不同的地方时这一点就很有用了,比如前面所提的前置声明。#include有两种形式,角括号和双引号,区别见前一节《头文件》

宏定义

#define可以用来定义宏,宏定义了一种替换规则,将一组输入序列(例如标识符)转换为替换他的输出序列(例如文本)。

宏有两种基本类型:类对象宏和函数宏。

函数宏的功能类似于函数,我们这里不会讨论它,因为函数宏的使用通常被认为是危险的,并且它可以做的所有事基本上都能用(内联)函数来完成。

类对象宏可以用下面两种方法之一定义:

#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.8节详细地讨论这种情况(以及为什么不应当这么用)。

不带替换文本的类对象宏

类对象宏也可以不带替换文本,例如:

#define USE_YEN

可以这么理解这种形式的宏:当预处理器遇到标识符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

因为PRINT_JOE已被宏定义,所以std::cout<<“Joe”<<std::endl;会被编译,而std::cout<<“Bob”<<std::endl;不会被编译。

#ifndef正好与#ifdef相反,它会使预处理器检查某个名称是否未被宏定义。

#ifndef PRINT_BOB

std::cout << “Bob” << std::endl;

#endif

程序将会打印“Bob”,因为PRINT_BOB未被宏定义。

条件编译在头文件保护符中常会使用(下一节内容)。

定义的范围

预编译指令会在编译之前被处理完成(由预处理器挨个文件从头至尾处理),预处理完成后,所有的指令都会被从文件中丢弃。这意味着这些指令只在从定义起到其所在文件结束这个范围内有效,一个文件中的预处理指令不会对同一项目中的其他文件产生影响。

看下面的例子:

function.cpp:

#include <iostream>
voiddoSomething()
{
#ifdef PRINT
std::cout<<"Printing!";
#endif
#ifndef PRINT
std::cout<<"Not printing!";
#endif
}

main.cpp:

voiddoSomething();
int main()
{
  #define PRINT
  doSomething();
  return0;
}

上述程序会打印:Not printing!  尽管PRINT在main.cpp中被定义了,但并不对function.cpp起作用。

最后,注意到头文件中定义的指令可以被#include到多个文件中,这提供了一种指令重用的方式,下一节我们会看到一个这样的例子。

转载请注明本文链接:http://www.icoder.top/blog/index.php/2016/09/19/110-a-first-look-at-the-preprocessor/