#include 介绍

当预处理器发现 #include 指令时,会查看后面的文件名并把文件的内容包含到当前文件中,即替换源文件中的 #include 指令。这相当于把被包含文件的全部内容输入到源文件 #include 指令所在的位置。

include 指令有两种形式:

  1. #include <stdio.h> // 文件名在尖括号中
  2. #include "mystuff.h" // 文件名在双引号中

在 UNIX 系统中,尖括号告诉预处理器在标准系统目录中查找该文件双引号告诉预处理器首先在当前目录中(或文件名中指定的其他目录)查找该文件,如果未找到再查找标准系统目录

  1. #include <stdio.h> // 查找系统目录
  2. #include "hot.h" // 查找当前工作目录
  3. #include "/usr/biff/p.h" // 查找/usr/biff目录

集成开发环境(IDE)也有标准路径或系统头文件的路径。许多集成开发环境提供菜单选项,指定用尖括号时的查找路径。在 UNIX 中,使用双引号意味着先查找本地目录,但是具体查找哪个目录取决于编译器的设定。有些编译器会搜索源代码文件所在的目录,有些编译器则搜索当前的工作目录,还有些搜索项目文件所在的目录。
ANSI C 不为文件提供统一的目录模型,因为不同的计算机所用的系统不同。一般而言,命名文件的方法因系统而异,但是尖括号和双引号的规则与系统无关。

为什么要包含文件?因为编译器需要这些文件中的信息。例如, 文件中通常包含 EOF、NULL、getchar() 和 putchar() 的定义。getchar() 和 putchar() 被定义为宏函数。此外,该文件中还包含 C 的其他 I/O 函数。
C语言习惯用 .h 后缀表示头文件,这些文件包含需要放在程序顶部的信息。头文件经常包含一些预处理器指令。有些头文件(如stdio.h)由系统提供,当然你也可以创建自己的头文件。包含一个大型头文件不一定显著增加程序的大小。在大部分情况下,头文件的内容是编译器生成最终代码时所需的信息,而不是添加到最终代码中的材料。

头文件的使用

浏览任何一个标准头文件都可以了解头文件的基本信息。头文件中最常用的形式如下。
明示常量 —— 例如,stdio.h 中定义的 EOF、NULL 和 BUFSIZE(标准 I/O 缓冲区大小)。
宏函数 —— 例如,getc(stdin) 通常用 getchar() 定义,而 getc() 经常用于定义较复杂的宏,头文件 ctype.h 通常包含 ctype 系列函数的宏定义。
函数声明——例如,string.h 头文件(一些旧的系统中是 strings.h)包含字符串函数系列的函数声明。在 ANSI C 和后面的标准中,函数声明都是函数原型形式。
结构模版定义 —— 例如,标准 I/O 函数使用 FILE 结构,该结构中包含了文件和与文件缓冲区相关的信息。FILE 结构在头文件 stdio.h 中。
类型定义 —— 例如,标准 I/O 函数使用指向 FILE 的指针作为参数。通常,stdio.h 用 #define 或 typedef 把 FILE 定义为指向结构的指针。类似地,size_t 和 time_t 类型也定义在头文件中。

许多程序员都在程序中使用自己开发的标准头文件。如果开发一系列相关的函数或结构,那么这种方法特别有价值

另外,还可以使用头文件声明外部变量供其他文件共享。例如,如果已经开发了共享某个变量的一系列函数,该变量报告某种状况(如,错误情况),这种方法就很有效。这种情况下,可以在包含这些函数声明的源代码文件定义一个文件作用域的外部链接变量。

  1. // 该变量具有文件作用域
  2. int status = 0; // 在源代码文件
  3. // 可以在与源代码文件相关联的头文件中进行引用式声明:
  4. extern int status; // 在头文件中

这行代码会出现在包含了该头文件的文件中,这样使用该系列函数的文件都能使用这个变量。虽然源代码文件中包含该头文件后也包含了该声明,但是只要声明的类型一致,在一个文件中同时使用定义式声明和引用式声明没问题。
需要包含头文件的另一种情况是,使用具有文件作用域、内部链接和 const 限定符的变量或数组。const 防止值被意外修改,static 意味着每个包含该头文件的文件都获得一份副本。因此,不需要在一个文件中进行定义式声明,在其他文件中进行引用式声明。
#include 和 #define 指令是最常用的两个 C 预处理器特性。

疑问

include 包含的头文件在只会在链接使用到的函数声明和定义吗?没有用到的函数声明和定义不会链接?