为什么C++有头文件和.cpp文件?
C++中的编译分为两个主要阶段:
第一个是将"源"文本文件编译成二进制"对象"文件:CPP文件是编译文件,编译时不知道其他CPP文件(甚至库),除非通过原始声明或标题包含.CPP文件通常编译为.OBJ或.O"对象"文件.
第二个是将所有"对象"文件链接在一起,从而创建最终的二进制文件(库或可执行文件).
HPP在哪里适合所有这些过程?
每个CPP文件的编译独立于所有其他CPP文件,这意味着如果A.CPP需要在B.CPP中定义的符号,例如:
// A.CPP void doSomething() { doSomethingElse(); // Defined in B.CPP } // B.CPP void doSomethingElse() { // Etc. }
它不会编译因为A.CPP无法知道"doSomethingElse"存在...除非A.CPP中有声明,例如:
// A.CPP void doSomethingElse() ; // From B.CPP void doSomething() { doSomethingElse() ; // Defined in B.CPP }
然后,如果您有使用相同符号的C.CPP,则复制/粘贴声明...
是的,有一个问题.复制/粘贴是危险的,难以维护.这意味着如果我们有一些方法可以不复制/粘贴,并且仍然声明符号,那将会很酷......我们怎么做呢?通过包含一些文本文件,通常后缀为.h,.hxx,.h ++或者我喜欢的C++文件,.hpp:
// 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 }
include
工作怎么样?实质上,包含文件将解析然后将其内容复制粘贴到CPP文件中.
例如,在以下代码中,使用A.HPP标头:
// A.HPP void someFunction(); void someOtherFunction();
......来源B.CPP:
// B.CPP #include "A.HPP" void doSomething() { // Etc. }
......将在纳入之后成为:
// B.CPP void someFunction(); void someOtherFunction(); void doSomething() { // Etc. }
在当前情况下,这不是必需的,并且B.HPP具有doSomethingElse
函数声明,并且B.CPP具有doSomethingElse
函数定义(其本身是声明).但在更一般的情况下,B.HPP用于声明(和内联代码),可能没有相应的定义(例如,枚举,普通结构等),因此如果B.CPP可能需要包含使用B.HPP的声明.总而言之,一个源默认包含它的标题是"好品味".
因此头文件是必需的,因为C++编译器无法单独搜索符号声明,因此,您必须通过包含这些声明来帮助它.
最后一句话:您应该在HPP文件的内容周围放置标题保护,以确保多个包含不会破坏任何内容,但总而言之,我认为HPP文件存在的主要原因如上所述.
#ifndef B_HPP_ #define B_HPP_ // The declarations in the B.hpp file #endif // B_HPP_
那么,主要原因是将接口与实现分离.标题声明"什么"类(或正在实现的任何内容)将执行,而cpp文件定义它将如何执行这些功能.
这减少了依赖性,因此使用头的代码不一定需要知道实现的所有细节以及仅为此需要的任何其他类/头.这将减少编译时间以及实现中的某些内容更改时所需的重新编译量.
它并不完美,你通常会采用像Pimpl Idiom这样的技术来正确分离界面和实现,但这是一个好的开始.
因为C这个概念起源于30年前,当时它是将多个文件中的代码链接在一起的唯一可行方法.
今天,这是一个非常糟糕的黑客,完全破坏了C++中的编译时间,导致无数的不必要的依赖(因为头文件中的类定义暴露了太多关于实现的信息),等等.
因为在C++中,最终的可执行代码不携带任何符号信息,所以它或多或少都是纯机器代码.
因此,您需要一种方法来描述一段代码的接口,该代码与代码本身是分开的.此描述位于头文件中.
因为设计库格式的人不想为C预处理器宏和函数声明等很少使用的信息"浪费"空间.
由于您需要该信息告诉编译器"当链接器正在执行其工作时此函数可用",因此他们必须提供第二个文件,以便存储此共享信息.
C/C++之后的大多数语言都将这些信息存储在输出中(例如,Java字节码),或者它们根本不使用预编译格式,总是以源代码形式分发并动态编译(Python,Perl).
因为C++从C继承了它们.不幸的是.
这是声明接口的预处理器方式。您将接口(方法声明)放入头文件中,并将实现放入cpp中。使用您的库的应用程序只需要知道接口,就可以通过#include访问该接口。
通常,您将需要定义接口而不必交付整个代码。例如,如果您有一个共享库,则将附带一个头文件,该头文件定义了共享库中使用的所有功能和符号。如果没有头文件,则需要发送源代码。
在单个项目中,至少将IMHO头文件用于两个目的:
明确性,即通过将接口与实现分开,可以更轻松地读取代码
编译时间。通过在可能的情况下仅使用接口,而不是完整的实现,可以减少编译时间,因为编译器可以简单地引用该接口,而不必解析实际的代码(理想情况下,只需完成此操作即可)一次)。