为什么有头文件和.cpp文件?

时间:2020-12-21 15:08:38

Why does C++ have header files and .cpp files?

为什么c++有头文件和。cpp文件?

9 个解决方案

#1


176  

Well, the main reason would be for separating the interface from the implementation. The header declares "what" a class (or whatever is being implemented) will do, while the cpp file defines "how" it will perform those features.

主要原因是将接口与实现分开。header声明“什么”类(或正在实现的任何内容)将会执行,而cpp文件将定义“如何”执行这些特性。

This reduces dependencies so that code that uses the header doesn't necessarily need to know all the details of the implementation and any other classes/headers needed only for that. This will reduce compilation times and also the amount of recompilation needed when something in the implementation changes.

这减少了依赖关系,因此使用header的代码并不需要知道实现的所有细节,也不需要知道任何其他类/header。这将减少编译时间,以及在实现更改时需要的重新编译的数量。

It's not perfect, and you would usually resort to techniques like the Pimpl Idiom to properly separate interface and implementation, but it's a good start.

它并不完美,您通常会使用Pimpl这样的技术来正确地分离接口和实现,但这是一个好的开始。

#2


512  

C++ compilation

A compilation in C++ is done in 2 major phases:

c++的编译主要分为两个阶段:

  1. The first is the compilation of "source" text files into binary "object" files: The CPP file is the compiled file and is compiled without any knowledge about the other CPP files (or even libraries), unless fed to it through raw declaration or header inclusion. The CPP file is usually compiled into a .OBJ or a .O "object" file.

    第一种方法是将“源”文本文件编译成二进制“对象”文件:CPP文件是编译后的文件,编译时不需要了解其他CPP文件(甚至是库),除非通过原始声明或标题包含。CPP文件通常被编译成. obj或. o“对象”文件。

  2. The second is the linking together of all the "object" files, and thus, the creation of the final binary file (either a library or an executable).

    第二个是将所有“对象”文件链接在一起,从而创建最终的二进制文件(要么是一个库,要么是可执行文件)。

Where does the HPP fit in all this process?

HPP在这个过程中处于什么位置?

A poor lonesome CPP file...

The compilation of each CPP file is independent from all other CPP files, which means that if A.CPP needs a symbol defined in B.CPP, like:

每个CPP文件的编译是独立于所有其他CPP文件的,这意味着如果A。CPP需要一个在B中定义的符号。CPP,比如:

// A.CPP
void doSomething()
{
   doSomethingElse(); // Defined in B.CPP
}

// B.CPP
void doSomethingElse()
{
   // Etc.
}

It won't compile because A.CPP has no way to know "doSomethingElse" exists... Unless there is a declaration in A.CPP, like:

它不会编译,因为A。CPP没有办法知道“doSomethingElse”是否存在……除非在a中有声明。CPP,比如:

// A.CPP
void doSomethingElse() ; // From B.CPP

void doSomething()
{
   doSomethingElse() ; // Defined in B.CPP
}

Then, if you have C.CPP which uses the same symbol, you then copy/paste the declaration...

然后,如果你有C。使用相同符号的CPP,然后复制/粘贴声明…

COPY/PASTE ALERT!

Yes, there is a problem. Copy/pastes are dangerous, and difficult to maintain. Which means that it would be cool if we had some way to NOT copy/paste, and still declare the symbol... How can we do it? By the include of some text file, which is commonly suffixed by .h, .hxx, .h++ or, my preferred for C++ files, .hpp:

是的,有一个问题。拷贝/粘贴是危险的,而且很难维护。这意味着如果我们有办法不复制/粘贴,并且仍然声明这个符号,那将会很酷。我们怎么做呢?通过包含一些文本文件,通常是由.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
}

How does include work?

Including a file will, in essence, parse and then copy-paste its content in the CPP file.

包括一个文件,本质上是解析,然后复制粘贴在CPP文件中的内容。

For example, in the following code, with the A.HPP header:

例如,在以下代码中,使用A。高压泵头:

// A.HPP
void someFunction();
void someOtherFunction();

... the source B.CPP:

…源B.CPP:

// B.CPP
#include "A.HPP"

void doSomething()
{
   // Etc.
}

... will become after inclusion:

…后将成为包含:

// B.CPP
void someFunction();
void someOtherFunction();

void doSomething()
{
   // Etc.
}

One small thing - why include B.HPP in B.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.

在当前情况下,这是不需要的,而B。HPP具有doSomethingElse函数声明和B。CPP具有doSomethingElse函数定义(它本身就是一个声明)。但在更一般的情况下,B。HPP用于声明(和内联代码),没有对应的定义(例如,枚举、普通结构等),所以如果B需要包含include。CPP使用的是B.HPP的声明。总之,它是“好品味”的来源,包括默认它的标题。

Conclusion

The header file is thus necessary, because the C++ compiler is unable to search for symbol declarations alone, and thus, you must help it by including those declarations.

因此,头文件是必需的,因为c++编译器无法单独搜索符号声明,因此,您必须通过包含这些声明来帮助它。

One last word: You should put header guards around the content of your HPP files, to be sure multiple inclusions won't break anything, but all in all, I believe the main reason for existence of HPP files is explained above.

最后一个词:你应该在你的HPP文件的内容周围设置标题保护,确保多个包含不会破坏任何东西,但总的来说,我认为HPP文件存在的主要原因是上面解释的。

#ifndef B_HPP_
#define B_HPP_

// The declarations in the B.hpp file

#endif // B_HPP_

#3


74  

Because C, where the concept originated, is 30 years old, and back then, it was the only viable way to link together code from multiple files.

因为C,这个概念的起源,是30岁,而在那时,它是唯一可行的方法,将来自多个文件的代码链接在一起。

Today, it's an awful hack which totally destroys compilation time in C++, causes countless needless dependencies (because class definitions in a header file expose too much information about the implementation), and so on.

今天,它是一个可怕的hack,它完全破坏了c++的编译时间,导致了无数不必要的依赖(因为头文件中的类定义暴露了太多关于实现的信息),等等。

#4


50  

Because in C++, the final executable code does not carry any symbol information, it's more or less pure machine code.

因为在c++中,最终的可执行代码不带任何符号信息,它或多或少是纯机器码。

Thus, you need a way to describe the interface of a piece of code, that is separate from the code itself. This description is in the header file.

因此,您需要一种方法来描述一段代码的接口,这与代码本身是分开的。此描述位于头文件中。

#5


10  

Because the people who designed the library format didn't want to "waste" space for rarely used information like C preprocessor macros and function declarations.

因为设计库格式的人不希望“浪费”空间,因为很少使用像C预处理器宏和函数声明这样的信息。

Since you need that info to tell your compiler "this function is available later when the linker is doing its job", they had to come up with a second file where this shared information could be stored.

因为您需要这个信息来告诉编译器“这个函数在链接器正在执行任务时可用”,因此他们必须找到第二个文件,以便存储这些共享信息。

Most languages after C/C++ store this information in the output (Java bytecode, for example) or they don't use a precompiled format at all, get always distributed in source form and compile on the fly (Python, Perl).

在C/ c++之后,大多数语言将这些信息存储在输出中(例如,Java字节码),或者它们根本不使用预编译格式,总是以源代码的形式分发,并以“fly (Python, Perl)”的形式进行编译。

#6


10  

Because C++ inherited them from C. Unfortunately.

因为c++从C继承了它们。

#7


4  

It's the preprocessor way of declaring interfaces. You put the interface (method declarations) into the header file, and the implementation into the cpp. Applications using your library only need to know the interface, which they can access through #include.

它是声明接口的预处理器方式。将接口(方法声明)放入头文件中,并将实现放入到cpp中。使用您的库的应用程序只需要知道接口,它们可以通过#include访问该接口。

#8


4  

Often you will want to have a definition of an interface without having to ship the entire code. For example, if you have a shared library, you would ship a header file with it which defines all the functions and symbols used in the shared library. Without header files, you would need to ship the source.

通常,您需要对接口进行定义,而不需要发送整个代码。例如,如果您有一个共享库,那么您将发送一个头文件,其中定义了共享库中使用的所有函数和符号。如果没有头文件,则需要发送源代码。

Within a single project, header files are used, IMHO, for at least two purposes:

在单个项目中,使用了头文件,至少有两个目的:

  • Clarity, that is, by keeping the interfaces separate from the implementation, it is easier to read the code
  • 透明性,即通过将接口与实现分离开来,更容易阅读代码。
  • Compile time. By using only the interface where possible, instead of the full implementation, the compile time can be reduced because the compiler can simply make a reference to the interface instead of having to parse the actual code (which, idealy, would only need to be done a single time).
  • 编译时间。通过只使用可能的接口,而不是完整的实现,编译时间可以减少,因为编译器可以简单地对接口进行引用,而不必解析实际的代码(idealy,这只需要一次完成)。

#9


-4  

Responding to MadKeithV's answer,

应对MadKeithV的回答,

This reduces dependencies so that code that uses the header doesn't necessarily need to know all the details of the implementation and any other classes/headers needed only for that. This will reduce compilation times, and also the amount of recompilation needed when something in the implementation changes.

这减少了依赖关系,因此使用header的代码并不需要知道实现的所有细节,也不需要知道任何其他类/header。这将减少编译时间,以及在实现更改时需要的重新编译的数量。

Another reason is that a header gives a unique id to each class.

另一个原因是,header为每个类提供了惟一的id。

So if we have something like

如果我们有类似的东西。

class A {..};
class B : public A {...};

class C {
    include A.cpp;
    include B.cpp;
    .....
};

We will have errors, when we try to build the project, since A is part of B, with headers we would avoid this kind of headache...

当我们试图构建项目时,我们会有错误,因为A是B的一部分,我们会避免这种头痛…

#1


176  

Well, the main reason would be for separating the interface from the implementation. The header declares "what" a class (or whatever is being implemented) will do, while the cpp file defines "how" it will perform those features.

主要原因是将接口与实现分开。header声明“什么”类(或正在实现的任何内容)将会执行,而cpp文件将定义“如何”执行这些特性。

This reduces dependencies so that code that uses the header doesn't necessarily need to know all the details of the implementation and any other classes/headers needed only for that. This will reduce compilation times and also the amount of recompilation needed when something in the implementation changes.

这减少了依赖关系,因此使用header的代码并不需要知道实现的所有细节,也不需要知道任何其他类/header。这将减少编译时间,以及在实现更改时需要的重新编译的数量。

It's not perfect, and you would usually resort to techniques like the Pimpl Idiom to properly separate interface and implementation, but it's a good start.

它并不完美,您通常会使用Pimpl这样的技术来正确地分离接口和实现,但这是一个好的开始。

#2


512  

C++ compilation

A compilation in C++ is done in 2 major phases:

c++的编译主要分为两个阶段:

  1. The first is the compilation of "source" text files into binary "object" files: The CPP file is the compiled file and is compiled without any knowledge about the other CPP files (or even libraries), unless fed to it through raw declaration or header inclusion. The CPP file is usually compiled into a .OBJ or a .O "object" file.

    第一种方法是将“源”文本文件编译成二进制“对象”文件:CPP文件是编译后的文件,编译时不需要了解其他CPP文件(甚至是库),除非通过原始声明或标题包含。CPP文件通常被编译成. obj或. o“对象”文件。

  2. The second is the linking together of all the "object" files, and thus, the creation of the final binary file (either a library or an executable).

    第二个是将所有“对象”文件链接在一起,从而创建最终的二进制文件(要么是一个库,要么是可执行文件)。

Where does the HPP fit in all this process?

HPP在这个过程中处于什么位置?

A poor lonesome CPP file...

The compilation of each CPP file is independent from all other CPP files, which means that if A.CPP needs a symbol defined in B.CPP, like:

每个CPP文件的编译是独立于所有其他CPP文件的,这意味着如果A。CPP需要一个在B中定义的符号。CPP,比如:

// A.CPP
void doSomething()
{
   doSomethingElse(); // Defined in B.CPP
}

// B.CPP
void doSomethingElse()
{
   // Etc.
}

It won't compile because A.CPP has no way to know "doSomethingElse" exists... Unless there is a declaration in A.CPP, like:

它不会编译,因为A。CPP没有办法知道“doSomethingElse”是否存在……除非在a中有声明。CPP,比如:

// A.CPP
void doSomethingElse() ; // From B.CPP

void doSomething()
{
   doSomethingElse() ; // Defined in B.CPP
}

Then, if you have C.CPP which uses the same symbol, you then copy/paste the declaration...

然后,如果你有C。使用相同符号的CPP,然后复制/粘贴声明…

COPY/PASTE ALERT!

Yes, there is a problem. Copy/pastes are dangerous, and difficult to maintain. Which means that it would be cool if we had some way to NOT copy/paste, and still declare the symbol... How can we do it? By the include of some text file, which is commonly suffixed by .h, .hxx, .h++ or, my preferred for C++ files, .hpp:

是的,有一个问题。拷贝/粘贴是危险的,而且很难维护。这意味着如果我们有办法不复制/粘贴,并且仍然声明这个符号,那将会很酷。我们怎么做呢?通过包含一些文本文件,通常是由.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
}

How does include work?

Including a file will, in essence, parse and then copy-paste its content in the CPP file.

包括一个文件,本质上是解析,然后复制粘贴在CPP文件中的内容。

For example, in the following code, with the A.HPP header:

例如,在以下代码中,使用A。高压泵头:

// A.HPP
void someFunction();
void someOtherFunction();

... the source B.CPP:

…源B.CPP:

// B.CPP
#include "A.HPP"

void doSomething()
{
   // Etc.
}

... will become after inclusion:

…后将成为包含:

// B.CPP
void someFunction();
void someOtherFunction();

void doSomething()
{
   // Etc.
}

One small thing - why include B.HPP in B.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.

在当前情况下,这是不需要的,而B。HPP具有doSomethingElse函数声明和B。CPP具有doSomethingElse函数定义(它本身就是一个声明)。但在更一般的情况下,B。HPP用于声明(和内联代码),没有对应的定义(例如,枚举、普通结构等),所以如果B需要包含include。CPP使用的是B.HPP的声明。总之,它是“好品味”的来源,包括默认它的标题。

Conclusion

The header file is thus necessary, because the C++ compiler is unable to search for symbol declarations alone, and thus, you must help it by including those declarations.

因此,头文件是必需的,因为c++编译器无法单独搜索符号声明,因此,您必须通过包含这些声明来帮助它。

One last word: You should put header guards around the content of your HPP files, to be sure multiple inclusions won't break anything, but all in all, I believe the main reason for existence of HPP files is explained above.

最后一个词:你应该在你的HPP文件的内容周围设置标题保护,确保多个包含不会破坏任何东西,但总的来说,我认为HPP文件存在的主要原因是上面解释的。

#ifndef B_HPP_
#define B_HPP_

// The declarations in the B.hpp file

#endif // B_HPP_

#3


74  

Because C, where the concept originated, is 30 years old, and back then, it was the only viable way to link together code from multiple files.

因为C,这个概念的起源,是30岁,而在那时,它是唯一可行的方法,将来自多个文件的代码链接在一起。

Today, it's an awful hack which totally destroys compilation time in C++, causes countless needless dependencies (because class definitions in a header file expose too much information about the implementation), and so on.

今天,它是一个可怕的hack,它完全破坏了c++的编译时间,导致了无数不必要的依赖(因为头文件中的类定义暴露了太多关于实现的信息),等等。

#4


50  

Because in C++, the final executable code does not carry any symbol information, it's more or less pure machine code.

因为在c++中,最终的可执行代码不带任何符号信息,它或多或少是纯机器码。

Thus, you need a way to describe the interface of a piece of code, that is separate from the code itself. This description is in the header file.

因此,您需要一种方法来描述一段代码的接口,这与代码本身是分开的。此描述位于头文件中。

#5


10  

Because the people who designed the library format didn't want to "waste" space for rarely used information like C preprocessor macros and function declarations.

因为设计库格式的人不希望“浪费”空间,因为很少使用像C预处理器宏和函数声明这样的信息。

Since you need that info to tell your compiler "this function is available later when the linker is doing its job", they had to come up with a second file where this shared information could be stored.

因为您需要这个信息来告诉编译器“这个函数在链接器正在执行任务时可用”,因此他们必须找到第二个文件,以便存储这些共享信息。

Most languages after C/C++ store this information in the output (Java bytecode, for example) or they don't use a precompiled format at all, get always distributed in source form and compile on the fly (Python, Perl).

在C/ c++之后,大多数语言将这些信息存储在输出中(例如,Java字节码),或者它们根本不使用预编译格式,总是以源代码的形式分发,并以“fly (Python, Perl)”的形式进行编译。

#6


10  

Because C++ inherited them from C. Unfortunately.

因为c++从C继承了它们。

#7


4  

It's the preprocessor way of declaring interfaces. You put the interface (method declarations) into the header file, and the implementation into the cpp. Applications using your library only need to know the interface, which they can access through #include.

它是声明接口的预处理器方式。将接口(方法声明)放入头文件中,并将实现放入到cpp中。使用您的库的应用程序只需要知道接口,它们可以通过#include访问该接口。

#8


4  

Often you will want to have a definition of an interface without having to ship the entire code. For example, if you have a shared library, you would ship a header file with it which defines all the functions and symbols used in the shared library. Without header files, you would need to ship the source.

通常,您需要对接口进行定义,而不需要发送整个代码。例如,如果您有一个共享库,那么您将发送一个头文件,其中定义了共享库中使用的所有函数和符号。如果没有头文件,则需要发送源代码。

Within a single project, header files are used, IMHO, for at least two purposes:

在单个项目中,使用了头文件,至少有两个目的:

  • Clarity, that is, by keeping the interfaces separate from the implementation, it is easier to read the code
  • 透明性,即通过将接口与实现分离开来,更容易阅读代码。
  • Compile time. By using only the interface where possible, instead of the full implementation, the compile time can be reduced because the compiler can simply make a reference to the interface instead of having to parse the actual code (which, idealy, would only need to be done a single time).
  • 编译时间。通过只使用可能的接口,而不是完整的实现,编译时间可以减少,因为编译器可以简单地对接口进行引用,而不必解析实际的代码(idealy,这只需要一次完成)。

#9


-4  

Responding to MadKeithV's answer,

应对MadKeithV的回答,

This reduces dependencies so that code that uses the header doesn't necessarily need to know all the details of the implementation and any other classes/headers needed only for that. This will reduce compilation times, and also the amount of recompilation needed when something in the implementation changes.

这减少了依赖关系,因此使用header的代码并不需要知道实现的所有细节,也不需要知道任何其他类/header。这将减少编译时间,以及在实现更改时需要的重新编译的数量。

Another reason is that a header gives a unique id to each class.

另一个原因是,header为每个类提供了惟一的id。

So if we have something like

如果我们有类似的东西。

class A {..};
class B : public A {...};

class C {
    include A.cpp;
    include B.cpp;
    .....
};

We will have errors, when we try to build the project, since A is part of B, with headers we would avoid this kind of headache...

当我们试图构建项目时,我们会有错误,因为A是B的一部分,我们会避免这种头痛…