在C++中,什么时候需要使用#include?

6
我已经编程了一段时间,但仍然不太清楚何时需要使用 #include。我知道为了安全起见,你可以在使用另一个文件中声明的东西时使用它。但有时我发现我可以删除 #include,但所有内容仍然能够成功编译。据我所知,这是因为其他已包含的文件已经包含了外部定义。我对了解以下两种情况的行为非常感兴趣:
  1. 假设我们有三个.h和.cc配对:f1.h/.cc、f2.h/.cc和f3.h/.cc。如果f2.h/.cc包括f1.h,f3.h/.cc包括f2.h,是否必须包括f1.h或者当它被包含在f2中时,f1.h的所有定义都将对f3文件可见?

  2. 再次假设我们有三个.h和.cc配对:f1.h/.cc、f2.h/.cc和f3.h/.cc。如果f2包括f1并且f2还包括f1,然后f3包括f1或f2,那么f1和f2之间的“循环链接”会导致问题吗?

你知道任何好的在线资源可以阅读来更好地理解在一个文件中包含某些内容如何影响项目中的后续文件吗?

听起来你已经解决了大部分问题。其余的可以通过相当简单的编译时测试来确定,我强烈建议你尝试这种方法,而不是在这里询问。当这种方法失败或者你不理解发生了什么时,请回来提问。 - San Jacinto
注意,我认为这是一个非常有价值的问题。我只是建议你自己动手去做,这样你会学得更好。 - San Jacinto
我认为在文件中只包含所需的文件是正确的做法。如果其他头文件中的包含发生更改,您必须手动再次执行此操作。因此,包含更多文件似乎并没有错。有时候,您还可以查看#pragma once,这是一个方便的命令。 - Marnix
我不明白为什么会有人“加精”这个问题。如果提问者事先查一下关于“C++中的翻译单元”方面的内容,这个问题就可以避免了。这有点像问“if是什么?”之类的问题……如果对此的答案不明显,那就应该回去重温基础知识了。不是有意冒犯,但实际上,我已经试着读了几遍,没能像其他人一样看出亮点。 - Poni
@Poni 我是一名专业的C++程序员,我理解对于初学者来说这可能会很困惑。当你开始处理比最基本的更复杂的项目时,这可能会很快变得混乱。这确实是一个非常初学者的问题,但这并不意味着提问是不合适的,也绝不像if()那么简单。 - San Jacinto
5个回答

6

其实很简单。如果你使用了某个东西,就必须包含声明你所使用的东西的头文件。唯一的例外是前向声明一个类/结构体或方法,像这样:

class myclass;   

如果你只需要声明一个指向类的指针或引用。

你不能真正依赖于其他头文件包含你需要的头文件。任何一天,其他头文件的维护者会意识到他/她不再需要那个包含文件并将其删除。


3
+1,永远不要依赖别人来完成你的工作;如果你需要什么东西,就自己把它包含进去。 - casablanca
1
此外,如果您想声明以值传递或返回 myclass 的函数。 - Puppy

2
问题1:我认为你缺少的是“f2包括f1”和“f2保证包括f1”的区别。这在标准头文件中尤其重要,因为任何标准头文件都允许包含任何其他标准头文件。因此,如果你依赖于在你的机器上工作的间接包含,那么你的代码可能无法在不同的C++实现上编译。
如果你有一个库,其中“f2.h”的文档说或暗示它包含“f1.h”,那么在所有兼容版本中它始终会这样做,那么你就可以依赖于间接包含。你可能会在使用库的一个组件时这样做,该组件基本上依赖于该库的另一个组件,但是其他用户可能会将该组件隔离使用。举个假设的例子,“xhtml_parser.h”可以合理地说明它提供了“xml_parser.h”的所有定义,以及一些额外的定义。
问题2:嗯,你想重新表达问题吗?“f2包括f1和f2包括f1”不是你的意思,也没有“循环链接”。如果你编写头文件使得f1包含f2和f2包含f1,那么即使在f3出现之前,循环包含也可能会导致问题,因为包含不是“链接”,而是将其他头文件的内容剪切并粘贴到当前头文件中。
因此,即使在f3出现之前,循环包含也可能会导致问题:
f1.h
----
#ifndef f1_h_included
#define f1_h_included

#include "f2.h"
struct DerivedA : BaseA {};
struct BaseB {};

#endif

f2.h
----
#ifndef f2_h_included
#define f2_h_included

#include "f1.h"
struct BaseA {};
struct DerivedB : BaseB {};

#endif

无论你包含"f1.h"还是"f2.h",这段代码都无法编译。假设先包含f1,预处理后的结果如下:
// contents of f2.h, pasted in at line 4 of f1.h
// (contents of f1.h on the circular include are ignored due to include guard)
struct BaseA {};
struct DerivedB : BaseB {};

// rest of f1.h
struct DerivedA : BaseA {};
struct BaseB {};

因此,DerivedB指定了一个尚未定义的基类。如果以相反的方式包含它们,则DerivedA也存在同样的问题。

1

你已经掌握得相当不错了。 为了使用输入/输出流,我需要包含这个头文件。 如果你编写了一个支持大整数类型的bigint类,并将其发送给朋友。你的朋友需要在他的程序中包含这个类才能使用它。所以当你的程序中没有可用的东西时,你就需要包含它。


1
然而有时候我发现我可以删除一个#include,一切都能够正常编译。据我所知,这是因为其他已经被包含的文件已经包含了外部定义。
正确。这只是运气好,有点像。
假设我们有三个.h/.cc对:f1.h/.cc、f2.h/.cc和f3.h/.cc。如果f2.h/.cc包含f1.h,f3.h/.cc包含f2.h,那么f3.h/.cc是否需要包含f1.h,或者当它被包含在f2中时,所有f1.h的定义都将对f3文件可见?
你可能指的是f1.h的声明,而不是定义,尽管你可能在其中有一些类和模板函数定义。
无论如何,答案是否定的。这些声明将是可见的。预处理器指令只是简单的文本插入。一旦你在脑海中形象化了这一点,扩展就变得容易理解了。
再说一遍,假设我们有三个.h/.cc文件对:f1.h/.cc、f2.h/.cc和f3.h/.cc。如果f2包含了f1,f2同时又包含了f3中的f1或f2,那么f1和f2之间的“循环链接”会导致问题吗?
是的,可能会。头文件保护可以缓解此问题,前提是头文件的内容是明智的。但是,如果f2的内容依赖于f1的内容,反之亦然,则代码中存在循环依赖。这应该通过消除循环依赖使用 前向声明来解决。
你知道有什么好的在线资源我可以阅读以更好地理解在一个文件中包含某些内容如何影响项目中随后的文件吗?

我可以推荐这些资源


0

首先是简短的回答,接下来是解释:

1)在f3.h/.cc中声明#include“f1.h”从来不是必要的,因为这会造成循环包含(不应该这样做)

2)在1->2、1->2->3中没有循环,即使你使用了一个包含保护(#ifndef 2 #define 2 my2code #endif),2也只会被包含一次

实现是与编译器相关的,但通常情况下:在菱形包含中,一个好的编译器将展开包含路径并找到最深层级的文件,即某个被包含但不需要包含其他任何文件的文件。然后它将连续包含那些已经被包含且仅依赖于已经被包含的文件。由于这可能导致重复包含,因此您应该始终只包含头文件(.cc文件将从相同目录链接而无需显式包含),并使用包含保护来保护您的头文件:

例如,myheader.h:

#ifndef _myheader_h_
#define _myheader_h_
int myglobal;
#endif

如果您的包含文件中存在循环依赖,那么这取决于编译器:它要么尝试找到最深层级并失败或选择,要么根本不尝试选择最深层级,并按照您在文件中给出的顺序进行包含。无论如何,如果您避免循环依赖(您应该这样做)并使用包含保护,那么您就是安全的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接