1、具体是什么时候会用到条件编译呢?
前面介绍过,大致用在三类地方:
(1)文件内容被重复include时,去掉重包含的内容
(2)帮助我们的程序的跨平台
(3)辅助调试程序
1.1 文件内容被重复include时,去掉重包含的内容
我们以include 头文件来举例介绍,当一个C/C++工程文件写复杂后,在一个.c/.cpp中往往可能会包含十几个头文件,误操作使得同一个头文件重复包含多次,这其实是很正常的。
重复include的话,预编译器处理#include时,会把同一个头文件的内容重复的复制(包含)到.c中,但是我们知道头文件中往往会有struct ***等类型的定义,重复包含后,会导致在同一个.c中有重复的结构体类型定义,我们在第一章就说过,在同一个c文件中不能有重复的类型构体定义,第二阶段编译时,会报重复定义的错误。
但是实际上我们在重复inlcude同一头文件时,并没有报错误,那是因为头文件中加入了#ifndef条件编译,所以说#ifndef其中有一个很重要的用途就是用于防止文件被被重复包含。
(1)以C标准库的头文件stdio.h头为例
1)windows下stdio.h
#ifndef _STDIO_H_ #define _STDIO_H_ //头文件的内容 ... #endif //头文件的结尾
2)Linux下的stdio.h头文件
#ifndef _STDIO_H #define _STDIO_H 1 //头文件的内容 ... #endif
疑问:都是stdio.h,但是它们的宏咋不一样呢,一个叫_STDIO_H_,_STDIO_H?
答:如果你仔细阅读这两个stdio.h的话,你会发现这它们里面的内容也不一样,这个并不奇怪,说明它们是由不同团队编写的,一个是用在windows这边,另一个是用在Linux这边。
尽管二者内容有所区别,但是所要实现的事情都是一样的,不然的话你在Liunx下c程序调用了printf函数,结果这个程序到了Windows就不能调用printf了,这就扯淡了。
疑问:宏的头尾怎么都带_呢?
这个是库、OS的标识符的命名习惯,目的是方便识别,因为你一看到带_打头和结尾你就知道,那一定是库、或者OS等标识符。
我们自己的应用程序尽量不要使用这种命名格式,以便与库、OS、框架等的标识符进行区别。
3)使用#ifndef来防止文件重复包含的原理
举例理解,比如:
a.c
#include <stdio.h> #include <stdio.h> int main() { ... }
(a)第一步:预编译器到相关路径找到include所指定的.h文件,然后将所有.h文件中的内容复制到a.c中,替换掉#include <***.h>
注意,重复的.h的内容会被全部复制到.c中。
a.c
/* stdio.h */ #ifndef _STDIO_H_ #define _STDIO_H_ 头文件的所有内容 #endif /* stdio.h */ #ifndef _STDIO_H_ #define_STDIO_H_ 头文件的所有内容 #endif int main() { ... }
(b)第二步:处理条件编译,去掉重复的内容
#ifndef的原理很简单,其实就是先重复包含内容,然后再通过#ifndef将重复的内容去掉。
4)说说“防止头文件内容重复宏”的命名
(a)不同的头文件中该宏的宏名是不相同的
如果相同的话,就会导致其它头文件无法正常被包含。
比如:假如stdio.h和stdlib.h中“防止文件内容重复的宏相同的话。
/* stdio.h */ #ifndef _STDIO_H_ #define _STDIO_H_ ... #endif /* stdlib.h */ #ifndef _STDIO_H_ #define _STDIO_H_ ... #endif
当相同时,第一次包含stdio.h时会定义_STDIO_H_,第二次包含stdlib.h时发现这个宏已经被定义了,导致stdlib.h的内容被丢弃。
(b)如何防止宏名冲突呢?
每个头文件的文件名基本都会不相同的,所以只要你在宏名里面参入文件名,就一定不会重名。
比如对于我们自己写的头文件来说,可以按照如下方式命名这个宏。
比如:
student.h
#ifndef H_STUDENT_H #define H_STUDENT_H ... #endif
或者命名为:STUDENT_H_INCLUDED
你也可以有你自己的自定义命名方式,不管你怎么命名,只要名字中包含“文件名”,该宏不会重名。
当然,如果是使用IDE来创建头文件的话,IDE会自动根据你的头文件名字生成该宏,IDE也会提示你,你可以自己修改,但是如果你是使用存文本来编写.h的话,这个宏就需要我们自己定义了。