为什么c++有头文件和。cpp文件?
因为在c++中,最终的可执行代码不携带任何符号信息,它或多或少是纯机器代码。
因此,您需要一种方法来描述一段代码的接口,这种方法与代码本身是分开的。该描述在头文件中。
主要原因是将接口从实现中分离出来。头文件声明了一个类(或任何正在实现的类)将做什么,而cpp文件定义了它将“如何”执行这些功能。
这减少了依赖关系,因此使用头文件的代码不一定需要知道实现的所有细节,也不需要知道为此所需的任何其他类/头文件。这将减少编译时间,以及当实现中的某些内容发生更改时所需的重新编译量。
它并不完美,您通常会求助于诸如Pimpl Idiom之类的技术来正确地分离接口和实现,但这是一个良好的开端。
因为设计库格式的人不想为很少使用的信息(如C预处理器宏和函数声明)“浪费”空间。
因为你需要这些信息来告诉你的编译器“当链接器完成它的工作时,这个函数稍后可用”,他们必须拿出第二个文件来存储这些共享信息。
C/ c++之后的大多数语言将这些信息存储在输出中(例如Java字节码),或者它们根本不使用预编译的格式,总是以源代码形式分发并动态编译(Python, Perl)。
通常情况下,您希望有一个接口的定义,而不必交付整个代码。例如,如果您有一个共享库,您将附带一个头文件,该文件定义了共享库中使用的所有函数和符号。如果没有头文件,则需要发布源代码。
在一个项目中,头文件至少有两个用途:
清晰性,即通过保持接口与实现分离,更容易阅读代码 编译时间。通过尽可能只使用接口,而不是完整的实现,可以减少编译时间,因为编译器可以简单地对接口进行引用,而不必解析实际代码(理想情况下,只需要一次完成)。
c++编译
c++中的编译分为两个主要阶段:
第一种是将“源”文本文件编译为二进制“对象”文件:CPP文件是编译后的文件,编译时不了解其他CPP文件(甚至库),除非通过原始声明或头包含提供给它。CPP文件通常被编译成. obj或. o“对象”文件。 第二步是将所有“目标”文件链接在一起,从而创建最终的二进制文件(库或可执行文件)。
HPP在整个过程中处于什么位置?
一个可怜孤独的CPP文件…
每个CPP文件的编译都独立于所有其他CPP文件,这意味着如果a .CPP需要在B.CPP中定义的符号,例如:
// A.CPP
void doSomething()
{
doSomethingElse(); // Defined in B.CPP
}
// B.CPP
void doSomethingElse()
{
// Etc.
}
它不会编译,因为a.c p没有办法知道“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.h ppp包括在b.c cpp中?
In the current case, this is not needed, and B.HPP has the doSomethingElse function declaration, and B.CPP has the doSomethingElse function definition (which is, by itself a declaration). But in a more general case, where B.HPP is used for declarations (and inline code), there could be no corresponding definition (for example, enums, plain structs, etc.), so the include could be needed if B.CPP uses those declaration from B.HPP. All in all, it is "good taste" for a source to include by default its header.
结论
因此,头文件是必要的,因为c++编译器不能单独搜索符号声明,因此,您必须通过包含这些声明来帮助它。
最后一句话:你应该在你的HPP文件的内容周围设置头保护,以确保多个包含不会破坏任何东西,但总而言之,我相信HPP文件存在的主要原因已经在上面解释过了。
#ifndef B_HPP_
#define B_HPP_
// The declarations in the B.hpp file
#endif // B_HPP_
或者更简单(尽管不是标准)
#pragma once
// The declarations in the B.hpp file
因为C语言是这个概念的发源地,它已经有30年的历史了,在当时,它是将多个文件中的代码链接在一起的唯一可行的方法。
今天,这是一个可怕的黑客,它完全破坏了c++中的编译时间,导致无数不必要的依赖关系(因为头文件中的类定义暴露了太多关于实现的信息),等等。
回应MadKeithV的回答,
这减少了依赖关系,因此使用标头的代码不会 必然需要知道实现的所有细节和任何 其他类/头只需要这样做。这将减少 编译次数,以及需要的重新编译量 实现中的某些内容发生了变化。
另一个原因是头文件为每个类提供了唯一的id。
如果我们有
class A {..};
class B : public A {...};
class C {
include A.cpp;
include B.cpp;
.....
};
我们会有错误,当我们试图构建项目时,因为A是B的一部分,有了头文件,我们就可以避免这种头痛……
推荐文章
- 为什么我的程序不能在Windows 7下用法语编译?
- 如何获取变量的类型?
- 什么是奇怪的重复模板模式(CRTP)?
- 连接两个向量的最佳方法是什么?
- 在c++中,是通过值传递更好,还是通过引用到const传递更好?
- 在STL中deque到底是什么?
- Windows上最好的免费c++分析器是什么?
- 如何自动转换强类型枚举为int?
- 在一个类中使用具有成员函数的泛型std::function对象
- 'for'循环中的后增量和前增量产生相同的输出
- 虚函数和纯虚函数的区别
- c++中的_tmain()和main()有什么区别?
- 内存泄漏是否正常?
- 当启用c++ 11时,std::vector性能回归
- 什么时候使用哪种指针?