14.4 文件包含
#include指令使另一个文件的内容被编译,就像它实际出现于#include指令出现的位置一样。这种替换执行的方式很简单:预处理器删除这条指令,并用包含的内容取而代之。这样,一个头文件如果被包含到10个源文件中,它实际上被编译了10次。
提示:
这个事实意味着使用#include文件时会涉及一些开销,但基于两个十分充分的理由,我们不必担心这种开销。首先,这种开销并不是很大。如果两个源文件都需要同一组声明,把这些声明复制到每个源文件所花费的编译时间相差无几。同时,这个开销只是在程序被编译时才存在,对运行时效率并无影响。更为重要的是,把这些声明放于一个头文件中具有重要的意义。如果其他源文件还需要这些声明,就不必把这些副本逐一复制到这些源文件中,因此它们的维护任务也变得简单了。
提示:
当头文件被包含时,位于头文件内的所有内容都要被编译。这个事实意味着每个头文件只应该包含一组函数或数据的声明。和把一个程序需要的所有声明都放入一个巨大的头文件中相比,使用几个头文件,每个头文件用于某个特定函数或模块的声明的做法会更好一些。
/*
** 文件包含。
*/
#include <stdio.h>
#include <stdlib.h>
int main( void ){
return EXIT_SUCCESS;
}
/* 输出:

file_inclusion_code.s 编译文件内容如下:
.file "file_inclusion_code.cpp"
.def __main; .scl 2; .type 32; .endef
.text
.globl main
.def main; .scl 2; .type 32; .endef
.seh_proc main
main:
.LFB18:
pushq %rbp
.seh_pushreg %rbp
movq %rsp, %rbp
.seh_setframe %rbp, 0
subq $32, %rsp
.seh_stackalloc 32
.seh_endprologue
call __main
movl $0, %eax
addq $32, %rsp
popq %rbp
ret
.seh_endproc
.ident "GCC: (tdm64-1) 4.9.2"
*/
14.4.1 函数库文件包含
编译器支持两种不同类型的#include文件包含:函数库文件和本地文件。事实上,它们之间的区别很小。
函数库头文件包含下面的语法:
#include<filename>
对于filename,并不存在任何限制,不过根据约定,标准库文件以一个.h后缀结尾。
编译器通过观察由编译器定义的“一系列标准位置”查找函数库文件。你所使用的编译器的文档应该说明这些标准位置是什么,以及怎样修改它们或者在列表中添加其他位置。
/* Dev-C++ 编译器配置

*/
14.4.2 本地文件包含
下面是#include指令的另一种形式:
#include "filename"
标准允许编译器自行决定是否把本地形式的#include和函数库形式的#include区别对待。可以先对本地文件使用一种特殊的处理方式,如果失败,编译器再按照函数库头文件的处理方式对它们进行处理。处理本地文件的一种常见策略就是在源文件所在的当前目录进行查找,如果该头文件并未找到,编译器就像查找函数库头文件一样在标准位置查找本地头文件。
从技术上说,函数库头文件并不需要以文件的形式存储,但对于程序员而言,这并非显而易见。
可以在所有的#include语句中使用双引号而不是尖括号。但是,如果使用这种方法,有些编译器在查找函数库头文件时可能会浪费少许时间。对函数库头文件使用尖括号的另一个较好的理由是它能给读者提供一些信息。使用尖括号,下面这条语句
#include<error.h>
显然引用的是一个函数库头文件。如果使用下面这种形式:
#include"error.h"
就无法弄清楚这个和上面相同相同的文件到底是一个函数库头文件还是一个本地头文件。要想弄明白它究竟是那种类型,唯一的方法是检查执行编译过程的目录。
UNIX系统和Borland C编译器所支持的一种变体形式是使用绝对路径名(absolute pathname),它不仅指定文件的名字,还指定了文件的位置。UNIX系统中的绝对路径名以一个斜杠开头。
在MS-DOS系统中,它所使用的是反斜杠(而不是斜杠)。如果一个绝对路径名出现在任何一种形式的#include中,那么正常的目录查找就被跳过,因为这个路径指定了头文件的位置。
/*
** 文件包含。
*/
#ifndef HEADER_H
#define HEADER_H
#include <stdio.h>
int print_good_morning( void );
#endif
#ifndef HEADER_2_H
#define HEADER_2_H
#include <stdio.h>
int print_good_evening( void );
#endif
/*
** 验证本地文件包含。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "header.h"
#include "header\header_2.h" /*header表示当前目录下的文件夹,header_2.h表示该header文件夹下面的header_2.h头文件。*/
int main( void ){
print_good_morning();
printf( "\n" );
print_good_evening();
return EXIT_SUCCESS;
}
int print_good_morning( void ){
return printf( "%s", "good morning" );
}
int print_good_evening( void ){
return printf( "%s", "good evening" );
}
/* 输出:

*/
/* 注意,两个头文件#ifndef 后面的内容不能一样,否则就会造成后面的一个头文件中的函数编译不通过的问题。*/
14.4.3 嵌套文件包含
完全可以在一个将被其他文件包含中使用#include指令。
这些函数的原型放到一个头文件中,并且用#include指令包含到需要使用这些函数的源文件中。但是,每个使用I/O函数的文件必须同时包含stdio.h以获得EOF的声明。因为,包含这些函数原型的头文件也可能包含一条语句:
#include<stdio.h>
包含了这个头文件,就自动引入了标准I/O声明。
标准要求编译器必须支持8层的头文件嵌套,但它并没有限定深度的最大值。事实上,我们并没有很好的理由让#include指令的嵌套超过一层或两层。
提示:
嵌套#include文件的一个不利之处在于它使得我们很难判断源文件之间的真正依赖关系。有些程序,如UNIX的make使用工具,必须知道这些依赖关系以便决定当某些文件被修改之后,哪些文件需要重新编译。
嵌套#include文件的另一个不利之处在于一个头文件可能会被包含多次包含。为了说明这种错误,考虑下面的代码:
#include "x.h"
#include "x.h"
显然,这里文件被包含了两次。没人会故意编写这样的代码。但下面的代码:
#include "a.h"
#include "b.h"
看上去没有什么问题。如果a.h和b.h都包含一个嵌套的#include文件x.h,那么x.h在此处也同样出现了两次,只不过它的形式不是那么明显而已。
/*
** x.h
*/
#include <stdio.h>
#include <stdlib.h>
int print_hello_world( void );
/*
** a.h
*/
#include <stdio.h>
#include <stdlib.h>
#include "x.h"
/*
** b.h
*/
#include <stdio.h>
#include <stdlib.h>
#include "x.h"
/*
** 嵌套文件包含。
*/
#include <stdio.h>
#include <stdlib.h>
#include "a.h"
#include "b.h" /* x.h is included twice */
int main( void ){
printf( "EOF = %d\n", EOF );
print_hello_world();
return EXIT_SUCCESS;
}
/*
** this is a bad habit, but the main goal of program is to show how file is included.
** a good habit should put achievement into corresponding source file, not into drivers.
*/
int print_hello_world( void ){
return printf( "%s", "hello, world" );
}
/* 输出:

*/
多重包含在绝大多数情况下出现于大型程序中,它往往需要使用很多头文件,因此要发现这种情况并不容易。要解决这个问题,可以使用条件编译。如果所有的头文件都像下面这样编写:
#ifndef _HEADERNAME_H
#define _HEADERNAME_H 1
/*
**All the stuff that you want in the header file。
*/
#endif
那么,多重包含的危险就被消除了。但头文件第一次被包含时,它被正常处理,符号_HEADERNAME_H被定义为1。如果头文件被再次包含,通过条件编译,它的所有内容被忽略。符号_HEADERNAME_H按照被包含文件的文件名进行取名,以避免由于其他头文件使用
相同的符号而引起的冲突。
注意,前一个例子中的定义也可以写作
#define _HEADERNAME_H
它的效果完全一样。尽管现在它的值是一个空字符串而不是“1”,但这个符号仍然被定义。
但是,必须记住预处理器仍将读入整个头文件,即使这个文件的所有内容将被忽略。由于这种处理将拖慢编译速度,因此如果可能,应避免出现多重包含,不管它是否是嵌套的#include文件导致的。
/*
** headername.h
*/
#ifndef _HEADERNAME_H
#define _HEADERNAME_H 1
/*
**All the stuff that you want in the header file。
*/
#endif
/*
** headername_2.h
*/
#ifndef _HEADERNAME_2_H
#define _HEADERNAME_2_H "hello"
/*
**All the stuff that you want in the header file。
*/
#endif
/*
** 使用ifndef预处理命令。
*/
#include <stdio.h>
#include <stdlib.h>
#include "headername.h"
#include "headername_2.h"
int main( void ){
printf( "_HEADERNAME_H = %d\n", _HEADERNAME_H );
printf( "_HEADERNAME_2_H = %s\n", _HEADERNAME_2_H );
return EXIT_SUCCESS;
}
/* 输出:

*/
/* 在DEV-C++编译器中,把#define _HEADERNAME_2_H "hello"改写为#define _HEADERNAME_2_H 导致编译不通过。*/