C++为什么需要头文件以及如何实践
翻译文章,地址:
https://www.cplusplus.com/articles/Gw6AC542/
0、简介
本文旨在解决一个常见的新手问题,即include头文件的问题。 概述和解释了几种良好做法,并且展示如何避免一些丑陋的代码。
后面的部分会涉及更高级的主题(内联inline和模板template),因此,即使是具有一定经验的 C++ 程序员也能从阅读中受益!
如果您已经熟悉基础知识,请跳到第 4 节。第4节是讨论实践和设计策略的地方。
1、为什么C++需要头文件?
如果您刚开始使用 C++,您可能想知道为什么C++需要 #include 头文件,以及为什么一个程序要拥有多个 .cpp 文件。 原因很简单:
a) 减少编译时间
随着程序的增长,您的代码也会增长,如果所有内容都在一个文件中,那么每次进行任何微小更改时都必须完全重新编译所有内容。
对于小程序来说,这似乎没什么大不了的(实际上也不是),但是当您有一个合理大小的项目时,编译整个程序可能需要几分钟的时间。
你能想象在每次小改动之间都要等那么久吗?就像下面的情况:
编译/等待 8 分钟/“我靠,忘记分号”/编译/等待 8 分钟/调试/编译/等待 8 分钟/。。。。。。
b) 使代码更有条理
如果您将不同的功能模块分离到不同的文件中,那么在您想要进行修改时,更容易找到您正在寻找的代码。
(否则你需要盯着它并记住它是如何使用的、以及它是如何工作的)
c) 接口与实现分离
如果您不明白这意味着什么,请不要担心,我们将在本文中看到它的实际应用。
以上都是优点,但很明显缺点是,如果您不了解它是如何工作的,这反而会给你带来麻烦。
(但实际上,随着项目变得越来越大,头文件比很多其他的替代方案更简单)
C++ 程序的构建有两个阶段过程。
首先,每个源文件都是独立编译的。 编译器为每个编译的源文件生成中间文件。 这些中间文件通常称为对象文件(Linux中后缀为.o,Windows中后缀为.obj)——但不要将它们与代码中的对象混淆。 一旦所有文件都被单独编译,链接器将所有目标文件链接在一起,从而生成最终的二进制文件(程序)。
这意味着每个源文件都与其他源文件分开编译。
因此,就编译而言,“a.cpp”对“b.cpp”内部发生的事情一无所知。 这里有一个简单的例子来说明:
// in myclass.cpp class MyClass { public: void foo(); int bar; }; void MyClass::foo() { // do stuff } // in main.cpp int main() { MyClass a; // Compiler error: 'MyClass' is unidentified return 0; }
即使在您的程序中(myclass.cpp)声明了 MyClass,它也没有在 main.cpp 中声明,因此当您编译 main.cpp 时会出现该错误。
这就是头文件的来源。头文件允许您使接口(在本例中为 MyClass 类)对其他 .cpp 文件可见,同时将实现(在本例中为 MyClass 的成员函数体)保留在其自己的 . .cpp 文件。 同样的例子,但略有调整:
// in myclass.h class MyClass { public: void foo(); int bar; }; // in myclass.cpp #include "myclass.h" void MyClass::foo() { } //in main.cpp #include "myclass.h" // defines MyClass int main() { MyClass a; // no longer produces an error, because MyClass is defined return 0; }
#include 语句基本上类似于复制/粘贴操作。 编译器将在编译文件时将#include 行“替换”为您包含的文件的实际内容。
2、这些文件有什么区别(.h/.cpp/.hpp/.cc等等)
所有文件在本质上都是相同的,因为它们都是文本文件,但是不同类型的文件应该有不同的扩展名:
- 头文件应使用 .h** 扩展名 (.h / .hpp / .hxx)。您使用哪个无关紧要。
- C++ 源文件应使用 .c**扩展名 (.cpp / .cxx / .cc)。您使用哪个无关紧要。
- C 源文件应使用 .c(仅限 .c)。
此处将 C 和 C++ 源文件分开的原因是它在某些编译器中有所不同。如果您将使用 C++ 代码,请避免使用 .c 扩展名。此外,如果您尝试编译具有头文件扩展名的文件,编译器可能会忽略它们。
那么头文件和源文件有什么区别呢?基本上,头文件是用来#included而不是编译,而源文件是编译而不用来是#included。您可以不遵守这些约定,并使具有源文件扩展名的文件,表现得像头文件,反之亦然,但您不应该这样做。我不想说你不应该的原因——记住不要给自己找麻烦。
一个例外是,有时(尽管很少)包含源文件很有用。此方案与实例化模板有关,超出了本文的范围。
现在......只要把它放在你的脑海中:“不要#include源文件”