我在使用类时尝试了一些c ++代码,这个问题发生在我身上,这让我感到烦恼。
我创建了一个包含我的类定义的头文件和一个包含该实现的cpp文件。
如果我在不同的cpp文件中使用此类,为什么我要包含头文件而不是包含类实现的cpp文件?
如果我包含类实现文件,那么应该自动导入类头文件(因为我已经将头文件包含在实现文件中)?这不是更自然吗?
对不起,如果这是一个愚蠢的问题,我真的很想知道为什么大多数人包含.h而不是.cpp文件,当后者看起来更自然时(我知道python有点,也许这就是为什么它对我来说似乎很自然)。是仅仅是历史还是有关于计划组织的技术原因还是其他什么?
答案 0 :(得分:13)
因为在编译另一个文件时,C ++实际上并不需要知道实现。它只需要知道每个函数的签名(它需要参数和它返回的内容),每个类的名称,哪些宏是#define
d,以及其他“摘要”信息这样,它可以检查你正确使用函数和类。在链接器运行之前,不会将不同.cpp
文件的内容放在一起。
例如,假设您有foo.h
int foo(int a, float b);
和foo.cpp
#include "foo.h"
int foo(int a, float b) { /* implementation */ }
和bar.cpp
#include "foo.h"
int bar(void) {
int c = foo(1, 2.1);
}
编译foo.cpp
时,它变为foo.o
,编译bar.cpp
时,它变为bar.o
。现在,在编译过程中,编译器需要检查foo()
中函数foo.cpp
的定义是否与foo()
中函数bar.cpp
的用法一致(即int
和float
并返回int
)。它的方式是让你在两个.cpp
文件中包含相同的头文件,如果定义和用法都与头中的声明一致,那么它们必须彼此一致。
但编译器实际上并未在foo()
中包含bar.o
的实现。它只包含call foo
的汇编语言指令。因此,当它创建bar.o
时,它不需要知道foo.cpp
的内容。但是,当您进入链接阶段(在编译之后发生)时,链接器实际上确实需要了解foo()
的实现,因为它将在最终程序中包含该实现并替换{{1使用call foo
的指令(或者它决定函数call 0x109d9829
的内存地址应该是什么)。
请注意,链接器不检查foo()
(foo()
)的实现是否与使用foo.o
一致(foo()
}) - 例如,它不会检查bar.o
和foo()
参数是否调用了int
!使用汇编语言进行这种检查很困难(至少比检查C ++源代码更难),因此链接器依赖于知道编译器已经检查过它。这就是为什么你需要头文件,以便将这些信息提供给编译器。
答案 1 :(得分:1)
魔术是由链接器完成的。编译时每个.cpp都会生成一个中间目标文件,其中包含表中的所有导出和导入符号。链接器将协调它们。换句话说,您只需要包含标题,每次引用包含的类时,编译器都会将引用类的签名放在符号表中。
如果你包含.cpp文件,你将会编译两次相同的代码,你会得到链接错误,因为链接器将找到两次相同的符号,因此它将是不明确的。
答案 2 :(得分:0)
一个技术原因是编译速度。假设您的类使用其他10个类(例如,作为成员变量的类型)。包括所有10个类的长.cpp文件会使你的类编译得慢得多(即可能是2秒而不是1秒)。
另一个原因是隐藏了实现。假设您正在编写一个类,供您公司的其他10个团队使用。他们必须知道和了解你的课程的所有内容都在.h文件(公共界面)中。您可以在.cpp文件(实现)中随意执行任何操作,您可以随意更改它,他们也不会关心。但是如果您更改.h文件,他们可能需要使用您的类调整其代码。
对于每个方法体,您可以选择将其放入.h文件还是.cpp文件。如果它在.h文件中,编译器可以在调用时对其进行内联,这可能会使代码更快一些。但编译速度会慢一些,而临时的.o(.obj)文件可能会变大(因为每个文件都包含已编译的方法体),而程序二进制文件(.exe)可能会变大,因为函数体占用空间很多次都是内联的。