C/C++ #include

本文详细解释了C语言中#include指令的作用,包括在UNIX和IDE中的查找逻辑,以及为什么需要包含文件头。讨论了头文件的使用,如声明常量、宏、函数、结构模板和类型定义,以及如何管理文件作用域和变量共享。

#include的作用

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

#include指令有两种形式:

`#include <stdio.h>` ←文件名在尖括号中
`#include "mystuff.h"` ←文件名在双引号中

#include在UNIX的判断逻辑

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

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

#include在IDE的判断逻辑

集成开发环境(IDE)也有标准路径或系统头文件的路径。许多集成开
发环境提供菜单选项,指定用尖括号时的查找路径。

在 UNIX 中,使用双引号意味着先查找本地目录,但是具体查找哪个目录取决于编译器的设定。有
些编译器会搜索源代码文件所在的目录,有些编译器则搜索当前的工作目录,还有些搜索项目文件所在的目录。

ANSI C不为文件提供统一的目录模型

ANSI C不为文件提供统一的目录模型,因为不同的计算机所用的系统不同。一般而言,命名文件的方法因系统而异,但是尖括号和双引号的规则与系统无关。

为什么要包含文件

为什么要包含文件?因为编译器需要这些文件中的信息。例如,stdio.h
文件中通常包含EOF、NULL、getchar()和 putchar()的定义。getchar()和
putchar()被定义为宏函数。此外,该文件中还包含C的其他I/O函数。

C语言习惯用.h后缀表示头文件,这些文件包含需要放在程序顶部的信
息。头文件经常包含一些预处理器指令。有些头文件(如stdio.h)由系统提
供,当然你也可以创建自己的头文件。

包含一个大型头文件不一定显著增加程序的大小。在大部分情况下,头
文件的内容是编译器生成最终代码时所需的信息,而不是添加到最终代码中
的材料。

头文件示例

假设你开发了一个存放人名的结构,还编写了一些使用该结构的函数。
可以把不同的声明放在头文件中:
程序names_st.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类型。
程序 name_st.c源文件:

// 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')
		continue; // 处理输入行中的剩余字符
	}
	return ret_val;
}

get_names()函数通过s_gets()函数调用了fgets()函数,避免了目标数组溢
出。

程序useheader.c程序

// 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.cuseheader.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 限定符的变量或数组。const 防止值被意外修改,static 意味着每个包含
该头文件的文件都获得一份副本。因此,不需要在一个文件中进行定义式声
明,在其他文件中进行引用式声明。

#include和#define指令是最常用的两个C预处理器特性。

参考资料

《C Primer Plus》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员-薯片

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值