标题背后的理由是什么?

7

我不太理解为什么需要一个头部,这似乎违反了DRY原则!头部中的所有信息(可以)都包含在实现中。


1
您的意思是头文件,就像在C项目中一样吗? - timdev
3
你说得对。你忘了它们是在30多年前发明的。为了让程序可以在几KB的内存中编译,必须做出一些妥协。 - jalf
这也是一个很好的观点,这也是我接受了这个答案的主要原因。 - RCIX
9个回答

20

它简化了编译过程。当您想要独立编译单元时,需要一些描述将链接到的部分的东西,而不必导入所有其他文件的全部内容。

它还允许隐藏代码。可以分发头文件以允许他人使用功能,而无需分发实现。

最后,它可以鼓励将接口与实现分离。

它们不是解决这些问题的唯一方法,但在30年前它们是一个好方法。今天我们可能不会为语言使用头文件,但它们并不是在2009年发明的。


但是如果你不分发实现,那么你的调用是如何工作的呢?它会神奇地跨越互联网来调用你的副本吗? - RCIX
那么像.NET这样的东西如何允许对编译后的DLL进行API探索呢? - RCIX
你需要翻译标题,但我认为这本身就值得一个问题。 - Dave Van den Eynde
4
@RCIX,它们并不总是这样。DLL可以按名称导出某些函数,并且可以查询此信息。一些DLL包含实现IDispatch的COM接口,其中包括类型库信息,也可以查询此信息。然而,未以某种方式导出的原始C/C++函数很难被查询(据我所知)。 - Steve Rowe
2
头文件不幸的是并没有将接口和实现分离开来(你的类仍然必须在头文件中列出私有成员变量)。但你的第一段话很正确。这就是为什么头文件被发明的原因。它不是出于方便而是必要性。 - jalf
1
@jaif:人们开始使用的语言并不是面向对象的。对于过程化语言,头文件可以很好地分离接口和实现,并且如果您真的想要,它们也允许您隐藏数据结构,只需通过不透明指针访问即可。 - dmckee --- ex-moderator kitten

4
许多现代语言的设计者,例如Java、Eiffel和C#,都明确地认同你的观点——这些语言从实现中提取模块的元数据。然而,头文件的概念本身并不排除这种可能性——编译器显然可以在编译一个.c文件时提取.h文件,就像其他语言的编译器隐式地做的那样。目前典型的C编译器没有这么做并不是一项语言设计问题——这是一项实现问题;显然用户对这样的功能没有需求,因此没有编译器厂商费心去实现它。
作为一种语言设计选择,使用单独的可读可编辑文本格式的.h文件可以让你拥有最好的两个世界:如果你愿意,可以手动编写.h文件,基于尚不存在的模块实现分别编译客户端代码;或者(假设编译器实现会提供它)可以通过编译实现自动获取.h文件。
如果C、C++等语言继续发展(显然它们今天仍然很好),像你这样不希望手动编写头文件的需求增长,最终编译器编写者将不得不提供“头文件生成”选项,而“最好的两个世界”不再是理论!

2
维护相同信息在两个文件中怎么能成为两全其美的最佳选择呢?最佳的选择应该是为了程序员的方便而自动生成类似头文件的东西。头文件是一个原始的hack,需要大量的额外努力,会减缓编译速度(在今天的代码库和今天的CPU上),并且通过强制考虑包含和声明顺序来避免循环引用而使您的代码变得复杂。而且编译器不能只是自动定义.h文件。这在某些情况下会改变您代码的语义。 - jalf
2
@jaif,正如我所说,C标准中没有禁止编译器在编译时生成.h文件的选项(包括所有外部可见实体和仅限于此的实体):如果您认为不是这样,请告诉我禁止了哪里。如果编译器实际上不这样做,那一定是因为缺乏需求-也就是说,C或C++的用户明显认为缺少这个功能并不是什么大问题,否则他们会向编译器供应商施加压力以提供该功能! - Alex Martelli

3

当c语言开发时,可用的计算机能力应该考虑一下。主内存以千字为单位进行测量,且数量并不多。硬盘更大一些,但也不是很大。真正的存储意味着需要使用卷轴式磁带,手动安装,由不爽的操作员操作,他们真的希望你走开,这样他们就可以玩“Hunt the Wumpus”游戏了。1 MIP的计算机速度是非常快的。在所有这些限制下,你必须与其他几十个用户共同使用它。

任何可以减少编译空间或时间复杂度的东西都是一个巨大的胜利。头文件就能做到。


4
“Grumpy operators”没有玩“猎捕乌普斯”游戏。我们迷失在一个看起来都一样的曲折通道的迷宫中。 - themis

2
当C语言发明.h文件时,检查语言处理器的二进制输出文件的整个想法可能很难理解。有一个名为JOVIAL的系统做了类似的事情,但它是奇特的,几乎只限于军事项目。 (我从未见过JOVIAL程序,只是听说过。)因此,当C语言问世时,模块化的通常设计模式是“没有任何检查”。可能会有一个限制,即.text符号只能链接到.text,.data只能链接到.data,但仅此而已。也就是说,当时的编译器通常一次处理一个源文件,然后链接器将它们组合在一起,除了如果你很幸运,“我是函数符号”与“我是数据符号”之外,没有任何错误检查的最小级别。因此,让编译器真正理解您正在调用的内容的想法有些新颖。即使今天,如果您制作一个完全虚假的头文件,在大多数AOT编译器中都不会被发现。聪明的东西,如CLR语言和Java,实际上会在类文件中编码一些东西。
所以,长远来看,我们可能不再需要头文件。

2
不要忘记文档头提供的信息。通常,您需要知道使用模块的所有内容都在其中。我个人不想浏览冗长的源代码,以了解需要使用什么和如何调用它...您无论如何都会提取这些信息,这实际上就是一个头文件。当然,现代IDE已经解决了这个问题,但是在处理一些旧的C代码时,我真的很喜欢手工制作包含有关用法和前后条件注释的头文件。
保持源代码、头文件和其他文档同步仍然是另一个难题...

1

在Java中,你没有头文件,但是你有接口。每个认真的Java专家都建议你将任何被其他项目/系统使用的东西定义为接口和实现。

让我们看一个Java接口定义包含调用签名、类型定义和常量。

大多数C头文件包含调用签名、类型定义和常量。

因此,对于所有实际目的而言,C/C++头文件只是接口定义,因此应该被视为一件好事。现在我知道在头文件中还可以定义其他无数的东西(宏、常量等等),但这只是C世界的一部分:-)

int function target () {
    // Default for shoot
    return FOOT;
}

你可能没有注意到,但是Java不需要在使用接口之前#包含其定义。头文件需要。这使它们成为好东西吗? - jalf
是的,但接口有一个特定的目的:确保您可以访问某些属性并调用某些方法,而不需要实际知道该对象是什么。头文件仅作为单个(Objective-)C(++)文件内容的重复存在,只是以压缩形式存在(至少我所见过的)。 - RCIX

1

详细阅读请点击this

头文件通常包含类、子程序、变量和其他标识符的前向声明。希望在多个源文件中声明标准标识符的程序员可以将这些标识符放在单个头文件中,其他代码可以在需要头文件内容时包含它们。

C标准库和C++标准库传统上在头文件中声明它们的标准函数。


0

如果你想让别人使用你的库的声明而不给他们实现,该怎么办呢?

正如另一个答案所指出的那样,头文件最初的原因是为了使在具有非常简单和有限工具的平台上进行解析/编译更容易。拥有两个软盘的机器可以让你在一个软盘上拥有编译器,而在另一个软盘上拥有代码,这使得事情变得更加容易。


我可以想象一个编译器,它可以有一个选项来自动生成声明文件,你不需要自己维护它们。 - sourcenouveau
是的,你可以这样做,但你需要一种告诉它应该导出什么和不应该导出什么的方法。编写自己的头文件可以让你非常具体化。 - dmckee --- ex-moderator kitten

0

当你将代码分为头文件和源文件时,你会分离声明和定义。当你查看头文件时,你可以看到你拥有什么,如果你想要查看实现细节,你需要去源文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接