我知道如果一个源文件需要引用来自其他文件的函数,则需要包含其头文件,但我不明白为什么源文件需要包含它自己的头文件。头文件中的内容只是在编译前作为函数声明复制粘贴到源文件中。对于包含自己头文件的源文件,这样的“声明”对我来说似乎并不必要,实际上,在从其源文件中移除头文件后,该项目仍然可以编译和连接,那么源文件包含自己的头文件的原因是什么呢?
我知道如果一个源文件需要引用来自其他文件的函数,则需要包含其头文件,但我不明白为什么源文件需要包含它自己的头文件。头文件中的内容只是在编译前作为函数声明复制粘贴到源文件中。对于包含自己头文件的源文件,这样的“声明”对我来说似乎并不必要,实际上,在从其源文件中移除头文件后,该项目仍然可以编译和连接,那么源文件包含自己的头文件的原因是什么呢?
主要的好处是编译器可以验证您的头文件和其实现的一致性。您这样做是因为它很方便,而不是因为它是必需的。可能确实可以在不进行此类包含的情况下使项目编译和正确运行,但长期来看,这会使项目维护变得复杂。
如果您的文件没有包括自己的头文件,则可能会意外地遇到这种情况:函数的前向声明与函数的定义不匹配——可能是因为您添加或删除了参数,忘记更新头文件。当这种情况发生时,依赖于具有不匹配的函数的代码仍然可以编译,但调用将导致未定义的行为。最好让编译器捕获此错误,当源文件包含其自己的头文件时,这会自动发生。
.c
文件之间的关系是,头文件包含了 .c
文件定义的函数和全局变量的声明。例如,对于函数,头文件中包含原型,.c
文件中包含完整定义;编译器检查完整定义与编译时所见到的头文件中的原型具有相同的参数和返回类型。 - Gilles 'SO- stop being evil'实际例子 - 假设项目中有以下文件:
/* foo.h */
#ifndef FOO_H
#define FOO_H
double foo( int x );
#endif
/* foo.c */
int foo( int x )
{
...
}
/* main.c */
#include "foo.h"
int main( void )
{
double x = foo( 1 );
...
}
foo.h
中的声明与foo.c
中的定义不匹配;返回类型不同。根据foo.h
中的声明,main.c
调用foo
函数并假定它返回一个double
。
foo.c
和main.c
是分别编译的。由于main.c
按照foo.h
中的声明调用foo
,因此可以成功编译。由于foo.c
没有包含foo.h
,编译器不知道声明和定义之间的类型不匹配,因此也可以成功编译。double
值,但函数定义返回一个int
。这是一个问题,特别是如果这两种类型的大小不同。最好的情况是得到一个垃圾结果。foo.c
中包含foo.h
,编译器可以在运行程序之前捕获这个不匹配。foo.h
定义了foo.c
使用的任何类型或常量,则肯定需要包含它。头文件告诉人们源文件可以做什么。
因此,包含头文件的源文件需要知道它的职责。这就是为什么要包含它的原因。
typedefs
、structs
、enums
、inline
函数、宏等,这些内容被源代码和使用库的其他人所使用。 - tmlen这很有用,因为可以在定义之前声明函数。
所以通常会先声明函数,然后调用它,最后再实现函数。你不一定非要这样做,但是可以这样做。
头文件包含了函数的声明,只要原型匹配,就可以在任何时候调用。只要编译器在完成编译之前找到了实现。