C ++头文件问题

时间:2010-06-26 18:55:25

标签: c++ header

我在使用类时尝试了一些c ++代码,这个问题发生在我身上,这让我感到烦恼。

我创建了一个包含我的类定义的头文件和一个包含该实现的cpp文件。

如果我在不同的cpp文件中使用此类,为什么我要包含头文件而不是包含类实现的cpp文件?

如果我包含类实现文件,那么应该自动导入类头文件(因为我已经将头文件包含在实现文件中)?这不是更自然吗?

对不起,如果这是一个愚蠢的问题,我真的很想知道为什么大多数人包含.h而不是.cpp文件,当后者看起来更自然时(我知道python有点,也许这就是为什么它对我来说似乎很自然)。是仅仅是历史还是有关于计划组织的技术原因还是其他什么?

3 个答案:

答案 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的用法一致(即intfloat并返回int)。它的方式是让你在两个.cpp文件中包含相同的头文件,如果定义和用法都与头中的声明一致,那么它们必须彼此一致。

但编译器实际上并未在foo()中包含bar.o的实现。它只包含call foo的汇编语言指令。因此,当它创建bar.o时,它不需要知道foo.cpp的内容。但是,当您进入链接阶段(在编译之后发生)时,链接器实际上确实需要了解foo()的实现,因为它将在最终程序中包含该实现并替换{{1使用call foo的指令(或者它决定函数call 0x109d9829的内存地址应该是什么)。

请注意,链接器检查foo()foo())的实现是否与使用foo.o一致(foo() }) - 例如,它不会检查bar.ofoo()参数是否调用了int!使用汇编语言进行这种检查很困难(至少比检查C ++源代码更难),因此链接器依赖于知道编译器已经检查过它。这就是为什么你需要头文件,以便将这些信息提供给编译器。

答案 1 :(得分:1)

魔术是由链接器完成的。编译时每个.cpp都会生成一个中间目标文件,其中包含表中的所有导出和导入符号。链接器将协调它们。换句话说,您只需要包含标题,每次引用包含的类时,编译器都会将引用类的签名放在符号表中。

如果你包含.cpp文件,你将会编译两次相同的代码,你会得到链接错误,因为链接器将找到两次相同的符号,因此它将是不明确的。

答案 2 :(得分:0)

一个技术原因是编译速度。假设您的类使用其他10个类(例如,作为成员变量的类型)。包括所有10个类的长.cpp文件会使你的类编译得慢得多(即可能是2秒而不是1秒)。

另一个原因是隐藏了实现。假设您正在编写一个类,供您公司的其他10个团队使用。他们必须知道和了解你的课程的所有内容都在.h文件(公共界面)中。您可以在.cpp文件(实现)中随意执行任何操作,您可以随意更改它,他们也不会关心。但是如果您更改.h文件,他们可能需要使用您的类调整其代码。

对于每个方法体,您可以选择将其放入.h文件还是.cpp文件。如果它在.h文件中,编译器可以在调用时对其进行内联,这可能会使代码更快一些。但编译速度会慢一些,而临时的.o(.obj)文件可能会变大(因为每个文件都包含已编译的方法体),而程序二进制文件(.exe)可能会变大,因为函数体占用空间很多次都是内联的。