文/Edward
之前我们已经讲到过,#include预处理指令使另外一个文件的内容在预处理的时候,先会被复制到当前的.c文件中来,产生一个中间文件,一般情况下,这个中间文件是存储在编译器内存里面的,它不会被输出,但是我们可以使用gcc命令来将这个文件存储下来,即使用” gcc -E ./helloworld.c > a.out”命令,即可将预处理产生的内容保留出来。如图1所示。
图1 预处理输出文件
我们在图1中,仅仅简单地包含了一个我们自己写的一个头文件,这个头文件里面有一个#define定义的标号MAX,也有一个变量a,当我们包含了这个头文件并且进行编译的时候,编译器便会将头文件里面的内容在编译流程的预处理阶段把.c源文件中的相关标号定义全部替换掉,如果.h头文件里面包含了变量的定义,便会将变量的定义原封不动地复制到.c源文件中。以上内容就是文件包含的全部内容。
然而#include预处理如果再深究下去,它的内容可不止如此。
库函数的包含和本地头文件的包含
前面我们也讲到过,用#include包含头文件的时候,根据包含的头文件到底是库函数还是用户自己写的本地头文件分别有不同的包含方式。
如果用#include包含库函数,那么其包含的方式为:
#include <filename>
对于filename不存在任何限制,不过根据约定,标准库的库文件以一个.h后缀结尾。编译器通过查找由编译器定义的“一系列标准库路径”来查找库函数的头文件。比如,在典型的UNIX/类UNIX系统上,C编译器在/user/include/目录中去查找库函数的头文件。
如果是本地头文件的包含,那么其包含方式为:
#include “filename”
这个头文件如果和所有的源文件放置在一个目录底下,那么就无需去显式地指明其路径。如果不在同一个文件夹下,那么UNIX和BorlandC编译器都支持一种变体形式,即使用绝对地址的路径名。如:
#include “../src/usart/ usart.h”
头文件的形式
再来想一想,C语言中的#include只能支持包含.h后缀的头文件吗?其实不然,除了C语言标准库文件在编写时是按照约定以.h结尾的,用户自己定义的头文件根本就没有这一条规定。比如图2所示。
图2 C语言包含乱七八糟后缀名的头文件
在图2中大家可以看到,我在b.c这个C语言源文件里面包含了一个c.x的头文件,这个c.x后缀名我自己也不知道是什么意思。但是奇怪的是,编译器在做预处理的时候,竟然将c.x文件中的内容正常的处理过来了,除了VSCode做代码静态解析的时候,有错误提示之外,其他貌似什么事情都没有发生。因此,在一个.c文件里面用#include包含什么后缀的文件,在C语言里面没有规定。只不过约定俗成,都用.h结尾的文件来当头文件而已。
但是,C语言中源文件的后缀名有明确要求,一定要用.c后缀的文件。
头文件里面的内容
接下来,最后一个内容,头文件里面到底可以用来写什么内容。这个问题非常玄乎,C语言也没有规定,随便你自己去写。我之前看到过有个哥们(一个半路出家的工程师)将函数全部定义在头文件中。对于这样的人才,我除了说他天资聪颖,资质过人,骨骼精奇这样的褒义词之外,根本就词穷了。C语言的头文件里面的的确确可以用来写函数,你没有听错,我再写一遍,的的确确可以用来写函数。但是,这样子写出来的函数很难被多个其它文件包含。这个问题我本来不想展开讲解,因为杀伤力太大,我怕将这个反面教材正面化了。但是满足大家的好奇,还是贴出来了,如图3所示。
图3 反面教材
![db4defe1824a518009b92c5a69c65ed6.png](https://i-blog.csdnimg.cn/blog_migrate/a9a62042d59ad3f1a3b14a122dc63021.jpeg)