【问题标题】:When do you need to use #include in c++什么时候需要在c++中使用#include
【发布时间】:2026-01-11 02:35:01
【问题描述】:

我已经编程了一段时间,但我仍然没有完全弄清楚的一件事就是你什么时候需要#include 一些东西。我知道为了安全起见,只要您使用在另一个文件中声明的内容,您就可以这样做。但是有时我发现我可以删除 #include 并且一切仍然可以正常编译。据我所知,这是因为已包含的其他文件已包含外部定义。我有兴趣了解以下两种特殊情况:

  1. 假设我们有三个 .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 或者 f1.h 的所有定义对f3 包含在 f2 中时的文件?

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

您是否知道有什么好的在线资源可供我阅读以更好地了解在一个文件中包含某些内容如何影响项目中的后续文件?

【问题讨论】:

  • 在我看来,您已经了解了大部分内容。其余的可以通过相当简单的编译时测试来确定,我强烈建议您尝试而不是在这里询问。如果失败或您不明白发生了什么,请回来询问。
  • 注意,我认为这是一个非常有效的问题。我只是建议你自己做这个会学得更好。
  • 我认为只在文件中包含所需的文件是正确的。如果由于某种原因其他头文件中的包含更改,您必须再次手动执行此操作。因此,包括更多似乎并没有错。您还可以查看#pragma once,有时这是一个方便的命令。
  • -1。我看不出有人如何“赞成质疑”这个问题。如果提问者检查“c++ 中的翻译单元”,这个问题就可以避免。此外,这就像问“'if'是什么?”......类似的东西。如果答案不明显,您应该返回并查看基础知识。没有冒犯,但实际上,我试着读了好几遍,显然看不到这本书的亮点。
  • @Poni 我是一名专业的 C++ 程序员,我理解这会让初学者感到困惑。当您进入超出最基本的复杂性的项目时,这可能会很快变得混乱。这当然是一个非常初学者的问题,但这并不意味着它不适合问,当然也不像 if() 那样简单。

标签: c++ include c-preprocessor


【解决方案1】:

你已经搞定了。 为了使用我需要的输入/输出流 为此包含头文件。 如果您编写了一个支持大整数类型的 bigint 类并且您发送了 这堂课给朋友。您的朋友需要将其包含在他的程序中才能使用它。因此,当您的程序不可用时,您可以包含一些内容。

【讨论】:

    【解决方案2】:

    但有时我发现我可以 删除#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 或将全部 f1.h 的定义对 f3 文件包含在 f2?

    您可能指的是f1.h 的声明,而不是定义,尽管其中可能有一些类和模板函数定义。

    无论如何,答案是否定的,永远没有必要。这些声明将是可见的。预处理器指令只是简单的文本插入。一旦你在脑海中想象出来,扩展就很容易理解了。

    再次说我们有三个 .h/.cc 对:f1.h/.cc、f2.h/.cc 和 f3.h/.cc。如果 f2 包括 f1 和 f2 包括 f1 然后 f3 包括 f1 或 f2 将之间的“循环联系” f1 和 f2 有问题吗?

    是的,可能。如果头文件的 contents 是健全的,Header guards 可以缓解这种情况。但是如果 f2 的 contents 依赖于 f1 的 contents ,反之亦然,那么你在代码中有一个循环依赖。这应该通过删除循环依赖来解决,usingforward declarations

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

    我可以推荐these resources

    【讨论】:

      【解决方案3】:

      首先是简短的答案,然后是解释:

      1) 没有必要在 f3.h/.cc 中声明 #include "f1.h" 因为这会形成一个包含循环(不应该这样做)

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

      该实现是特定于编译器的,但一般来说:在菱形包含中,一个好的编译器会展平包含路径并找到最深层次的文件,即包含在某处但不需要包含其他任何内容的文件。然后它将不断地包含那些被包含的文件,并且只依赖于已经包含的文件。由于这可能导致重复包含,因此您应该始终只包含标题(.cc 将从相同的目录链接而没有明确包含)并且还使用包含保护来保护您的头文件:

      例如myheader.h:

      #ifndef _myheader_h_
      #define _myheader_h_
      int myglobal;
      #endif
      

      如果您的包含中有一个循环,则取决于编译器:它要么尝试找到最深的级别,要么失败或选择,要么根本不尝试选择最深的级别并包含在顺序中你提供了你的文件;无论如何:如果您避免循环(您应该)并使用包含保护,那么您是安全的。

      【讨论】:

        【解决方案4】:

        没什么大不了的。如果您使用某些东西,则必须包含声明您使用的东西的标题。唯一的例外是 forward declaring 类/结构或方法,例如:

        class myclass;   
        

        如果您只需要声明类的指针或引用。

        你不能真的依赖其他标题,包括你需要的标题,碰巧。任何一天,其他标头的维护者都会意识到他/她不再需要该包含并将其删除。

        【讨论】:

        • +1,永远不要依赖别人做你的工作;如果您需要某些东西,请自己提供。
        • 另外,如果你想声明按值获取或返回myclass 的函数。
        【解决方案5】:

        问题 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 的标题,它可能会导致问题,因为 include 不是“链接”,它几乎是另一个头文件内容的剪切和粘贴。

        因此,即使在 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 相同的问题。

        【讨论】:

          最近更新 更多