C 语言是一种先声明后使用的语言。

举个例子:

如果你要在 main() 函数里调用一个你的函数 foo(),那么你有两种写法:

  1. foo() 的定义写在 main() 之前。此时 foo() 的声明和定义是同时发生的:

    int foo() {
    ...
    } int main() {
    foo();
    }
  2. foo() 的定义写在 main() 之后。此时 foo() 的声明必须出现在被 main() 调用之前:

    int foo();
    
    int main() {
    foo();
    } int foo() {
    ...
    }

实际上,我们只要保证在 foo()main() 调用之前声明 foo() 就好了。无论是写法 1 还是写法 2,foo() 的声明都是在被 main() 调用之前发生的。


对于单文件 C 项目来讲这样还好说。然而当我们项目中的代码越来越多之后,把所有代码放到单个文件里会使我们的项目变得难以维护。这时我们就需要把代码拆分,把功能相近的代码放到一个文件里,功能不同的代码分别放到不同的文件里。这样有利于我们后期对项目的维护。

比如说,我可以在 main.c 文件中只写 main() 函数,而把我的其他函数写到一个单独的文件 foo.c 中,就像这样:

// main.c
int main() {
...
}
// foo.c
int foo1 {
...
} int foo2 {
...
}

然而这里有一个问题:我们如何在 main() 函数中调用 foo.c 文件中的函数呢?

首先有一个笨方法,就是你在调用 main() 函数之前手动加上 foo.c 文件中函数的声明:

int foo1();
int foo2(); int main() {
foo1();
foo2();
}

然后我们编译的时候,两个文件都要编译并链接:

cc -c main.c
cc -c foo.c

这将生成目标文件 main.ofoo.o,接下来我们再对这两个文件进行链接:

cc main.o foo.o -o program

这样就生成了可执行程序 program


如果我们只用到两个函数,这样也不算麻烦。然而现实中我们可能要调用成百上千个函数,这样一来这种方法就有些过于麻烦了。

那么有没有一种方法能一次声明所有函数?

首先我们介绍一下 #include 预处理指令。它的功能是将一个文件的内容插入到这个 #include 指令所在的位置。

那我们只要把 foo.c 文件的内容插入到 main.c 文件中 main() 函数之前的位置不就好了?就像这样:

#include "foo.c"

int main() {
foo1();
foo2();
}

我们让编译器对 main.c 文件进行预处理:

cc -E main.c

编译器输出的内容是这样的:

int foo1() {
...
} int foo2() {
...
} int main() {
foo1();
foo2()
}

可以看到,#include 指令将 foo.c 文件的内容原封不动地插入到了 main.c 中。

如果要构建项目,我们只需要编译 main.c 就够了。因为预处理阶段已经把 foo.c 的内容全部加入到 main.c 中了。

cc main.c -o program

这种方式就类似我们前面提到的方法 1 —— 将函数定义放在 main() 函数之前。

对于小项目来说,这种方式够用了。然而对于比较大的项目,这种方式有一个显著的缺点 —— 你会发现这种方式其实还是相当于把所有代码写入到了一个文件中。对于代码量大的项目,编译一个这样的文件可能相当耗时。并且你一旦对项目文件的任何部分做了改动,都要重新编译整个项目。显然这种方式不适合大型项目。

参考我们之前的做法,我们能不能只把函数声明的部分提取出来,然后把它们 includemain.c 文件中?这样 main.c 文件就只包含其他文件的声明部分,而不是全部代码。这样我们在编译的时候,就可以各个文件分别编译。如果其中某个文件发生了变动,我们只需要重新编译这个变动的文件,再重新链接即可。而链接的过程是比较快的。相比重新编译整个项目,显然这是更优的选择。

对于我们的这个例子,我们只需再创建一个 foo.h 文件,并将 foo.c 文件中所有函数的声明提取出来放入其中,这样我们只需在 main.c 文件中加入 #include "foo.h" 命令,就可以只将这些函数声明加入 main.c 文件,而不是全部代码。

我们把这种从源文件 foo.c 中提取函数声明组成的文件 foo.h 叫做头文件(header)。

// main.c
#include "foo.h" int main() {
foo1();
foo2();
}
// foo.h
int foo1();
int foo2();
// foo.c
#include "foo.h" int foo1() {
...
} int foo2() {
...
}

在这里你看到源文件 foo.c 也包含了其自身的头文件 foo.h,是因为在实际应用中头文件往往不止包括函数声明,也包括结构体声明、常量定义等源文件也必须用到的信息。因此在实际应用中源文件也常常包括其自身的头文件。

此时我们让编译器对 main.c 文件进行预处理:

cc -E main.c

就会看到 main.c 文件只包含了 foo.c 文件中函数的声明:

int foo1();
int foo2(); int main() {
foo1();
foo2();
}

让编译器对 foo.c 文件进行预处理:

cc -E main.c

可以看到 foo.c 文件也包含了自己的函数声明:

int foo1();
int foo2(); int foo1() {
...
} int foo2() {
...
}

如果我们想要构建项目,需要分别编译 main.cfoo.c,最后再进行链接:

# 编译
cc -c main.c
cc -c foo.c # 链接
cc main.o foo.o -o program

实际上,使用头文件的好处远不止上面提到的这点。因此使用头文件是编程中的一个好习惯。

C 语言头文件作用的简单理解的更多相关文章

  1. [转载]C语言头文件的作用

    最近在工作当中遇到了一点小问题,关于C语言头文件的应用问题,主要还是关于全局变量的定义和声明问题.学 习C语言已经有好几年了,工作使用也近半年了,但是对于这部分的东西的确还没有深入的思考过.概念上还是 ...

  2. c语言头文件中定义全局变量的问题

    c语言头文件中定义全局变量的问题 (转http://www.cnblogs.com/Sorean/) 先说一下,全局变量只能定义在 函数里面,任意函数,其他函数在使用的时候用extern声明.千万不要 ...

  3. C语言头文件

    最近在工作当中遇到了一点小问题,关于C语言头文件的应用问题,主要还是关于全局变量的定义和声明问题.学习C语言已经有好几年了,工作使用也近半年了,但是对于这部分的东西的确还没有深入的思考过.概念上还是比 ...

  4. 嵌入式C语言头文件的建立与使用

    如何正确编写 C 语言头文件和与之相关联的 c 源程序文件,这首先就要了解它们的各自功能. 要理解 C 文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程. 一般说来编译器会做以下几 ...

  5. C语言头文件的使用(转载)

    C语言头文件的使用 ——by janders 转载请注名作者和出处,谢谢! C语言中的.h文件和我认识由来已久,其使用方法虽不十分复杂,但我却是经过了几个月的“不懂”时期,几年的“一知半解”时期才逐渐 ...

  6. C语言头文件怎么写?(转载)

    ---恢复内容开始--- c语言头文件怎么写?我一直有这样的疑问,但是也一直没去问问到底咋回事:所以今天一定要把它弄明白! 其实学会写头文件之后可以为我们省去不少事情,可以避免书写大量的重复代码,还在 ...

  7. c语言头文件以及make注意事项

    c语言头文件以及make注意事项 头文件说明:自己定义的头文件和项目文件放在一起,注意使用""而不是使用<>,系统的头文件才使用<> 当main函数要调用其 ...

  8. C语言头文件到底是什么?

    C语言头文件到底是什么? 在C语言学习的时候总是会引入这样的语句#include <stdio.h>,书上解释说把stdio.h这个文件的全部内容直接插入到这个位置,然后再经过C语言的编译 ...

  9. 51单片机C语言学习笔记6:51单片机C语言头文件及其使用

    很多初学单片机者往往对C51的头文件感到很神秘,而为什么要那样写,甚至有的初学者喜欢问,P1口的P为什么要大写,不大写行不行呢?其实这个是在头文件中用sfr定义的,现在定义好了的是这样的 sfr P1 ...

  10. C++标准库头文件名字和C语言头文件名字的区别

    1.C++版本的C标准库头文件,一般是cname,而C语言头文件一般是name.h 2.命名为cname的头文件中定义的名字都是从std中来的,而如果是name.h则不是这样的. 3.与是用name. ...

随机推荐

  1. 最新最全的BMS/EMS/PCS六大国产“储能方案”,不信你全都看过!

    作为国内领先的嵌入式产品平台提供商,创龙科技在"能源电力"行业拥有超过1000家客户,接下来就让小编向大家分享创龙科技推出的BMS/EMS/PCS"六大储能方案" ...

  2. 继续我们的复习之路——webapi

    前面断更几天是因为在住安心复习DOM BOM的内容 不得不说 还得是DOM 在这一章节的复习内容中  涌现出了很多又代表意义 经典的一些小案例 而且 还是有些难度的 有一两个我反正是自己独立完成不了 ...

  3. 深度学习领域的名词解释:SOTA、端到端模型、泛化、RLHF、涌现 ..

    SOTA (State-of-the-Art) 在深度学习领域,SOTA指的是"当前最高技术水平"或"最佳实践".它用来形容在特定任务或领域中性能最优的模型或方 ...

  4. QEMU + Vscode + Arm Arch's Linux调试小记

    QEMU + Vscode + Arm Arch's Linux调试小记 ​ 前几天看到了一篇讲授如何调试ARM Linux内核的文章,这里现在记录一下调试ARM Linux内核的办法 下载QEMU ...

  5. 大语言模型的应用探索—AI Agent初探!

    前言 大语言模型的应用之一是与大语言模型进行聊天也就是一个ChatBot,这个应用已经很广泛了. 接下来的一个应用就是AI Agent. AI Agent是人工智能代理(Artificial Inte ...

  6. IDEA 设置自动去掉不用的import

  7. yb课堂 搭建node环境和npm安装 《二十六》

    搭建node环境和npm安装 什么是NodeJS? Node.js就是运行在服务端得JavaScript 什么是npm? nodejs的包管理工具,可以下载使用公共仓库的包,类似maven包安装分为本 ...

  8. 解决方案 | 将时间转换为毫秒bat

    @echo off setlocal enabledelayedexpansion rem 输入的时间 set "time_input=00:07:07.1" rem 解析时间 f ...

  9. [oeasy]python0129_unicode_中文字符序号_十三道大辙_字符编码解码_eval_火星文

    unicode 中文字符分类 回忆上次内容 字符集 从博多码 到 ascii 再到 iso-8859 系列 各自割据   如何把世界上各种字符统进行编码 unicode顺势而生不断进化 不过字符总量超 ...

  10. LLM-01 大模型 本地部署运行 ChatGLM2-6B-INT4(6GB) 简单上手 环境配置 单机单卡多卡 2070Super8GBx2 打怪升级!

    搬迁说明 之前在 CSDN 上发文章,一直想着努力发一些好的文章出来!这篇文章在 2024-04-17 10:11:55 已在 CSDN 发布 写在前面 其他显卡环境也可以!但是最少要有8GB的显存, ...