之前我们说的预处理器命令都是无条件执行的,而预处理器也支持有条件的复合命令,也就是有条件地根据常量的值选择替换的预处理器命令。当然,由于是预处理器命令,在正式编译之前所有的替换都已经执行好了,因此条件也必须是一个常量表达式。
条件编译的命令有#if、#ifdef、#ifndef、#else、#elif、#endif和defined。下面我们通过条件编译的常见应用来逐个介绍这些命令。
条件编译首先适合编写跨平台代码,我们可以通过判断一些预设的值是否被定义来决定使用哪个版本的代码。
动手写3.6.9
动手写3.6.9展示了条件编译在跨平台程序中的大致用法。运行结果如图3.6.8所示:
图3.6.8 跨平台程序
在跨平台程序中,我们一般会在某个头文件或配置文件中集中放上平台的信息以及其他的一些配置选项,这些信息在某个平台上是永远不变的,因此在预处理阶段就能确定。有了这些常数或者定义的宏名之后,我们就可以用条件编译命令编写不同版本的代码了。
在这里,我们使用#define的时候并没有指定符号代表的语义,这是完全可以的,因为我们使用defined()就是要判断这个符号是否存在,至于它代表什么,我们不需要关心。#if后面跟任意的常量表达式,也可以是1>0这样的。因为我们并没有定义“VS2015”,#if后面的条件不满足,所以预处理器就忽略后面的一行代码。接着看#elif后面的条件(“elif”是“else if”的缩写),如果所有条件都不满足,就会保留#else后面的代码。最后的#endif是条件编译必需的终止符,如果没有#endif,预处理器就不知道后面的代码是不是只有#else的时候才保留了。
条件编译的另一个重要作用是避免重复包含头文件。如果在A.h中包含B.h,然后B.h包含C.h,最后C.h又包含了A.h,这样A.h在间接包含C.h的同时又一次包含了自己,并且编译器还会继续扩展包含,也就是说A.h中所有的声明定义都在同一个文件中至少出现两次。虽然extern变量可以重复出现,但头文件也可能有像类定义这样不能在同一个文件中出现两次的内容,这样的显示是很糟糕的,特别是在大型程序中,我们甚至都不知道包含的文件会间接包含多少其他头文件。为了解决这个问题,我们需要使用条件编译,确保每个头文件中的内容在#include展开以后都只出现一次。
动手写3.6.10(www.xing528.com)
动手写3.6.10展示了如何利用条件编译来避免重复包含。我们在头文件中加上几句条件编译命令,#ifndef是“if not defined”的缩写,#ifndef M3_6_10_1等价于#if !defined(M3_6_10_1),这里的意思就是如果没定义这个宏,我们就对这个宏进行定义。对于这个例子将会产生如下的步骤:
1.3.6.10_main.cpp会第一次包含3.6.10_2.h,M3_6_10_2会被定义。
2.接下来3.6.10_2.h中包含的3.6.10_1.h会被展开。
3.在3.6.10_1.h中M3_6_10_1会被定义。
4.3.6.10_1.h中包含的3.6.10_2.h会被展开。
5.因为M3_6_10_2已经定义,所以展开结束,预处理器继续处理3.6.10_main.cpp中剩余的预处理器命令。
虽然这些步骤看起来有些复杂,但理解的关键就是懂得#include的含义,以及预处理器处理每个文件的时候都要一层层地将所有#include展开到底。
注意每个头文件中定义的宏名一般采用文件名的大写,这里由于名称开头不能用数字以及特殊符号,我们做了一些修改。此外,还有一个非常简便的方式可以避免重复包含,那就是在头文件开头加上一行“#pragma once”。
最后,条件编译的另一个常用的用法是充当注释。在我们调试或修改代码的时候可以简单地用#if 0和#endif来框住代码段,因为条件永远不成立,所以就相当于注释了。如果要临时再激活代码,我们只需要把#if 0改成#if 1就可以了。当然我们也要记得在不需要的时候把这两行删掉,以避免不必要的麻烦。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。