C++中包含双标题

Double header inclusion in C++?

本文关键字:标题 包含双 C++      更新时间:2023-10-16

我有一个看似相对简单的问题,但我一直在努力理解它。

如果这是一个简单的问题,

我很抱歉,但就像许多简单的问题一样,我似乎在任何地方都找不到可靠的解释。

使用以下代码:

/*foo.c*/
#include "bar.h"
int main() {
   return(my_function(1,2));
}
/*bar.h*/
int my_function(int,int);
/*bar.c*/
#include "bar.h" /*is this necessary!?*/
int my_function(int x, int y) {
    return(x+y);
}

简单地说,第二次包含是必要的吗?我不明白为什么我总是看到两个源文件中都包含标题。当然,如果函数是通过包含"bar.h"在"foo.c"中声明的,则不需要在另一个链接的源文件(尤其是实际定义它的源文件)中再次声明它???一位朋友试图向我解释,它对函数来说并不重要,但对于结构体来说却很重要,这对我来说仍然遥不可及!帮助!

是否只是为了清楚起见,以便程序员可以看到哪些功能正在外部使用?

我就是不明白!

谢谢!

在这种特殊情况下,由于您描述的原因,这是不必要的。在具有一组可能全部相互依赖的更复杂的函数的情况下,它可能很有用。如果在.cpp文件的顶部包含标头,则可以有效地向前声明每个函数,因此不必担心确保函数定义按特定顺序排列。

我还发现它清楚地表明这些函数定义对应于这些声明。这使读者更容易找到翻译单元如何相互依赖。当然,文件名可能就足够了,但一些更复杂的项目在.cpp文件和.h文件之间没有一对一的关系。有时标头被分解为多个部分,或者许多实现文件将在单个标头中声明其外部函数(对于大型模块很常见)。

真的,所有的内含物都是不必要的。毕竟,您始终可以在所有需要它们的文件中复制声明(或定义,如果是类)。我们使用预处理器来简化此任务并减少冗余代码的数量。坚持始终包含相应标头的模式更容易,因为它将始终有效,而不必在每次编辑文件时检查每个文件并确定是否有必要包含。

C 语言(和C++)的设计方式是编译器单独处理每个 .c 文件。

您通常会为其中一个 c 文件启动编译器(例如 cl.exe 或 gcc),这会生成一个对象文件(.o 或 .obj)。

生成所有对象文件后,运行链接器,将所有对象文件传递给它,它会将它们绑定到可执行文件中。

这就是为什么每个.c文件都需要包含它所依赖的标头。当编译器处理它时,它不知道您可能拥有哪些其他 .c 文件。它所知道的只是您指向的 .c 文件的内容,以及它包含的标头。

在您的简化示例中,没有必要在"bar.c"中包含"bar.h"。但在现实世界中,在大多数情况下,情况会如此。如果你在 "bar.h" 中有一个类声明,并且 "bar.c" 具有这个类的函数,则需要包含。如果您有任何其他在"bar.c"中使用的声明 - 它是一个常量,枚举等 - 再次包含是需要的。因为在现实世界中几乎总是需要它,所以简单的规则是 - 始终将头文件包含在相应的源文件中。

如果标头仅声明全局函数,而源文件仅实现它们(不调用任何函数),则并非绝对必要。但通常情况并非如此;在大型程序中,您很少需要全局函数。

如果标头定义了一个类,则需要将其包含在源文件中才能定义成员函数:

void Thing::function(int x) {
   //^^^^^^^ needs class definition
}

如果标头在命名空间中声明函数,则最好将定义放在命名空间之外:

void ns::function(int x) {
   //^^^^ needs previous declaration
}

如果参数类型与之前的声明不匹配,这将给出一个很好的编译时错误 - 为此您需要包含标头。在其命名空间中定义函数

namespace ns {
    void function(int x) {
        // ...
    }
}

如果参数类型错误,将静默声明新的重载。

简单的规则是这样的(考虑到foo是某个类的成员函数):-

因此,如果某个头文件声明了一个函数,则说:=

//foo.h
void foo (int x);
编译器需要在你定义此函数的任何位置看到此声明(以确保

您的定义与声明一致)并且您正在调用此函数(以确保您使用正确的参数数量和类型调用该函数)。

这意味着您必须在调用该函数的所有位置以及为该函数提供定义的位置包含foo.h

此外,如果foo是一个全局函数(不在任何命名空间内),那么就没有必要在实现文件中包含该foo.h