新手程序员李雷现在对头文件已经比较了解了,他也深刻地理解了“函数和变量只在头文件中声明,然后在C文件中定义”的基本原则。

但是教授今天在海边钓了几条大鱼,心情很好,心情一好,就忍不住多教教李雷一些东西。

教授说:“你们现在作为新手,很多东西都是知其然,不知其所以然。今天我想告诉你的是,C语言编译器,其实并不知道什么是头文件,什么是C文件。”

李雷大吃一惊,摆出一副聆听的态度。

教授微笑道:“其实编译器只知道 #include 指令,而我们的所谓头文件,其实就是基于这个功能,从逻辑上作出的定义。这些个定义并不是为了满足编译器的要求,而是满足程序员作为人类,便于维护大型工程以及代码复用的要求。”

我们要透过现象看本质。

1: 对于编译器来说,它根本不在乎你有没有头文件。

2: 编译器也根本不在乎你的所有程序文件是什么后缀名,你把头文件命名为 aaa.x , 把c文件命名为 aaa.y 都没问题。它在乎的只是你使用命令行编译的时候,传入正确的参数就行了。

真正关心在乎这些的是程序员。

程序员利用编译器提供的最基本简单的 #include 指令功能,设计出了一堆规范和概念,以便于代码复用和维护。

#include 可以把任何文件包含进来,哪怕是在一个C程序中包含另一个C程序,只要包含进来以后的结果都满足C语言的规范即可。

我们再回顾一次编译器对 #include 指令的处理方式。

比如C程序中有一行 #include <stdio.h>

编译器读到这里的时候,本质上它就是把 stdio.h 的内容原封不动地替换进来。

编译器并不知道 stdio.h 是什么所谓的头文件。

stdio.h 是头文件,那只是我们给了它这个定义而已。

所以我们假设有如下两个极其粗糙的C程序:

a.c :

c语言编译器怎么检测(编译器并不知道什么头文件)(1)

b.c:

c语言编译器怎么检测(编译器并不知道什么头文件)(2)

你看, b.c 直接 #include 了 a.c

这个本质就是 把 b.c 和 a.c 合并成了一个文件然后让编译器来编译,完全没有问题。

所以你的编译命令行完全可以一句 cc b.c -o test 就可以生成名为 test 的可执行文件。

教授今天谈性很高,又继续说道:

在有些大工程的时候,我们甚至用 #include 来包含注释文件。

假设a.c里面有一个极其复杂的函数,光是函数前置的注释就高达二百多行,按传统方式我们这么写:

c语言编译器怎么检测(编译器并不知道什么头文件)(3)

这样的话,程序员每次要定位到函数体,都要卷半天的屏,尤其是远程使用vim之类的编辑器的时候。

聪明而又喜欢装逼的程序员就想了个办法,把那一堆注释存到同目录下一个专门的文件 super.comment 里面,所以 super.comment 的内容是:

/*

长达二百行的注释

*/

然后 b.c 修改成:

c语言编译器怎么检测(编译器并不知道什么头文件)(4)

是不是简洁了很多?

是不是逼格很高?

毕竟谁又能阻止一个程序员少年装逼呢?

李雷迷惑了,说:“在这里 #include “super.comment” 有什么卵用吗?那只是一堆注释而已,我还不如这样做:”

c语言编译器怎么检测(编译器并不知道什么头文件)(5)

教授哈哈大笑,说:“你个傻*,

首先, #include “super.comment” 指令是完全无害的。

其次,大多数IDE面对这句指令的时候,都会自动把 super.comment 识别为可点击的文件,你鼠标移上去一点,IDE就开个新窗口帮你打开这个注释文件,多方便啊。

最后, 这样显得逼格高呀!”

教授最后说道:“其实这些东西都不重要,我只是通过这些奇奇怪怪的知识来给你加深对 #include 指令,以及今天这个话题的的本质了解。你只有对编译器的本质了解得更多以后,才会提升到自由王国。在以后开发大型工程的时候,你才能灵活地根据实际情况对工程作出更好的设计。好了我要去钓鱼了,下次再聊。”

,