// B.HPP (here, we decided to declare every symbol defined in B.CPP)
void doSomethingElse() ;
// A.CPP
#include "B.HPP"
void doSomething()
{
doSomethingElse() ; // Defined in B.CPP
}
// B.CPP
#include "B.HPP"
void doSomethingElse()
{
// Etc.
}
// C.CPP
#include "B.HPP"
void doSomethingAgain()
{
doSomethingElse() ; // Defined in B.CPP
}
9 回答
那么,主要原因是将接口与实现分离 . Headers 声明“什么”类(或正在实现的任何内容)将执行,而cpp文件定义它将如何执行这些功能 .
这减少了依赖性,因此使用头的代码不一定需要知道实现的所有细节以及仅为此所需的任何其他类/头 . 这将减少编译时间以及实现中的某些内容更改时所需的重新编译量 .
它并不完美,你通常会采用像_1052340这样的技术来正确分离界面和实现,但这是一个好的开始 .
C编译
C中的汇编分为两个主要阶段:
第一个是将“源”文本文件编译成二进制“对象”文件:CPP文件是编译文件,编译时不知道其他CPP文件(甚至库),除非通过原始声明提供给它或 Headers 包含 . CPP文件通常编译为.OBJ或.O“对象”文件 .
第二个是将所有“对象”文件链接在一起,从而创建最终的二进制文件(库或可执行文件) .
HPP在哪里适合所有这些过程?
糟糕的寂寞CPP档案......
每个CPP文件的编译独立于所有其他CPP文件,这意味着如果A.CPP需要在B.CPP中定义的符号,例如:
它不会编译因为A.CPP无法知道“doSomethingElse”存在...除非A.CPP中有声明,例如:
然后,如果你有C.CPP使用相同的符号,你然后复制/粘贴声明...
COPY / PASTE ALERT!
是的,有一个问题 . 复制/粘贴是危险的,难以维护 . 这意味着如果我们有一些方法可以不复制/粘贴,并且仍然声明符号,那将会很酷......我们怎么能这样做?通过包含一些文本文件,通常后缀为.h,.hxx,.h或者我喜欢的C文件,.hpp:
如何包含工作?
实质上,包含文件将解析然后将其内容复制粘贴到CPP文件中 .
例如,在以下代码中,使用A.HPP标头:
......来源B.CPP:
...将在包含后成为:
一件小事 - 为什么在B.CPP中加入B.HPP?
在当前情况下,这不是必需的,并且B.HPP具有
doSomethingElse
函数声明,并且B.CPP具有doSomethingElse
函数定义(其本身是声明) . 但是在更一般的情况下,B.HPP用于声明(和内联代码),可能没有相应的定义(例如,枚举,普通结构等),因此如果B.CPP可能需要包含使用B.HPP的声明 . 总而言之,默认情况下,它的头部包含了"good taste" .结论
因此头文件是必需的,因为C编译器无法单独搜索符号声明,因此,您必须通过包含这些声明来帮助它 .
最后一句话:您应该在HPP文件的内容周围放置 Headers 保护,以确保多个包含不会破坏任何内容,但总而言之,我相信HPP文件存在的主要原因如上所述 .
因为C这个概念起源于30年前,当时它是将多个文件中的代码链接在一起的唯一可行方法 .
今天,它是一个非常糟糕的黑客,它完全破坏了C中的编译时间,导致无数不必要的依赖(因为头文件中的类定义暴露了太多关于实现的信息),等等 .
因为在C中,最终的可执行代码不携带任何符号信息,所以它或多或少都是纯机器代码 .
因此,您需要一种方法来描述一段代码的接口,该代码与代码本身是分开的 . 此描述位于头文件中 .
因为设计库格式的人不想为C预处理器宏和函数声明等很少使用的信息“浪费”空间 .
由于您需要该信息告诉编译器“当链接器正在执行其工作时此函数可用”,他们必须提供第二个文件,以便存储此共享信息 .
C / C之后的大多数语言将此信息存储在输出中(例如,Java字节码),或者它们根本不使用预编译格式,始终以源代码形式分发并即时编译(Python,Perl) .
因为C从C继承了它们 . 不幸的是 .
它是声明接口的预处理器方式 . 您将接口(方法声明)放入头文件,并将实现放入cpp . 使用您的库的应用程序只需知道接口,他们可以通过#include访问 .
通常,您需要具有接口的定义,而无需发送整个代码 . 例如,如果您有一个共享库,您将发送一个包含它的头文件,该文件定义共享库中使用的所有函数和符号 . 如果没有头文件,则需要发送源代码 .
在单个项目中,使用头文件,恕我直言,至少有两个目的:
Clarity,即通过将接口与实现分开,更容易阅读代码
编译时间 . 通过尽可能使用接口,而不是完整的实现,编译时间可以减少,因为编译器可以简单地引用接口而不必解析实际的代码(理想情况下,只需要完成一次) .
回应MadKeithV's answer,
另一个原因是 Headers 为每个类提供唯一的id .
所以,如果我们有类似的东西
当我们尝试构建项目时,我们会有错误,因为A是B的一部分,有 Headers 我们会避免这种头痛......