C code, Header files

Sep. 26, 2022

Introduction

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

#include指令有两种形式:

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

在UNIX系统中:

  • 尖括号告诉预处理器在标准系统目录中查找该文件;
  • 双引号告诉预处理器首先当前目录中(或文件名中指定的其他目录)查找该文件,如果未找到再查找标准系统目录
1
2
3
#include <stdio.h>       //查找系统目录
#include "hot.h"         //查找当前工作目录
#include "/usr/biff/p.h" //查找/user/biff目录

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

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

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


Why Use Header Files?

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

C语言系统用.h后缀表示头文件,这些文件包含需要放在程序顶部的信息。头文件经常包含一些预处理器指令。

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


Example

比如开发一个存放人名的结构,还编写了一些使用该结构的函数,我们就可以把不同的声明放在头文件中。

例如,创建一个头文件names_st.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//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防止多重包含头文件。

注意:这些内容是编译器在创建可执行代码时所需的信息,而不是可执行代码

可执行代码通常在源代码文件中,而不是头文件中。下面的name_st.h源代码文件中就包含了names_st.h头文件,因此编译器知道变量names的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// name_st.c -- 定义 names_st.h中的函数
#include <stdio.h>
#include "names_st.h"

//函数定义
void get_names(names * pn)
{
    printf("Please enter your fist 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;
}

之后,创建下面的useheader.c源代码文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// useheader.c -- 使用 names_st 结构

#include <stdio.h>
#include "names_st.h"

int main(void)
{
    names candidate;

    get_names(&candidate);
    printf("Let's welcome ");
    show_names(&candidate);
    printf(" to this program!\n");
    return 0;
}

运行:

1
2
3
Please enter your fist name:Tommy
Please enter your last name:Shelby
Let's welcome Tommy Shelby to this program!

该程序需要注意以下几点:

  • 两个源代码文件name_st.huseheader.c都是用names_st类型结构,所以它们都必须包含names_st.h头文件;
  • 必须编译和链接names_st.cuseheader.c源代码文件;
  • 这个示例这是一个很经典的例子:声明和指令放在nems_st.h头文件中,函数定义放在names_st.c源文件中,主程序放在useheader.c文件中。

参考

[1] Prata S. C Primer Plus(6th Edition)[M]. 姜佑, 译. 北京: 人民邮电出版社, 2018.