什么是#include指令
当预处理器发现#include 指令时,会查看后面的文件名并把文件的内容包含到当前文件中,即替换源文件中的#include指令。
这相当于把被包含文件的全部内容输入到源文件#include指令所在的位置。#include指令有两种形式:
#include <stdio.h> ←文件名在尖括号中
#include "mystuff.h" ←文件名在双引号中
在 UNIX 系统中,尖括号告诉预处理器在标准系统目录中查找该文件。 双引号告诉预处理器首先在当前目录中(或文件名中指定的其他目录)查找该文件,如果未找到再查找标准系统目录:
#include <stdio.h> ←查找系统目录
#include "hot.h" ←查找当前工作目录
#include "/usr/biff/p.h" ←查找/usr/biff目录
单引号是不行的
集成开发环境(IDE)也有标准路径或系统头文件的路径。许多集成开发环境提供菜单选项,指定用尖括号时的查找路径。在 UNIX 中,使用双引号意味着先查找本地目录,但是具体查找哪个目录取决于编译器的设定。有 些编译器会搜索源代码文件所在的目录,有些编译器则搜索当前的工作目录,还有些搜索项目文件所在的目录。
ANSI C不为文件提供统一的目录模型,因为不同的计算机所用的系统 不同。一般而言,命名文件的方法因系统而异,但是尖括号和双引号的规则与系统无关。
为什么要包含文件?因为编译器需要这些文件中的信息。例如,stdio.h 文件中通常包含EOF、NULL、getchar()和 putchar()的定义。getchar()和 putchar()被定义为宏函数。此外,该文件中还包含C的其他I/O函数。
C语言习惯用.h后缀表示头文件,这些文件包含需要放在程序顶部的信 息。头文件经常包含一些预处理器指令。有些头文件(如stdio.h)由系统提供,当然你也可以创建自己的头文件。
包含一个大型头文件不一定显著增加程序的大小。在大部分情况下,头文件的内容是编译器生成最终代码时所需的信息,而不是添加到最终代码中 的材料。
头文件示例
假设你开发了一个存放人名的结构,还编写了一些使用该结构的函数。 可以把不同的声明放在头文件中。
// names_st.h -- names_st 结构的头文件
// 常量
#include <string.h>
#define SLEN 32
// 结构声明
struct names_st
{
char first[SLEN];
char last[SLEN];
};
// 类型定义
typedef struct names_st names;
// 函数原型
void get_names(names *);
void show_names(const names *);
char * s_gets(char * st, int n);
该头文件包含了一些头文件中常见的内容:#define指令、结构声明、 typedef和函数原型。注意,这些内容是编译器在创建可执行代码时所需的信 息,而不是可执行代码。为简单起见,这个特殊的头文件过于简单。通常, 应该用#ifndef和#define防止多重包含头文件。我们稍后介绍这些内容。
可执行代码通常在源代码文件中,而不是在头文件中。例如该程序包含了names_st.h头文件,所以 编译器知道names类型。
// names_st.c -- 定义 names_st.h中的函数
#include <stdio.h>
#include "names_st.h" // 包含头文件
// 函数定义
void get_names(names * pn)
{
printf("Please enter your first name: ");
s_gets(pn->first, SLEN);
printf("Please enter your last name: ");
s_gets(pn->last, SLEN);
}
void show_names(const names * pn)
{
printf("%s %s", pn->first, pn->last);
}
char * s_gets(char * st, int n)
{
char * ret_val;
char * find;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n'); // 查找换行符
if (find) // 如果地址不是NULL,
*find = '\0'; // 在此处放置一个空字符
else
while (getchar() != '\n')
1221
continue; // 处理输入行中的剩余字符
}
return ret_val;
}
get_names()函数通过s_gets()函数调用了fgets()函数,避免了目标数组溢出。
// useheader.c -- 使用 names_st 结构
#include <stdio.h>
#include "names_st.h"
// 记住要链接 names_st.c
int main(void)
{
names candidate;
get_names(&candidate);
printf("Let's welcome ");
show_names(&candidate);
printf(" to this program!\n");
return 0;
}
下面是该程序的输出:
Please enter your first name: Ian
Please enter your last name: Smersh
Let's welcome Ian Smersh to this program!
该程序要注意下面几点。
两个源代码文件都使用names_st类型结构,所以它们都必须包含 names_st.h头文件。
必须编译和链接names_st.c和useheader.c源代码文件。
声明和指令放在nems_st.h头文件中,函数定义放在names_st.c源代码文 件中。
使用头文件
浏览任何一个标准头文件都可以了解头文件的基本信息。头文件中最常用的形式如下。
-
明示常量——例如,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类型也定义在头文件中。
许多程序员都在程序中使用自己开发的标准头文件。如果开发一系列相 关的函数或结构,那么这种方法特别有价值。
另外,还可以使用头文件声明外部变量供其他文件共享。例如,如果已经开发了共享某个变量的一系列函数,该变量报告某种状况(如,错误情况),这种方法就很有效。这种情况下,可以在包含这些函数声明的源代码文件定义一个文件作用域的外部链接变量:
int status = 0; // 该变量具有文件作用域,在源代码文件
然后,可以在与源代码文件相关联的头文件中进行引用式声明:
extern int status; // 在头文件中
这行代码会出现在包含了该头文件的文件中,这样使用该系列函数的文件都能使用这个变量。虽然源代码文件中包含该头文件后也包含了该声明, 但是只要声明的类型一致,在一个文件中同时使用定义式声明和引用式声明没问题。
需要包含头文件的另一种情况是,使用具有文件作用域、内部链接和 const 限定符的变量或数组。const 防止值被意外修改,static 意味着每个包含该头文件的文件都获得一份副本。因此,不需要在一个文件中进行定义式声明,在其他文件中进行引用式声明。