0 为什么要添加头文件?为什么要使用头文件?
可以看下下面图片左边,是不使用头文件,假设我们为了实现某些功能,编写的函数,全部声明在主函数之前,写几个函数还行,如果是大型项目,哇,那么多函数全部写在一个文件里面,可能是有的是控制电源,有的是控制发送文字,这怎么后期管理啊
所以最好还是像右边一样,我们把这些自己编写的特定功能的函数(自己造的小轮子)拿出来,函数的定义,将功能类似的统一放在一个.c文件中,然后函数声明放在同名的.h文件中
然后在其他文件中或者包含main函数的文件中,#include添加对应的头文件即可完成在这个源文件中使用这个头文件对应的函数
1 基本概念
概念 | 头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享。 有两种类型的头文件:程序员编写的头文件和编译器自带的头文件。 在程序中要使用头文件,需要使用 C 预处理指令 #include 来引用它。前面我们已经看过 stdio.h 头文件,它是编译器自带的头文件。 引用头文件相当于复制头文件的内容,但是我们不会直接在源文件中复制头文件的内容,因为这么做很容易出错,特别在程序是由多个源文件组成的时候。 A simple practice in C 或 C++ 程序中,建议把所有的常量、宏、系统全局变量和函数原型写在头文件中,在需要的时候随时引用这些头文件。
|
2 使用方法
使用方法 <>或者“ ” | #include <you_use.h>
|
3 #include的过程实际是什么呢?
https://zhuanlan.zhihu.com/p/49543172
Linux:gcc工具_gcc -o-CSDN博客
请参考阅读这两个文章,会发现,#include 头文件后,实际上在预处理过程,就会把这些函数定义再拷贝一份到源文件。系统帮忙自动完成这个过程。
4 头文件中还可以包含多个头文件
一个xxx.h头文件中,还可以添加一个或者多个头文件,不止是可以添加在源文件中
有了前面对于#include 是"拷贝"的理解,应该不难理解为什么.h文件还可以继续放在.h文件中了
不再多做解释
5 防止编译多次的#ifndef用法
只引用一次头文件
如果一个头文件被引用两次,编译器会处理两次头文件的内容,这将产生错误。为了防止这种情况,标准的做法是把文件的整个内容放在条件编译语句中,如下:
#ifndef HEADER_FILE
#define HEADER_FILE
the entire header file file
#endif
这种结构就是通常所说的包装器 #ifndef。当再次引用头文件时,条件为假,因为 HEADER_FILE 已定义。此时,预处理器会跳过文件的整个内容,编译器会忽略它。
备注:小节5这段拷贝字菜鸟教程:C 头文件 | 菜鸟教程
6 我的一些QA
Q: 假设有一个my_class.c已经有了一些函数的定义,把函数声明放在了my_class.h中,为什么还要在my_class.c里面加上#include ”my_class.h“呢?因为一般不是在其他需要调用到my_class.c的函数的源文件里面才添加这个头文件嘛?
A:在 my_class.c
文件中包含 #include "my_class.h"
有以下几个重要原因:
一、确保函数声明的一致性
-
编译器检查
- 当编译
my_class.c
文件时,编译器需要知道在这个文件中定义的函数的正确声明。虽然函数的定义在这个文件中,但编译器在处理每个源文件时是独立进行的。如果没有包含头文件,编译器可能无法确定函数的参数类型、返回值类型以及其他属性,从而导致编译错误。 - 例如,如果在
my_class.h
中声明了一个函数void myFunction(int arg);
,而在my_class.c
中定义了这个函数但没有包含头文件,编译器可能会认为这个函数的定义与其他地方的声明不一致,从而产生错误。
- 当编译
-
代码自洽性
- 包含头文件可以确保
my_class.c
文件中的函数定义与头文件中的声明一致。如果在头文件中修改了函数的声明,例如改变了参数类型或返回值类型,那么包含头文件可以确保在编译my_class.c
文件时能够检测到这种不一致性,并及时进行修正。 - 比如,如果在开发过程中决定修改
myFunction
的参数类型,从int
改为double
,那么在修改my_class.h
中的声明后,如果my_class.c
没有包含头文件,编译器可能不会检测到这个变化,从而导致在运行时出现错误。
- 包含头文件可以确保
二、方便代码维护和可读性
-
代码组织
- 包含头文件可以使
my_class.c
文件的结构更加清晰,易于维护。开发人员可以在头文件中查看函数的声明,了解这个模块提供的接口,然后在对应的.c
文件中找到函数的实现。这种分离有助于提高代码的可读性和可维护性。 - 例如,当其他开发人员需要查看或修改
my_class.c
中的代码时,他们可以首先查看my_class.h
头文件,了解这个模块的功能和接口,然后更容易地理解.c
文件中的实现代码。
- 包含头文件可以使
-
宏定义和类型定义
- 头文件中通常不仅包含函数声明,还可能包含宏定义、类型定义、结构体声明等。在
my_class.c
文件中包含头文件可以确保能够正确使用这些定义。 - 比如,如果在
my_class.h
中定义了一个宏#define MAX_VALUE 100
或者一个结构体typedef struct { int x; int y; } MyStruct;
,那么在my_class.c
文件中如果需要使用这些宏或结构体,就必须包含头文件。
- 头文件中通常不仅包含函数声明,还可能包含宏定义、类型定义、结构体声明等。在
总之,虽然在其他源文件中调用 my_class.c
中的函数时需要包含 my_class.h
头文件,但在 my_class.c
文件本身中也包含头文件是为了确保编译器能够正确处理这个文件中的函数定义,同时提高代码的可读性和可维护性。
Q:源文件的头文件一定要和他同名嘛?
A:在 C 和 C++ 中,源文件的头文件不一定要和它同名。
一、不同名的情况
-
反映功能而非文件名
- 头文件的名称通常是为了反映其包含的内容或功能,而不一定与对应的源文件名称直接相关。
- 例如,一个源文件名为
calculator.c
,它实现了各种数学计算功能,其头文件可以命名为math_operations.h
,更清晰地表达了头文件中所包含的函数和数据结构是与数学运算相关的。
-
通用头文件
- 有时候会有一些通用的头文件,它们可以被多个源文件包含,但并不与任何一个特定的源文件同名。
- 比如,一个项目中有多个源文件都需要进行错误处理,那么可以有一个通用的头文件
error_handling.h
,被不同的源文件如main.c
、data_processing.c
等包含。
-
模块化设计
- 在大型项目中,为了实现模块化设计,头文件的命名可能会遵循一定的命名规范,以反映模块的功能和层次结构,而不一定与源文件同名。
- 例如,一个图形处理软件项目可能有不同的模块,如
rendering
(渲染)、geometry
(几何)等。源文件rendering_module.c
对应的头文件可能是rendering.h
,源文件geometry_module.c
对应的头文件可能是geometry.h
。
二、同名的情况
-
约定俗成
- 在一些小型项目或简单的代码结构中,为了方便起见,人们可能会遵循源文件和头文件同名的约定。
- 例如,源文件
my_program.c
对应的头文件为my_program.h
。这种命名方式可以使开发者更容易找到对应的头文件和源文件。
-
清晰的对应关系
- 同名的头文件和源文件可以提供一种清晰的对应关系,特别是在项目结构比较简单的情况下,方便开发者理解代码的组织方式。
- 如果一个项目中有多个源文件和头文件,同名的方式可以让开发者更快地确定哪个头文件与哪个源文件对应。
总之,源文件的头文件名称可以根据项目的需求、代码结构和命名规范来确定,不一定与源文件同名。选择合适的头文件名称有助于提高代码的可读性、可维护性和可扩展性。