【问题标题】:Why put function declarations and definitions in separate files?为什么将函数声明和定义放在单独的文件中?
【发布时间】:2026-02-01 15:30:02
【问题描述】:

在 codeacademy 的函数课程中,如果你要在程序中调用函数,他们会教你使用三个文件:

  • int main() 文件是我通过反复试验发现的,它是 c++ 程序(我猜...)的程序部分不可或缺的一部分,文件扩展名为 .cpp

  • 用于 DECLARING 函数的头文件,文件扩展名为 .hpp。

  • 带有函数定义的单独文件,文件扩展名为 .cpp

在头文件中单独声明和定义函数并将它们简单地包含在int main()之上是否可以工作?对我来说,声明和定义的单独文件似乎会混淆大型项目中的问题。

【问题讨论】:

  • 在工作中,我编写了 50+ 千行长的程序。想象一下,如果所有东西都在一个文件中,编译需要多长时间。我绝对不会做任何事情。
  • 其实恰恰相反。即使是一个中等规模的项目也可能有几十个.cpp 文件,其中有几个调用函数由另一个源文件定义。要调用一个函数,它必须被声明,但是(除了像内联函数这样的特殊情况,不应该乱用)在整个项目中不能多次定义函数。仅将头文件用于声明允许每个源文件包含每个头文件而不是需要它。在标头中定义函数可防止该标头被项目中的多个源文件包含。
  • @drescherjm 是的。我处理的应用程序大约有 600k 行代码,如果所有内容都在标头中,那么构建将是一场噩梦。目前,在 20 核/40 线程机器上完整构建大约需要 5 分钟,但过去需要 10 分钟以上,并且编译时间的减少主要是通过将内容移出标头并删除不需要的包含来实现的。它在大型项目中产生了巨大的影响。
  • @dresherjm: GCC 有一些 C++ 文件,包含数万行 C++。它有效,人们正在为它做出贡献。比如它的gcc/go/gofrontend/expressions.cc在GCC 10.1中有19711行,每天都是手写和编译的。

标签: c++ syntax header declaration definition


【解决方案1】:

在大型项目中,您通常只需要类型和函数声明,而不需要定义。例如在 other 头文件中。如果所有定义都在头文件中,那么包含多个其他头文件及其传递包含的组合结果将导致 huge 编译单元。这显着会影响编译时间,因为编译器需要处理的代码量会比需要的数量级激增。这也会影响链接时间,因为链接器在丢弃更多目标文件中包含的重复项方面需要做更多的工作。

除非所有内容都标记为 inline,否则您也很容易遇到 ODR(单一定义规则)问题。

【讨论】:

    【解决方案2】:

    在大型项目中,许多文件可能需要函数声明,但函数定义应该只编译一次。它在链接时与所有需要它的地方结合在一起。

    【讨论】:

      【解决方案3】:

      一个小的 C++ 程序可以是(并且通常是由一个)translation unit 组成的,例如几千行 C++ 代码。在这种情况下,您可以拥有一个 myprog.cc C++ 源文件(其中包含多个 #include-s)。

      但是当您在团队中处理更大的程序时,拥有多个 C++ 源文件会很方便。

      一些 C++ 文件是由另一个程序(称为 metaprogrammingsource to source compilation)生成的,并且可能包含一百万行 C++ 行。 ANTLRGNU BisonTypeScript2Cxx 能够生成 C++ 代码。

      但是如果你在一个像 Alice 和 Bob 这样的人的团队中工作,可以很方便地确定 Alice 负责 alice.cc 而 Bob 编写 bob.cc,并且他们在一个共同的头文件 header.hh 上进行合作。在alice.ccbob.cc 中都是#include-d。 header.hh 实际上定义了软件项目的 API

      详细了解version control 系统(我更喜欢git)和build automation 工具(例如ninjamake)。

      gitlabgithub 或其他地方的现有open source 项目的C++ 代码中寻找灵感(特别是在ClangGCC 的源代码中,它们都是主要的C++ 编译器) .

      FWIW,在GCC 10.1(2020 年 5 月)中,gcc/go/gofrontend/expressions.cc 文件是手写的,有 19711 行 C++ 代码,因此将近两万行。它们每天都会编译。我确实知道从事这方面工作的人,他们是才华横溢的专业人士。 FTLK 1.4 的最大文件是它的 src/Fl_Text_Display.cxx,有 4175 行 C++ 行。

      根据个人经验,您可能有一个包含几十万行 C++ 的 C++ 函数(这仅在生成该 C++ 代码时才有意义),但是 optimizing compiler 的编译时间是劝阻的。您可以调整我的manydl.c 程序以生成任意大小的 C++ 文件(它当前生成具有“可调”大小功能的“随机”C 文件)。但FluidQt Designer 生成的C++ 代码可能相当大,而GUI 生成的C++ 代码通常由长但概念上简单的函数组成。

      C++11 标准(参见n3337)中的任何内容都不需要多个翻译单元。您可能拥有(参见 sqlite 示例)一个包含一百万行的 C++ 文件 foo.cc。您可以生成一些 C++ 源代码。 Qt 项目,GCC 编译器。 Jacques Pitrat 关于人工生物:有意识机器的良心 ISBN 978-1848211018 在许多页面中解释了为什么这种方法值得。

      【讨论】:

        【解决方案4】:

        这个问题有两种答案

        1. 程序员为什么要将内容拆分为多个 .h/.hpp 和 .cpp 文件?

          我相信这里的答案是,当您的 .cpp 文件变得非常大且包含许多可能与需要提供文件提供的功能的人无关的代码时,它可以帮助组织。这是一个例子:

          假设您有一些在屏幕上显示图像的 c++ 代码。作为想要使用该代码的人,您可能对该代码公开的函数/类感兴趣,这些函数/类可以让您控制该功能。也许代码公开了以下有用的功能:

          • WriteImageToScreen(int position_x, int position_y)
          • ClearScreen()

          查看头文件会容易得多,它只告诉您允许使用什么,而不是所有这些是如何实现的。实现这两个函数以便您可以调用它们很可能需要 1000 行代码和一堆您不关心的变量和语句。不必阅读这有助于您专注于代码的重要部分。您想与之交互的片段。

          我已经介绍了这个示例,就好像您正在调用其他人的代码一样,但同样适用于您自己的代码。随着您的项目变得越来越大,对文件公开的每个功能进行摘要会很方便。

          综上所述,并不是每个人都同意这是做事的正确方式或者它是有帮助的。

        2. 为什么编译器需要将内容拆分为多个 .h/.hpp 和 .cpp 文件?

          如果您不熟悉这个术语,编译器就是将您的源代码文本转换为您的计算机可以执行的程序的程序。

          那么为什么编译器需要单独的 .hpp/.cpp 文件呢?其他人几乎已经用这个一针见血了,但是如果某些东西被多次定义,c++ 编译器会感到困惑。如果您将所有内容都放在头文件中,那么当您将该头文件包含在多个文件中时,它将被定义多次。所以本质上这又回到了组织问题。

          我见过只有一个主文件的程序员,然后在编译期间所有代码都直接包含在该主文件中

        #include "SomeFile.cpp"
        #include "AnotherFile.cpp"
        // ...
        #include "SoManyFiles.cpp"
        
        int main()
        {
           DoStuff();
        }
        

        我相信这被称为单体构建,不推荐。

        【讨论】:

          【解决方案5】:

          如果你有一个玩具项目,你可以。

          如果您有 1,000,000 行代码,那么您的构建时间将会很糟糕。

          C++20 引入了模块,这应该可以解决整个问题。

          其他语言具有可以从“模块”中提取接口的工具。 希望当 C++20 到来时,工具将可用。

          将接口与实现分开的唯一充分理由是,如果存在 是一个接口的多种实现。例如。 VHDL,并将在 C++20 模块中提供。务实的原因是编译速度和易读性。

          【讨论】:

          • 我看到构建时间问题是答案中经常提到的一个问题,写“巨大”(好吧,对我来说只是这样,因为我制作的最大程序大约有250 行...)这样的程序是我完全不熟悉的整个领域。所以,如果我有一个包含 100 万行代码的文件,构建时间会有多可怕(估计就好了,谢谢...)
          • 一百万行项目将被拆分成数百或数千个 .h 和 .cpp 文件。我应该说“增量”构建。进行修改时,仅重新编译涉及的部分,然后链接整个批次。如果一个 mod 只涉及一个 .cpp,则只重新编译一个文件。如果一切都在标题中。包含它的所有文件都被触及,这些文件通过系统级联。在极限情况下,更改一行可能意味着必须重新编译一百万。
          最近更新 更多