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源文件”

3、include保护

cpp