当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《为什么C++需要单独的头文件?》经验,为你挑选了6个好方法。

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

C++于1998年获得批准,那为什么这样设计呢?单独的头文件有哪些优点?


跟进问题:

当我包含的所有内容都是.h文件时,编译器如何找到带有代码的.cpp文件?是否假设.cpp文件与.h文件具有相同的名称,或者它是否实际查看目录树中的所有文件?



1> Steve Jessop..:

您似乎要求将定义与声明分开,尽管头文件还有其他用途.

答案是C++并不"需要"这个.如果您将所有内联标记(无论如何对于类定义中定义的成员函数都是自动的),则不需要分离.您只需在头文件中定义所有内容即可.

您可能想要分开的原因是:

    改善构建时间.

    在没有定义源的情况下链接代码.

    避免将所有内容标记为"内联".

如果您更普遍的问题是"为什么C++与Java不相同?",那么我不得不问,"为什么要编写C++而不是Java?" ;-p

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

#include是一个直接的文本替代.如果在头文件中定义所有内容,预处理器最终会在项目中创建每个源文件的大量复制和粘贴,并将其提供给编译器.C++标准在1998年被批准的事实与此无关,这是因为C++的编译环境与C语言的编译环境密切相关.

转换我的评论以回答您的后续问题:

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

它没有,至少在它编译使用头文件的代码时没有.您要链接的函数甚至不需要编写,更不用说编译器知道.cpp它们将在哪个文件中.调用代码在编译时需要知道的所有内容都在函数声明中表示.在链接时,您将提供.o文件列表,或静态或动态库,有效的标头是一个承诺,函数的定义将在某处.


@AndresCanella不,不.它使阅读和维护不是你自己的代码成为一场噩梦.要完全理解代码中的内容,您需要跳过2n个文件而不是n个文件.这只是不是Big-Oh表示法,2n与n相比有很大的不同.
添加到"您可能想要分离的原因是:"我认为头文件最重要的功能是:将代码结构设计与实现分开,因为:A.当您进入涉及许多对象的非常复杂的结构时更容易筛选头文件并记住它们如何协同工作,并由标题注释补充.B.让一个人不负责定义所有的对象结构,而其他一些人则负责实现它保持组织有序.总之,我认为它使复杂的代码更具可读性.

2> Donald Byrd..:

C++就是这样做的,因为C就是这样做的,所以真正的问题是为什么C这样做呢?维基百科对此有所说明.

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


+1击中头部的钉子.这真的不需要冗长的解释.
它没有打我的头钉:(我还是要回去看看C++为什么必须使用前置声明,为什么它不能从源文件识别标识,并直接从动态读取库符号,为什么C++做的是方式只是因为C这样做:p
你是一个更好的程序员@AlexanderTaylor :)

3> jalf..:

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

声称它启用/强制/允许分离接口和实现 - 但通常情况并非如此.头文件充满了实现细节(例如,类的成员变量必须在头中指定,即使它们不是公共接口的一部分),并且函数可以并且通常类声明中内联定义在标题中,再次破坏了这种分离.

有时可以说它可以改善编译时间,因为每个翻译单元都可以独立处理.然而,在编译时,C++可能是最慢的语言.部分原因是同一标题中的许多重复包含.多个翻译单元包含大量标题,需要对它们进行多次解析.

最终,标题系统是设计C时70年代的神器.那时,计算机的内存非常少,将整个模块保留在内存中并不是一种选择.编译器必须从顶部开始读取文件,然后线性地完成源代码.标头机制实现了这一点.编译器不必考虑其他转换单元,只需从上到下读取代码即可.

C++保留了这个系统以实现向后兼容.

今天,没有任何意义.它效率低,容易出错且过于复杂.如果是目标,那么有更好的方法来分离界面和实现.

但是,C++ 0x的提议之一是添加一个适当的模块系统,允许将代码编译为类似于.NET或Java,编译成更大的模块,一次性完成,无需头文件.这个提议没有在C++ 0x中得到削减,但我相信它仍然在"我们以后喜欢这样做"的类别.也许在TR2或类似的.



4> Michael Stum..:

对于我(有限 - 我通常不是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不知道私有函数,因此Somec中也可以使用该函数.

这是.h文件的来源:它们指定要从.c文件中"导出"的所有函数(和变量),可以在其他.c文件中访问.这样,您获得了类似公共/私人范围的东西.此外,您可以将此.h文件提供给其他人而无需共享源代码 - .h文件也可以对编译的.lib文件起作用.

因此,主要原因是为了方便,源代码保护以及在应用程序的各个部分之间进行一些解耦.

那是C.C++引入了类和私有/公共修饰符,因此当您仍然可以询问是否需要它们时,C++ AFAIK仍然需要在使用它们之前声明函数.此外,许多C++开发人员也是C devleopers,并将他们的概念和习惯接管到C++ - 为什么要改变未破坏的东西?


为什么编译器无法运行代码并找到所有函数定义?看起来很容易编程到编译器中.
如果您*拥有*您通常没有的来源.编译的C++实际上是机器代码,只有足够的附加信息来加载和链接代码.然后,将CPU指向入口点,然后让它运行.这与Java或C#根本不同,后者将代码编译为包含其内容元数据的中间字节码.
是的 - 使用磁带质量torage编译16 bitter是非常重要的.

5> erelender..:

第一个优点:如果您没有头文件,则必须在其他源文件中包含源文件.这将导致包含文件在包含的文件更改时再次编译.

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



6> VoidPointer..:

对头文件的需求源于编译器用于了解其他模块中的函数和/或变量的类型信息的限制.已编译的程序或库不包含编译器绑定到其他编译单元中定义的任何对象所需的类型信息.

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

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

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

推荐阅读
LEEstarmmmmm
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有