【C语言】#ifdef和#endif条件编译
条件编译
- 条件编译
- 条件编译的格式
- 常见形式
- defined运算符
- #if的使用
- #if defined的使用
- #if和#elif命令
- #ifdef和#ifndef命令
- #if #endif的用法
- #ifdef #endif的用法
- 总结
条件编译
一般情况下,源程序中的所有行都参与编译。但有时希望对其中一部分内容只在满足一定条件下才进行编译,即对一部分内容指定编译条件,这就是“条件编译”。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。
条件编译命令指定预处理器依据特定的条件来判断保留或删除某段源代码。
例如,可以使用条件编译让源代码适用于不同的目标系统,而不需要管理该源代码的各种不同版本。
条件编译的格式
条件编译区域以#if
、#ifdef
或#ifndef
等命令作为开头,以#endif
命令结尾。条件编译区域可以有任意数量的#elif
命令,但最多一个#else
命令。
以#if开头的条件编译区域具有以下格式:
#if 表达式1
[程序段1]
[#elif 表达式2
程序段2]
......
[#elif 表达式n
程序段n]
[#else
程序段n+1]
#endif
- 语义和正常代码相同,如果表达式1成立,则编译程序段1,如果表达式2成立,则编译程序段2,否则编译程序段n+1
- 预编译指令中的表达式与C语言本身的表达式基本一致,如逻辑运算、算术运算、位运算等均可以在预编译指令中使用
- C语言的代码时先编译再执行,预编译指令时在编译之前进行处理,通过预编译进行宏替换、条件选择代码段,生成最后的待编译代码,最后进行编译
- 不能忘记#endif
预处理器会依次计算条件表达式,直到发现结果非0(即true)的条件表达式,预处理器会保留对应程序段内的源代码。如果找不到值为true的表达式,并且该条件编译区域中包含#else命令,则保留#else命令程序段内的代码。
程序段中可以包含任意C源代码,也可以包含更多的命令,包括嵌套的条件式编译命令。在预处理阶段结束时,没有被预处理器保留以用于后续处理的程序段会被全部删除。
常见形式
#ifdef 标识符
程序段1
#else
程序段2
#endif
作用:当标识符已经被定义过(一般是用#define
命令定义),则对程序段1
进行编译,否则编译程序段2
其中#else
部分也可以没有,即:
#ifdef
程序段1
#endif
在头文件中使用#ifdef和#ifndef是非常重要的,可以防止双重定义的错误。例如:在头文件aaa.h中定义了类aaa
class aaa{
};
如果两次#include "aaa.h"就会出错(不一定是直接两次,有可能两个不同的头文件中都包含了这个头文件),因为相同的类不能定义两次,所以需要修改aaa.h
有时,在b.h中#inclde "a.h"
,在c.h中#inclde "b.h"
且#inclde "c.h"
,这时,如果不用ifndef/endif
,就会包含两次a.h产生错误
#ifndef _aaa_
#define _aaa_
class aaa{
};
#endif
可以避免问题。因为如果已经包含某头文件,_aaa_就已经有定义,那么#ifndef条件为假,就不会执行#ifndef和#endif之间的程序段,不会再次执行对类aaa的定义了。
defined运算符
一元运算符defined可以出现在#if或#elif命令的条件中。形式如下:
defined 标识符
defined (标识符)
如果指定的identifier是一个宏名称(已被#define定义,并且未被#undef命令取消定义),则defined表达式会生成值1.否则,defined表达式会生成值0
defined运算符相对于#ifdef和#ifndef命令的优点是:可以在更大型的预处理器表达式中使用它的值。例如:
#if defined( _unix_ )&& defined( _GNUC_ )
#endif
大多数编译器会提供预定义宏,用来识别目标系统和编译器。因此,在unix系统中,通常预先定义好了宏_unix_,而GCC编译器则会预先定义好了宏_GNUC_。类似地,微软Windows平台上地Visual C编译器会自动定义好_WIN32和宏_MSC_VER。
#if的使用
#if 表达式
程序段
#endif
if后面接表达式,如果表达式成立,就会把后面的程序段编译进去(注意是编译不是执行)
#if defined的使用
#if defined (宏)
程序段
#endif
#if后面接一个宏,如果前面定义过这个宏,编译器就会编译程序段,如果没有定义过,就不会编译。不管该宏定义的是什么以及对不对。
#if和#elif命令
作为#if和#elif命令条件的表达式,必须是整数常量预处理器表达式。与普通的整数常量表达式的区别在于:
- 不能再#if或#elif表达式中使用类型转换运算符
- 可以使用预处理运算符defined
- 再预处理器展开所有宏,并计算完所有的defined表达式后,会使用字符o替换掉表达式中所有其他标识符或关键字
- 表达式中所有带符号值都具有intmax_t类型,并且所有无符号值都具有uintmax_t类型。字符常量也会受该规则的影响。intmax_t和unitmax_t定义在头文件stdint.h中。
- 预处理器会把字符常量和字符串变量中的字符与转义序列转换成字符集中对应的字符。然而,字符常量再预处理器表达式和在后期编译阶段是否具有相同的值,取决于实现版本。
#ifdef和#ifndef命令
通过#ifdef和#ifndef命令测试某个宏是否已被定义。
#ifdef 标识符
#ifndef 标识符
等同于:
#if defined (标识符)
#if !defined (标识符)
如果identifier不是宏名称,则#ifndef标识符后面的条件代码被保留。
#if #endif的用法
#ifdef #endif的用法
#ifdef和#endif必须成对使用。理论上可以出现在任何地方(头文件和实现文件中),通常为了放置头文件被多次包含,在头文件中使用是必须的:
#ifndef MY_HEAD_H //头文件开头,注意不要和其它头文件冲突
头文件声明
#endif
总结
把头文件的内容都放在#ifndef和#endif中。无论头文件会不会被多个文件引用,都加上条件编译。一般格式为:
#ifndef <标识>
#define <标识>
程序段
#endif
标识
理论上可以自由命名,但是每个头文件的这个标识
都应该是唯一的。标识的命名规则一般是头文件名全大写,前后加下划线,并把文件名中的.
也写成下划线。如:stdio.h
#ifndef _STDIO_H_
#define _STDIO_H_
程序段
#endif