我有一个看起来相对简单的问题,但却一直在努力去理解它。
如果这是一个简单的问题我道歉,但就像许多简单的问题一样,我似乎无法在任何地方找到可靠的解释。
使用以下代码:
/*foo.c*/
#include "bar.h"
int main() {
return(my_function(1,2));
}
/*bar.h*/
int my_function(int,int);
/*bar.c*/
#include "bar.h" /*is this necessary!?*/
int my_function(int x, int y) {
return(x+y);
}
简单来说,第二次包容是否必要?我不明白为什么我一直看到两个源文件中都包含标题。当然,如果函数是在" foo.c"通过包括" bar.h,"它不需要在另一个链接的源文件中第二次声明(特别是实际定义它的那个)?一位朋友试图向我解释说,它对于功能并不重要,但它确实对结构有用,这仍然是我的事!救命啊!
是否只是为了清晰起见,以便程序员可以看到外部使用哪些功能?
我只是不明白!
谢谢!
答案 0 :(得分:2)
在这种特殊情况下,由于您所描述的原因,它是不必要的。在您拥有可能完全相互依赖的更复杂功能集的情况下,它可能很有用。如果您在.cpp
文件的顶部包含标题,则您已经有效地向前声明了每个函数,因此您不必担心确保函数定义按特定顺序排列。< / p>
我还发现它清楚地表明这些函数定义对应于那些声明。这使读者更容易找到翻译单元如何相互依赖。当然,文件的名称可能就足够了,但是一些更复杂的项目在.cpp
文件和.h
文件之间没有一对一的关系。有时标题被分解为多个部分,或者许多实现文件将在单个标题中声明它们的外部函数(对于大型模块是常见的)。
真的,所有包含都是不必要的。毕竟,您可以在所有需要它们的文件中复制声明(或类的情况下的定义)。我们使用预处理器来简化此任务并减少冗余代码的数量。更容易坚持总是包含相应标题的模式,因为它始终有效,而不是每次编辑时检查每个文件,并确定是否有必要包含。
答案 1 :(得分:1)
C语言(和C ++)的设计方式是编译器单独处理每个.c文件。
您通常会为其中一个c文件启动编译器(例如cl.exe或gcc),这会生成一个目标文件(.o或.obj)。
生成所有目标文件后,运行链接器,将所有目标文件传递给它,然后将它们绑定到一个可执行文件中。
这就是每个.c文件需要包含它所依赖的标头的原因。当编译器正在处理它时,它对您可能拥有的其他 .c文件一无所知。它只知道你指向它的.c文件的内容,以及它包含的头文件。
答案 2 :(得分:0)
简单的规则就是这个(考虑到foo是某个类的成员函数): -
所以,如果某个头文件声明一个函数说:=
//foo.h
void foo (int x);
编译器需要在您定义此函数的任何地方看到此声明(以确保您的定义符合声明)并且您正在调用此函数(以确保您使用正确数量和类型的参数调用该函数)。
这意味着您必须在调用该函数的任何地方包含foo.h
以及您为该函数提供定义的位置。
此外,如果foo是一个全局函数(不在任何命名空间内),那么就不需要在实现文件中包含foo.h
。
答案 3 :(得分:0)
在简化示例中包含&#34; bar.h&#34; in&#34; bar.c&#34;没有必要。但在现实世界中,大多数情况下都是如此。如果你在&#34; bar.h&#34;和&#34; bar.c&#34;具有此类的功能,需要包含。如果您在&#34; bar.c&#34;中使用了任何其他声明。 - 它是一个常数,枚举等 - 再次包含是必要的。因为在现实世界中它几乎总是需要的,简单的规则是 - 始终在相应的源文件中包含头文件。
答案 4 :(得分:0)
如果头只声明全局函数,并且源文件只实现它们(不调用它们中的任何一个)那么它并不是绝对必要的。但通常情况并非如此;在大型程序中,您很少需要全局函数。
如果标题定义了一个类,那么您需要将它包含在源文件中以定义成员函数:
void Thing::function(int x) {
//^^^^^^^ needs class definition
}
如果头部在命名空间中声明函数,那么将定义放在命名空间之外是个好主意:
void ns::function(int x) {
//^^^^ needs previous declaration
}
如果参数类型与之前的声明不匹配,那么这将产生一个很好的编译时错误 - 您需要为其包含标题。在其命名空间中定义函数
namespace ns {
void function(int x) {
// ...
}
}
如果参数类型错误,将默默地声明一个新的重载。