我从来没有真正理解过为什么C ++需要一个单独的头文件,其功能与.cpp文件中的功能相同。这使得创建类和重构它们非常困难,并且向项目中添加了不必要的文件。然后就是必须包含头文件,但是必须显式检查它是否已经包含在内的问题。
C ++于1998年获得批准,为什么要这样设计?拥有单独的头文件有什么优点?
后续问题:
当我仅包含.h文件时,编译器如何查找其中包含代码的.cpp文件?它是否假定.cpp文件与.h文件具有相同的名称,或者实际上查看目录树中的所有文件?
我从来没有真正理解过为什么C ++需要一个单独的头文件,其功能与.cpp文件中的功能相同。这使得创建类和重构它们非常困难,并且向项目中添加了不必要的文件。然后就是必须包含头文件,但是必须显式检查它是否已经包含在内的问题。
C ++于1998年获得批准,为什么要这样设计?拥有单独的头文件有什么优点?
当我仅包含.h文件时,编译器如何查找其中包含代码的.cpp文件?它是否假定.cpp文件与.h文件具有相同的名称,或者实际上查看目录树中的所有文件?
Answers:
尽管头文件还有其他用途,但您似乎在询问将定义与声明分开的问题。
答案是C ++不需要。如果将所有内容都标记为内联(对于类定义中定义的成员函数而言,这始终是自动的),则无需进行分隔。您可以在头文件中定义所有内容。
您可能想分开的原因是:
如果您更笼统的问题是“为什么C ++与Java不一样?”,那么我不得不问,“为什么用C ++而不是Java?” ;-p
不过,更严重的是,原因是C ++编译器不能像javac可以做的那样,直接进入另一个翻译单元并弄清楚如何使用其符号。需要头文件来向编译器声明在链接时可以预期得到的内容。
所以 #include
是直文本替换。如果您在头文件中定义所有内容,则预处理器最终将为项目中的每个源文件创建一个巨大的副本并将其粘贴,然后将其馈送到编译器中。C ++标准于1998年获得批准这一事实与此无关,这是C ++的编译环境是如此紧密地基于C的事实。
转换我的评论以回答您的后续问题:
编译器如何查找包含代码的.cpp文件
它不会,至少在编译使用头文件的代码时不会。您要链接的函数甚至都不需要编写,不必介意编译器知道.cpp
它们将在哪个文件中。调用代码在编译时需要知道的所有内容都在函数声明中表示。在链接时,您将提供.o
文件列表,静态或动态库,并且有效的头文件保证了函数的定义将在某个地方。
有些人认为头文件是一个优点:
最终,标头系统是设计C时70年代的产物。那时,计算机的内存非常少,将整个模块都保留在内存中并不是一个选择。编译器必须从顶部开始读取文件,然后线性地遍历源代码。标头机制可以实现这一点。编译器不必考虑其他翻译单元,而只需要从头到尾读取代码。
C ++保留了该系统以实现向后兼容。
今天,这没有任何意义。它效率低下,容易出错且过于复杂。有更好的方法来分离接口和实现,如果那样的话是目标。
但是,针对C ++ 0x的建议之一是添加一个适当的模块系统,从而允许将类似于.NET或Java的代码编译为更大的模块,这些操作一次完成就没有头文件。该提议并未在C ++ 0x上有所作为,但我认为它仍属于“我们希望以后再做”类别。也许在TR2或类似产品中。
据我(有限的-我通常不是C开发人员)的理解,它植根于C。请记住,C不知道类或名称空间是什么,它只是一个长程序。另外,在使用函数之前必须先声明它们。
例如,以下应该给出编译器错误:
void SomeFunction() {
SomeOtherFunction();
}
void SomeOtherFunction() {
printf("What?");
}
错误应该是因为“ SomeOtherFunction未声明”,因为您在声明之前调用了它。解决此问题的一种方法是将SomeOtherFunction移到SomeFunction上方。另一种方法是首先声明函数签名:
void SomeOtherFunction();
void SomeFunction() {
SomeOtherFunction();
}
void SomeOtherFunction() {
printf("What?");
}
这使编译器知道:在代码中的某个地方,有一个名为SomeOtherFunction的函数,该函数返回void并且不接受任何参数。因此,如果您诱使试图调用SomeOtherFunction的代码,请不要慌张,而是去寻找它。
现在,假设您在两个不同的.c文件中具有SomeFunction和SomeOtherFunction。然后,您必须在Some.c中#include“ SomeOther.c”。现在,向SomeOther.c添加一些“私有”功能。由于C不知道私有函数,因此该函数也将在Some.c中可用。
这是.h文件出现的位置:它们指定要从.c文件“导出”的所有函数(和变量),这些文件可以在其他.c文件中访问。这样,您将获得类似公共/私有范围的信息。另外,您可以将此.h文件提供给其他人,而不必共享您的源代码-.h文件也适用于编译的.lib文件。
因此,主要原因实际上是为了方便起见,用于源代码保护以及在应用程序的各个部分之间进行一些解耦。
那是C。C ++引入了类和私有/公共修饰符,因此尽管您仍然可以询问是否需要它们,但C ++ AFAIK仍需要在使用它们之前声明函数。而且,许多C ++开发人员都是C开发人员,或者曾经是C开发人员,并将他们的概念和习惯转移到了C ++中-为什么要改变那些没有被破坏的东西?
第一个优点:如果没有头文件,则必须在其他源文件中包含源文件。当包含文件更改时,这将导致包含文件再次被编译。
第二个优点:它允许共享接口而无需在不同部门(不同的开发人员,团队,公司等)之间共享代码。
对头文件的需求源于编译器对于了解其他模块中的函数和/或变量的类型信息所具有的限制。编译的程序或库不包含编译器绑定到其他编译单元中定义的任何对象所需的类型信息。
为了弥补这一限制,C和C ++允许声明,并且可以在预处理器的#include指令的帮助下将这些声明包含在使用它们的模块中。
另一方面,像Java或C#这样的语言在编译器的输出(类文件或程序集)中包含绑定所需的信息。因此,不再需要维护模块的客户端要包括的独立声明。
绑定信息未包含在编译器输出中的原因很简单:在运行时不需要绑定信息(在编译时会进行任何类型检查)。这样只会浪费空间。请记住,C / C ++来自可执行文件或库的大小确实很重要的时代。
C ++旨在向C基础结构中添加现代编程语言功能,而无需不必要地更改与C语言无关的任何内容。
是的,在这一点上(第一个C ++标准出现10年后,它开始大量使用后20年),很容易问为什么它没有合适的模块系统。显然,当今正在设计的任何新语言都无法像C ++一样工作。但这不是C ++的重点。
C ++的要点是不断发展,是现有实践的平滑延续,仅添加新功能而不会(经常)破坏对其用户社区有效的工作。
这意味着与其他语言相比,它使某些事情(特别是对于开始新项目的人员)更加困难,并且使某些事情(尤其是对于维护现有代码的人们)更加轻松。
因此,与其期望C ++会变成C#(因为我们已经有了C#,这将是毫无意义的),何不只是为工作选择合适的工具呢?我自己,我努力用现代语言编写大量的新功能(我碰巧使用C#),而且我保留了大量现有的C ++,因为用C ++重写并没有真正的价值。所有。无论如何,它们集成得非常好,因此几乎没有痛苦。
嗯,C ++于1998年获得批准,但已使用了更长的时间,并且批准主要是为了确定当前用法,而不是强加结构。而且由于C ++基于C,并且C具有头文件,所以C ++也具有头文件。
头文件的主要原因是启用文件的单独编译,并最大程度地减少依赖性。
说我有foo.cpp,我想使用bar.h / bar.cpp文件中的代码。
我可以在foo.cpp中#include“ bar.h”,然后即使bar.cpp不存在,也可以编程并编译foo.cpp。头文件向编译器保证,bar.h中的类/函数将在运行时存在,并且具有需要了解的所有内容。
当然,如果在尝试链接程序时bar.h中的函数没有主体,则它将不会链接,并且会出现错误。
副作用是您可以在不透露源代码的情况下为用户提供头文件。
另一个是,如果您更改了* .cpp文件中的代码实现,但根本不更改标头,则只需要编译* .cpp文件,而不是使用它的所有文件。当然,如果您在头文件中放入了很多实现,那么它的用处就会减少。
它不需要具有与main中相同功能的单独头文件。仅当您使用多个代码文件开发应用程序并且使用先前未声明的函数时,才需要使用它。
这确实是一个范围问题。
C ++于1998年获得批准,为什么要这样设计?拥有单独的头文件有什么优点?
实际上,头文件在首次检查程序时变得非常有用,签出头文件(仅使用文本编辑器)可以使您大致了解程序的体系结构,这与其他语言不同,在其他语言中,您必须使用复杂的工具来查看类和他们的成员职能。
好了,您可以不用头文件来完美地开发C ++。实际上,一些大量使用模板的库并未使用头文件/代码文件范例(请参见boost)。但是在C / C ++中,您不能使用未声明的内容。解决该问题的一种实用方法是使用头文件。另外,您无需共享代码/实现即可获得共享界面的优势。而且我认为这不是C创建者预想的:使用共享头文件时,必须使用著名的:
#ifndef MY_HEADER_SWEET_GUARDIAN
#define MY_HEADER_SWEET_GUARDIAN
// [...]
// my header
// [...]
#endif // MY_HEADER_SWEET_GUARDIAN
那实际上不是语言功能,而是处理多重包含的一种实用方法。
因此,我认为创建C时,正向声明的问题被低估了,现在使用像C ++这样的高级语言时,我们必须处理这类事情。
给我们贫穷的C ++用户带来的另一个负担...
如果要让编译器自动找出其他文件中定义的符号,则需要强制程序员将这些文件放在预定义的位置(例如Java包结构确定项目的文件夹结构)。我更喜欢头文件。另外,您可能需要使用的库源,也可能需要某种统一的方式将编译器所需的信息放入二进制文件中。