我从未真正理解为什么c++需要一个单独的头文件,其中包含与.cpp文件中相同的函数。它使得创建类和重构类变得非常困难,并且给项目添加了不必要的文件。还有一个必须包含头文件的问题,但是必须显式地检查它是否已经被包含。
c++在1998年被批准,那么为什么要这样设计呢?使用单独的头文件有什么好处?
后续问题:
当我所包含的是.h文件时,编译器如何找到其中的代码的.cpp文件?它是假定.cpp文件与.h文件具有相同的名称,还是实际上遍历目录树中的所有文件?
我从未真正理解为什么c++需要一个单独的头文件,其中包含与.cpp文件中相同的函数。它使得创建类和重构类变得非常困难,并且给项目添加了不必要的文件。还有一个必须包含头文件的问题,但是必须显式地检查它是否已经被包含。
c++在1998年被批准,那么为什么要这样设计呢?使用单独的头文件有什么好处?
后续问题:
当我所包含的是.h文件时,编译器如何找到其中的代码的.cpp文件?它是假定.cpp文件与.h文件具有相同的名称,还是实际上遍历目录树中的所有文件?
第一个优点:如果没有头文件,就必须在其他源文件中包含源文件。这将导致在包含的文件更改时重新编译包含的文件。
第二个优点:它允许在不同的单元(不同的开发人员、团队、公司等)之间共享接口而无需共享代码。
c++在1998年被批准,那么为什么要这样设计呢?使用单独的头文件有什么好处?
实际上头文件在第一次检查程序时变得非常有用,检查头文件(仅使用文本编辑器)可以让您概述程序的体系结构,不像其他语言,您必须使用复杂的工具来查看类及其成员函数。
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++。事实上,一些大量使用模板的库并不使用头文件/代码文件范例(参见boost)。但是在C/ c++中,你不能使用没有声明的东西。一个实用的方法 处理这个问题的方法就是使用头文件。此外,您还可以在不共享代码/实现的情况下共享接口。我认为这不是C语言的创造者所能想象到的:当你使用共享头文件时,你必须使用著名的:
#ifndef MY_HEADER_SWEET_GUARDIAN
#define MY_HEADER_SWEET_GUARDIAN
// [...]
// my header
// [...]
#endif // MY_HEADER_SWEET_GUARDIAN
这并不是真正的语言特性,而是处理多重包含的一种实用方法。
所以,我认为在创建C时,前向声明的问题被低估了,现在当使用像c++这样的高级语言时,我们必须处理这类事情。
对于我们这些可怜的c++用户来说,这又是一个负担……
以我的理解(有限的-我通常不是一个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中#包含“SomeOther.c”。现在,向other .c中添加一些“私有”函数。由于C不知道私有函数,因此该函数也可以在Some.c中使用。
这就是.h文件的作用:它们指定你想从一个.c文件中“导出”的所有函数(和变量),这些函数可以在其他.c文件中访问。这样,您就获得了类似于Public/Private作用域的东西。此外,您可以将这个.h文件提供给其他人,而不必共享您的源代码- .h文件也适用于编译过的.lib文件。
主要原因是为了方便,为了源代码保护和应用程序各部分之间的解耦。
那是C。c++引入了类和私有/公共修饰符,所以当你仍然可以询问是否需要它们时,c++ AFAIK仍然要求在使用它们之前声明函数。此外,许多c++开发人员也是或曾经是C开发人员,并将他们的概念和习惯转移到c++中——为什么要改变没有破坏的东西呢?
如果你想让编译器自动找到其他文件中定义的符号,你需要强迫程序员把这些文件放在预定义的位置(就像Java包的结构决定了项目的文件夹结构)。我更喜欢头文件。此外,你还需要你所使用的库的源代码,或者一些统一的方法来把编译器需要的信息放在二进制文件中。
c++的设计目的是在C基础设施中添加现代编程语言的特性,而无需不必要地更改C语言中与语言本身无关的任何内容。
是的,在这一点上(在第一个c++标准诞生10年之后,在它开始大量使用20年之后),很容易会问为什么它没有一个合适的模块系统。很明显,现在设计的任何新语言都不能像c++那样工作。但这不是c++的意义所在。
c++的重点是进化,是现有实践的平稳延续,只是添加新的功能,而不(经常)破坏对用户社区来说足够有效的东西。
这意味着,与其他语言相比,它使一些事情变得更困难(特别是对于开始一个新项目的人),而另一些事情则更容易(特别是对于维护现有代码的人)。
因此,与其期待c++变成c#(这毫无意义,因为我们已经有了c#),为什么不选择正确的工具来完成这项工作呢?就我自己而言,我努力用现代语言(我碰巧使用c#)编写大量的新功能,并且我有大量现有的c++,我将它们保存在c++中,因为重新编写它们将没有真正的价值。它们融合得很好,所以基本上是无痛的。
头文件的需求源于编译器在了解其他模块中的函数和(或)变量的类型信息方面的限制。编译后的程序或库不包含编译器绑定到其他编译单元中定义的任何对象所需的类型信息。
为了弥补这一限制,C和c++允许声明,并且可以在预处理器的#include指令的帮助下将这些声明包含到使用它们的模块中。
另一方面,像Java或c#这样的语言在编译器的输出(类文件或程序集)中包含了绑定所必需的信息。因此,不再需要维护模块的客户端所包含的独立声明。
编译器输出中没有包含绑定信息的原因很简单:在运行时不需要绑定信息(任何类型检查都发生在编译时)。这只会浪费空间。请记住,C/ c++来自于一个可执行文件或库的大小相当重要的时代。
有些人认为头文件是一种优势:
It is claimed that it enables/enforces/allows separation of interface and implementation -- but usually, this is not the case. Header files are full of implementation details (for example member variables of a class have to be specified in the header, even though they're not part of the public interface), and functions can, and often are, defined inline in the class declaration in the header, again destroying this separation. It is sometimes said to improve compile-time because each translation unit can be processed independently. And yet C++ is probably the slowest language in existence when it comes to compile-times. A part of the reason is the many many repeated inclusions of the same header. A large number of headers are included by multiple translation units, requiring them to be parsed multiple times.
归根结底,头文件系统是70年代C语言刚被设计出来时的产物。当时,计算机的内存非常少,将整个模块保存在内存中是不可能的。编译器必须从文件顶部开始读取,然后线性地遍历源代码。报头机制支持这一点。编译器不需要考虑其他翻译单元,它只需要从上到下读取代码。
c++为了向后兼容而保留了这个系统。
今天,这毫无意义。它效率低下,容易出错,而且过于复杂。如果目标是分离接口和实现,还有更好的方法。
然而,c++ 0x的建议之一是添加一个适当的模块系统,允许代码像。net或Java那样被编译成更大的模块,所有这些都是一次性的,而且没有头文件。这个提议在c++ 0x中没有被采纳,但我相信它仍然属于“我们希望以后再做这个”的范畴。也许在TR2或类似的地方。
c++是在1998年被批准的,但它的使用时间比那要长得多,而且批准主要是规定当前的用法,而不是强加结构。由于c++是基于C语言的,而C语言有头文件,所以c++也有头文件。
使用头文件的主要原因是支持文件的单独编译,并最小化依赖关系。
假设我有foo.cpp,我想使用bar.h/bar.cpp文件中的代码。
我可以在foo.cpp中#包含“bar.h”,然后编程和编译foo.cpp,即使bar.cpp不存在。头文件向编译器承诺bar.h中的类/函数将在运行时存在,并且它已经拥有了它需要知道的一切。
当然,如果bar.h中的函数没有body当我试图链接我的程序时,它就不会链接,我就会得到一个错误。
一个副作用是,您可以在不透露源代码的情况下为用户提供头文件。
另一种情况是,如果更改了*.cpp文件中代码的实现,但根本不更改头文件,则只需要编译*.cpp文件,而不需要编译使用它的所有内容。当然,如果您在头文件中放置了大量的实现,那么这就变得不那么有用了。