为什么有头文件和.cpp文件?
为什么C ++有头文件和.cpp文件?
那么,主要的原因是将接口从实现中分离出来。 头文件声明了“什么”一个类(或任何正在执行的)将会做什么,而cpp文件定义了“如何”它将执行这些function。
这减less了依赖关系,以便使用头的代码不一定需要知道实现的所有细节和任何其他类/头只需要的细节。 这将减less编译时间以及实现中的某些内容所需的重新编译的数量。
这并不完美,你通常会使用像Pimpl成语这样的技术来正确地分离界面和实现,但这是一个好的开始。
C ++编译
C ++编译分两个主要阶段完成:
-
首先是将“源”文本文件编译为二进制“对象”文件:CPP文件是编译后的文件,在没有任何关于其他CPP文件(甚至库)的知识的情况下编译,除非通过原始声明或标题包含。 CPP文件通常被编译成.OBJ或.O“对象”文件。
-
第二个是所有“对象”文件的链接,从而创build最终的二进制文件(库或可执行文件)。
HPP在哪些方面适合所有这些过程?
一个贫穷的孤独的CPP文件…
每个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
工作?
包括一个文件本质上将parsing,然后将其内容复制粘贴到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.CPP中包含B.HPP?
在目前的情况下,这是不必要的,B.HPP具有doSomethingElse
函数声明,而B.CPP具有doSomethingElse
函数定义(它本身就是一个声明)。 但是在更一般的情况下,在B.HPP用于声明(和内联代码)的情况下,可能没有相应的定义(例如枚举,普通结构等),所以如果B.CPP使用B.HPP的声明。 总而言之,源代码默认包含头文件是“好品味”。
结论
头文件是必要的,因为C ++编译器不能单独search符号声明,因此,你必须通过包含这些声明来帮助它。
最后一句话:你应该在你的HPP文件的内容中加上标题警卫,以确保多个内容不会破坏任何内容,但总而言之,我相信HPP文件存在的主要原因在上面解释。
#ifndef B_HPP_ #define B_HPP_ // The declarations in the B.hpp file #endif // B_HPP_
因为起源于C的概念已经有30年的历史了,而当时C是将多个文件中的代码连接在一起的唯一可行的方法。
今天,这是一个非常糟糕的破解,它彻底破坏了C ++中的编译时间,导致无数不必要的依赖(因为头文件中的类定义暴露了太多关于实现的信息)等等。
因为在C ++中,最终的可执行代码不带任何符号信息,所以它或多或less是纯粹的机器代码。
因此,您需要一种方法来描述一段代码的界面,这与代码本身是分开的。 这个描述在头文件中。
因为devise库格式的人不想为C预处理器macros和函数声明等很less使用的信息“浪费”空间。
既然你需要这些信息来告诉你的编译器“这个函数在链接器工作之后就可以使用”,他们必须拿出第二个文件来存储这个共享信息。
C / C ++之后的大多数语言将这些信息存储在输出中(例如,Java字节码),或者根本不使用预编译格式,始终以源代码forms分发并且即时编译(Python,Perl)。
因为C ++从Cinheritance了它们。不幸的是,
这是声明接口的预处理方式。 你把接口(方法声明)放到头文件中,并把实现放到cpp中。 使用你的库的应用程序只需要知道他们可以通过#include访问的接口。
通常你会希望有一个接口的定义,而不必运送整个代码。 例如,如果你有一个共享库,你将会发送一个头文件,它定义了共享库中使用的所有函数和符号。 没有头文件,你需要运送源代码。
在一个项目中,使用头文件,恕我直言,至less有两个目的:
- 清晰度,就是说,通过使接口与实现分离,读取代码更容易
- 编译时间。 通过只使用接口,而不是完整的实现,可以减less编译时间,因为编译器可以简单地对接口进行引用,而不必parsing实际的代码(理想情况下,只需要完成一次)。
回应MadKeithV的回答 ,
这减less了依赖关系,以便使用头的代码不一定需要知道实现的所有细节和任何其他类/头只需要的细节。 这将减less编译时间,并减less实现中的某些内容所需的重新编译的数量。
另一个原因是标题为每个类提供了一个唯一的ID。
所以如果我们有类似的东西
class A {..}; class B : public A {...}; class C { include A.cpp; include B.cpp; ..... };
当我们尝试构build项目时,我们将会出现错误,因为A是B的一部分,我们将会避免这种头痛的问题。