C 语言头文件作用的简单理解
C 语言是一种先声明后使用的语言。
举个例子:
如果你要在 main() 函数里调用一个你的函数 foo(),那么你有两种写法:
将
foo()的定义写在main()之前。此时foo()的声明和定义是同时发生的:int foo() {
...
} int main() {
foo();
}
将
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.o 和 foo.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() 函数之前。
对于小项目来说,这种方式够用了。然而对于比较大的项目,这种方式有一个显著的缺点 —— 你会发现这种方式其实还是相当于把所有代码写入到了一个文件中。对于代码量大的项目,编译一个这样的文件可能相当耗时。并且你一旦对项目文件的任何部分做了改动,都要重新编译整个项目。显然这种方式不适合大型项目。
参考我们之前的做法,我们能不能只把函数声明的部分提取出来,然后把它们 include 到 main.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.c 和 foo.c,最后再进行链接:
# 编译
cc -c main.c
cc -c foo.c
# 链接
cc main.o foo.o -o program
实际上,使用头文件的好处远不止上面提到的这点。因此使用头文件是编程中的一个好习惯。
C 语言头文件作用的简单理解的更多相关文章
- [转载]C语言头文件的作用
最近在工作当中遇到了一点小问题,关于C语言头文件的应用问题,主要还是关于全局变量的定义和声明问题.学 习C语言已经有好几年了,工作使用也近半年了,但是对于这部分的东西的确还没有深入的思考过.概念上还是 ...
- c语言头文件中定义全局变量的问题
c语言头文件中定义全局变量的问题 (转http://www.cnblogs.com/Sorean/) 先说一下,全局变量只能定义在 函数里面,任意函数,其他函数在使用的时候用extern声明.千万不要 ...
- C语言头文件
最近在工作当中遇到了一点小问题,关于C语言头文件的应用问题,主要还是关于全局变量的定义和声明问题.学习C语言已经有好几年了,工作使用也近半年了,但是对于这部分的东西的确还没有深入的思考过.概念上还是比 ...
- 嵌入式C语言头文件的建立与使用
如何正确编写 C 语言头文件和与之相关联的 c 源程序文件,这首先就要了解它们的各自功能. 要理解 C 文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程. 一般说来编译器会做以下几 ...
- C语言头文件的使用(转载)
C语言头文件的使用 ——by janders 转载请注名作者和出处,谢谢! C语言中的.h文件和我认识由来已久,其使用方法虽不十分复杂,但我却是经过了几个月的“不懂”时期,几年的“一知半解”时期才逐渐 ...
- C语言头文件怎么写?(转载)
---恢复内容开始--- c语言头文件怎么写?我一直有这样的疑问,但是也一直没去问问到底咋回事:所以今天一定要把它弄明白! 其实学会写头文件之后可以为我们省去不少事情,可以避免书写大量的重复代码,还在 ...
- c语言头文件以及make注意事项
c语言头文件以及make注意事项 头文件说明:自己定义的头文件和项目文件放在一起,注意使用""而不是使用<>,系统的头文件才使用<> 当main函数要调用其 ...
- C语言头文件到底是什么?
C语言头文件到底是什么? 在C语言学习的时候总是会引入这样的语句#include <stdio.h>,书上解释说把stdio.h这个文件的全部内容直接插入到这个位置,然后再经过C语言的编译 ...
- 51单片机C语言学习笔记6:51单片机C语言头文件及其使用
很多初学单片机者往往对C51的头文件感到很神秘,而为什么要那样写,甚至有的初学者喜欢问,P1口的P为什么要大写,不大写行不行呢?其实这个是在头文件中用sfr定义的,现在定义好了的是这样的 sfr P1 ...
- C++标准库头文件名字和C语言头文件名字的区别
1.C++版本的C标准库头文件,一般是cname,而C语言头文件一般是name.h 2.命名为cname的头文件中定义的名字都是从std中来的,而如果是name.h则不是这样的. 3.与是用name. ...
随机推荐
- Python 潮流周刊#58:最快运行原型的语言(摘要)
本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...
- 【C++】使用ort推理yolov10
[C++]使用ort推理yolov10 前言:由于笔者是编导专业,想玩玩yolo模型,搜来搜去全是python,所以在学会之后写一篇文章帮助和笔者同样情况的人 环境 Windows 10 C++17 ...
- MySQL ibdata1文件太大的解决办法
在MySQL数据库中,如果不指定innodb_file_per_table=1参数单独保存每个表的数据,MySQL的数据都会存放在ibdata1文件里,时间久了这个文件就会变的非常大. 下面是参考网上 ...
- [oeasy]python0116_文字的起源_苏美尔文明_楔形文字_两河流域
文字起源 回忆上次内容 上次回顾了西里尔字符的编码过程 KOI-7 KOI-8 ISO-8859 系列进行总结 字符扩展 ascii 共 16 种 由iso组织制定 从 iso-8859-1 到 ...
- [oeasy]python0101_尾声_PC_wintel_8080_诸神的黄昏_arm_riscv
尾声 回忆上次内容 回顾了 ibm 使用开放架构 用 pc兼容机 战胜了 dec 小型机 apple 个人电脑 触击牺牲打 也破掉了 自己 软硬一体全自主的 金身 借助了 各种 软硬件厂商的 力量 最 ...
- AT_arc111_a 题解
洛谷连接&Atcoder 链接 题目简述 给定两个数 \(n\) 和 \(m\),输出 \(\left\lfloor\frac{10^n}{m}\right\rfloor \bmod m\) ...
- C语言基础要点
C语言基础 C语言基础 C程序编译过程 C程序编译步骤 汇编语言 32关键字 数据类型 常量 size程序 类型限定 goto语句 指针 指针和字符串 作用域 函数 内存 进程内存结构 可执行文件结构 ...
- CCF 有趣的数
问题描述: 试题编号: 201312-4 试题名称: 有趣的数 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 我们把一个数称为有趣的,当且仅当: 1. 它的数字只包含0, 1 ...
- Jenkins如何使用CrumbIssuer防御CSRF攻击
1.CSRF(跨站请求伪造)概述 在讲解Jenkins的跨站请求伪造(CSRF)保护机制之前,让我们首先对CSRF这一安全威胁及其重要性进行简明扼要的概述. 1.1 CSRF(跨站请求伪造)的原理 ...
- 【H5】15 表单 其四 数据发送
一旦在客户端上验证了表单数据,就可以提交表单了. 并且,由于我们在上一篇文章中介绍了验证,因此我们准备提交! 本文着眼于用户提交表单时会发生什么-数据将流向何处,以及到达表单后如何处理? 我们还将研究 ...