我从未真正理解为什么c++需要一个单独的头文件,其中包含与.cpp文件中相同的函数。它使得创建类和重构类变得非常困难,并且给项目添加了不必要的文件。还有一个必须包含头文件的问题,但是必须显式地检查它是否已经被包含。
c++在1998年被批准,那么为什么要这样设计呢?使用单独的头文件有什么好处?
后续问题:
当我所包含的是.h文件时,编译器如何找到其中的代码的.cpp文件?它是假定.cpp文件与.h文件具有相同的名称,还是实际上遍历目录树中的所有文件?
我从未真正理解为什么c++需要一个单独的头文件,其中包含与.cpp文件中相同的函数。它使得创建类和重构类变得非常困难,并且给项目添加了不必要的文件。还有一个必须包含头文件的问题,但是必须显式地检查它是否已经被包含。
c++在1998年被批准,那么为什么要这样设计呢?使用单独的头文件有什么好处?
后续问题:
当我所包含的是.h文件时,编译器如何找到其中的代码的.cpp文件?它是假定.cpp文件与.h文件具有相同的名称,还是实际上遍历目录树中的所有文件?
当前回答
c++的设计目的是在C基础设施中添加现代编程语言的特性,而无需不必要地更改C语言中与语言本身无关的任何内容。
是的,在这一点上(在第一个c++标准诞生10年之后,在它开始大量使用20年之后),很容易会问为什么它没有一个合适的模块系统。很明显,现在设计的任何新语言都不能像c++那样工作。但这不是c++的意义所在。
c++的重点是进化,是现有实践的平稳延续,只是添加新的功能,而不(经常)破坏对用户社区来说足够有效的东西。
这意味着,与其他语言相比,它使一些事情变得更困难(特别是对于开始一个新项目的人),而另一些事情则更容易(特别是对于维护现有代码的人)。
因此,与其期待c++变成c#(这毫无意义,因为我们已经有了c#),为什么不选择正确的工具来完成这项工作呢?就我自己而言,我努力用现代语言(我碰巧使用c#)编写大量的新功能,并且我有大量现有的c++,我将它们保存在c++中,因为重新编写它们将没有真正的价值。它们融合得很好,所以基本上是无痛的。
其他回答
c++是这样做的,因为C是这样做的,所以真正的问题是为什么C要这样做?维基百科对此做了一些说明。
较新的编译语言(例如 Java、c#)不使用转发 声明;标识符 从源自动识别 文件和读取直接从动态 符号库。这意味着头文件 不需要文件。
您似乎在询问如何将定义与声明分开,尽管头文件还有其他用途。
答案是c++不“需要”这个。如果将所有内容都标记为内联(对于类定义中定义的成员函数来说,这是自动的),则不需要分离。你可以在头文件中定义一切。
你想分手的原因可能是:
改善构建时间。 在没有定义源的情况下链接到代码。 避免将所有内容标记为“内联”。
如果你更普遍的问题是,“为什么c++和Java不一样?”,那么我不得不问,“为什么你写c++而不是Java?”- p;
但是,更严重的是,c++编译器不能像javac那样直接进入另一个翻译单元,并找出如何使用它的符号。头文件需要向编译器声明在链接时它期望可用的内容。
所以#include是一个直接的文本替换。如果你在头文件中定义所有东西,预处理器最终会对项目中的每个源文件创建一个巨大的复制和粘贴,并将其提供给编译器。c++标准在1998年被批准的事实与此无关,这是c++的编译环境如此紧密地基于C的事实。
转换我的评论来回答你接下来的问题:
编译器如何找到包含代码的.cpp文件
It doesn't, at least not at the time it compiles the code that used the header file. The functions you're linking against don't even need to have been written yet, never mind the compiler knowing what .cpp file they'll be in. Everything the calling code needs to know at compile time is expressed in the function declaration. At link time you will provide a list of .o files, or static or dynamic libraries, and the header in effect is a promise that the definitions of the functions will be in there somewhere.
头文件的需求源于编译器在了解其他模块中的函数和(或)变量的类型信息方面的限制。编译后的程序或库不包含编译器绑定到其他编译单元中定义的任何对象所需的类型信息。
为了弥补这一限制,C和c++允许声明,并且可以在预处理器的#include指令的帮助下将这些声明包含到使用它们的模块中。
另一方面,像Java或c#这样的语言在编译器的输出(类文件或程序集)中包含了绑定所必需的信息。因此,不再需要维护模块的客户端所包含的独立声明。
编译器输出中没有包含绑定信息的原因很简单:在运行时不需要绑定信息(任何类型检查都发生在编译时)。这只会浪费空间。请记住,C/ c++来自于一个可执行文件或库的大小相当重要的时代。
c++在1998年被批准,那么为什么要这样设计呢?使用单独的头文件有什么好处?
实际上头文件在第一次检查程序时变得非常有用,检查头文件(仅使用文本编辑器)可以让您概述程序的体系结构,不像其他语言,您必须使用复杂的工具来查看类及其成员函数。
如果你想让编译器自动找到其他文件中定义的符号,你需要强迫程序员把这些文件放在预定义的位置(就像Java包的结构决定了项目的文件夹结构)。我更喜欢头文件。此外,你还需要你所使用的库的源代码,或者一些统一的方法来把编译器需要的信息放在二进制文件中。