为什么C ++需要一个单独的头文件?

我从来没有真正理解为什么C ++需要一个单独的头文件,其function与.cpp文件中的相同。 它使得创build类和重构它们变得非常困难,并且将不必要的文件添加到项目中。 然后有必要包含头文件的问题,但必须明确检查是否已包括在内。

C ++在1998年被批准了,为什么这样devise呢? 有一个单独的头文件有什么好处?


后续问题:

当我包含的是.h文件时,编译器如何find带有代码的.cpp文件? 它是否假定.cpp文件与.h文件具有相同的名称,还是实际查看目录树中的所有文件?

您似乎在询问如何将声明与定义分开,但头文件还有其他用途。

答案是C ++不需要“这个”。 如果将所有内联标记为内联(对于类定义中定义的成员函数来说这是自动的),则不需要分离。 你可以在头文件中定义一切。

你可能分开的原因是:

  1. 提高构build时间。
  2. 链接代码而不具有定义的来源。
  3. 避免标记“内联”的所有内容。

如果更一般的问题是“为什么C ++与Java不相同?”,那么我不得不问:“你为什么要编写C ++而不是Java?” ;-p

更严重的是,原因在于C ++编译器不能直接进入另一个翻译单元,并以javac能够和可以做到的方式找出如何使用它的符号。 头文件需要向编译器声明它可能在链接时可用。

所以#include是一个直接的文本replace。 如果你在头文件中定义了所有的东西,那么预处理器最终会在你的项目中创build一个巨大的拷贝和粘贴每个源文件,并将其提供给编译器。 C ++标准在1998年被批准的事实与此无关,事实上C ++的编译环境与C的编译环境非常接近。

转换我的意见,以回答您的后续问题:

编译器如何find带有代码的.cpp文件

它不,至less在编译使用头文件的代码的时候没有。 你所连接的函数甚至不需要被编写,不用.cpp编译器知道他们将要进入​​的.cpp文件。在编译时调用代码需要知道的一切都在函数声明中表示。 在链接时,您将提供一个.o文件列表,或静态或dynamic库,并且头文件实际上是一个承诺,函数的定义将在某处。

C ++是这样做的,因为C就是这样做的,所以真正的问题是C为什么这样做? 维基百科对此有所了解。

较新的编译语言(如Java,C#)不使用前向声明; 标识符将从源文件中自动识别,并直接从dynamic库符号中读取。 这意味着头文件是不需要的。

有些人认为头文件是一个优势:

  • 据称,它能够/强制/允许分离界面和执行 – 但通常情况并非如此。 头文件充满了实现细节(例如,一个类的成员variables必须在头文件中指定,即使它们不是公共接口的一部分),函数可以(通常是) 类声明中内联定义在头上,再次破坏这个分离。
  • 有时会说,因为每个翻译单元都可以独立处理,所以可以提高编译时间。 然而,在编译时,C ++可能是现存最慢的语言。 部分原因是同一个标题的许多重复包含。 大量的标题被多个翻译单元包括在内,要求它们被多次parsing。

最终,头部系统是70年代Cdevise时的一个神器。 那时,计算机的内存很less,而将整个模块保存在内存中不是一个select。 编译器必须开始读取顶部的文件,然后通过源代码线性进行。 标题机制启用了这一点。 编译器不必考虑其他翻译单元,只需从上到下阅读代码即可。

而C ++保留了这个系统的向后兼容性。

今天,这是没有意义的。 这是效率低下,容易出错和过于复杂。 如果是目标,那么有更好的方法来分离接口和实现。

但是,对于C ++ 0x的build议之一就是添加一个合适的模块系统,允许将代码类似于.NET或Java编译为更大的模块,所有这些模块都是一次性的,而且不需要头文件。 这个build议并没有削减C ++ 0x,但我相信它仍然在“我们很愿意做这个”类别。 也许在一个TR2或类似的。

对于我(有限 – 我不是一个C开发人员通常)的理解,这是植根于C.请记住,C不知道什么类或名称空间,这只是一个长期的程序。 而且,函数必须在使用之前声明。

例如,以下应该给出一个编译器错误:

 void SomeFunction() { SomeOtherFunction(); } void SomeOtherFunction() { printf("What?"); } 

错误应该是“SomeOtherFunction没有声明”,因为你在声明之前调用它。 解决这个问题的一种方法是通过移动SomeFunction上面的SomeOtherFunction。 另一种方法是首先声明函数签名:

 void SomeOtherFunction(); void SomeFunction() { SomeOtherFunction(); } void SomeOtherFunction() { printf("What?"); } 

这让编译器知道:看代码中的某处,有一个名为SomeOtherFunction的函数返回void,并且不带任何参数。 所以如果你试图调用SomeOtherFunction的代码,不要惊慌,而是去寻找它。

现在,想象你在两个不同的.c文件中有SomeFunction和SomeOtherFunction。 然后你必须在Some.c中包含“SomeOther.c”。 现在,给SomeOther.c添加一些“私人”function。 由于C不知道私有函数,所以该函数也可以在Some.c中使用。

这是.h文件进来的地方:它们指定了所有要从其他.c文件中可以访问的.c文件导出的函数(和variables)。 这样,你就可以获得像公共/私人范围的东西。 另外,你可以把这个.h文件给别人,而不必共享你的源代码 – .h文件也可以对编译后的.lib文件起作用。

所以主要的原因实际上是为了方便起见,保护源代码并在应用程序的各个部分之间有一些解耦。

这是C虽然。 C ++引入了Classes和private / public修饰符,所以虽然你仍然可以问是否需要,但C ++ AFAIK在使用之前仍然需要声明函数。 此外,许多C ++开发人员也是C开发人员,并将他们的概念和习惯接pipe到C ++ – 为什么改变不被破坏?

第一个优点:如果你没有头文件,你将不得不在其他源文件中包含源文件。 这将导致包含的文件在被包含的文件改变时被再次编译。

第二个优点:它允许共享接口,而不需要在不同的单元(不同的开发者,团队,公司等)之间共享代码。

C ++被devise为将现代编程语言特性添加到C基础架构中,而不会不必要地改变关于C语言的任何关于语言本身的东西。

是的,在这个时候(第一个C ++标准10年之后,开始严重使用20年后)很容易问为什么没有合适的模块系统。 显然,今天devise的任何新语言都不会像C ++那样工作。 但这不是C ++的要点。

C ++的重点在于进化,是现有实践的顺利延续,只是增加了新function,没有(经常)打破对用户群体适用的事情。

这意味着它会使一些事情变得更加困难(尤其是对于开始一个新项目的人来说),有些事情比其他语言更容易一些(尤其是那些维护现有代码的事情)。

所以,不要期望C ++变成C#(这对我们已经有了C#来说是毫无意义的),为什么不select正确的工具呢? 我自己,我努力用现代语言编写大量的新function(我碰巧使用C#),而且我有大量现有的C ++,因为在重写C ++时没有实际的价值所有。 无论如何,它们都很好地融合在一起,所以很大程度上是无痛的。

它不需要一个单独的头文件,其function与main相同。 如果您使用多个代码文件开发应用程序,并且使用之前未声明的函数,则只需要它。

这真是一个范围问题。

头文件的需要是由于编译器了解函数和/或其他模块中的variables的types信息而产生的。 编译的程序或库不包含编译器所需的types信息,以便绑定到在其他编译单元中定义的任何对象。

为了弥补这个限制,C和C ++允许声明,并且这些声明可以被包含在预处理器的#include指令的帮助下使用它们的模块中。

另一方面,像Java或C#这样的语言包括在编译器输出(类文件或程序集)中绑定所需的信息。 因此,不再需要维护模块的客户单独的声明。

绑定信息不包含在编译器输出中的原因很简单:在运行时不需要(在编译时进行任何types检查)。 这只会浪费空间。 请记住,C / C ++来自可执行文件或库的大小确实很重要的时间。

C ++在1998年被批准了,为什么这样devise呢? 有一个单独的头文件有什么好处?

实际上,头文件在第一次检查程序时变得非常有用,检查头文件(仅使用文本编辑器)就可以让您大致了解程序的体系结构,而不像其他语言,您必须使用复杂的工具来查看类和他们的成员职能。

我认为头文件背后的真实(历史)原因使编译器开发人员更容易…但是,然后,头文件确实有优势。
查看以前的post以获得更多讨论…

那么,你可以完美地开发没有头文件的C ++。 实际上,一些使用模板的库不使用头文件/代码文件范例(参见boost)。 但是在C / C ++中,你不能使用没有声明的东西。 处理这个问题的一个实际的方法是使用头文件。 另外,您可以获得共享界面而不共享代码/实现的优势。 我想这不是C创作者设想的:当你使用共享头文件时,你必须使用着名的:

 #ifndef MY_HEADER_SWEET_GUARDIAN #define MY_HEADER_SWEET_GUARDIAN // [...] // my header // [...] #endif // MY_HEADER_SWEET_GUARDIAN 

这不是一个真正的语言function,而是一个处理多重包含的实用方法。

所以,我认为,当创buildC时,前向声明的问题被低估,现在使用像C ++这样的高级语言时,我们必须处理这种事情。

对于我们糟糕的C ++用户来说另一个负担

那么C ++在1998年就已经被批准了,但是它的使用时间比这个要长得多,而且批准主要是放下当前的使用,而不是强加结构。 由于C ++基于C,而C具有头文件,C ++也拥有它们。

头文件的主要原因是启用单独的文件编译,并尽量减less依赖性。

说我有foo.cpp,我想使用bar.h / bar.cpp文件中的代码。

我可以在foo.cpp中包含“bar.h”,然后编程并编译foo.cpp,即使bar.cpp不存在。 头文件作为编译器的承诺,bar.h中的类/函数将在运行时存在,并且它已经包含了所有需要知道的东西。

当然,如果bar.h中的函数没有链接我的程序时,它就不会链接,我会得到一个错误。

一个副作用是你可以给用户一个头文件而不会泄露你的源代码。

另一个原因是如果你在* .cpp文件中改变你的代码的实现,但是根本不改变头部,你只需要编译* .cpp文件,而不是使用它的所有东西。 当然,如果你把大量的实现放到头文件中,那么这就变得不那么有用了。

如果您希望编译器自动查找其他文件中定义的符号,则需要强制程序员将这些文件放入预定义的位置(如Java程序包结构确定项目的文件夹结构)。 我更喜欢头文件。 你也需要你使用的库的来源或者一些统一的方式来把编译器所需要的信息放在二进制文件中。