C++ headers 区别

C++ headers difference

刚接触编程,对 headers 有一些疑问。

书中用到的:

#include "std_lib_facilities.h"

我的老师总是使用:

#include <iostream>
using namespace std;

为什么我们会使用一个而不是另一个?一个工作比另一个好?任何帮助,将不胜感激。

随书提供或在线访问的 include_std_facilities.h 文件就像是包含多个头文件的快捷方式(如果包含该文件,查看源代码,它只包含一些有用的头文件,例如您不需要自己明确包含它们)。

可以把它想象成邀请你妈妈来吃晚饭。你邀请她,这不可避免地意味着你的父亲(好吧,我在做假设)、你的妹妹和她那条讨厌的活泼的狗也会自动过来吃晚饭,而不会明确要求他们。

这不是一个很好的实践,但我认为作者有他们的理由。

你的老师是对的,最小代码通常会包含iostream,以便你可以输出到终端。但是,这是我必须强调的一点,使用 namespace std 是不好的做法。您可以通过显式使用 std:: 访问 std 功能 - 例如:

std::cout << "hello world!" << std::endl;

出于多种原因,在 std 命名空间之外工作是有益的。您可以通过一些谷歌搜索找到这些。

我猜这本书的作者这样做是为了让代码示例更短,而不是作为示例风格的示例。无需在每个示例中都放入样板文本。

就我而言,将一堆包含文件放入每个人都包含的头文件中(这不是预编译的头文件)会大大减慢编译速度(不仅需要查找和加载每个文件,还需要检查依赖项等),特别是如果您有一个过分热心的 IT 部门坚持实时扫描每个文件访问。

文件中包含的 header 根据文件的需要从文件更改到文件。 Stroustrup 使用的内容在他的源代码示例的上下文中起作用。您的老师使用的内容符合老师的需求。

你使用什么取决于你的需要和程序的需要。

每个文件应包含 所有 的 header 项,仅此而已。如果在文件中使用 std::string,则 #include <string>。如果使用std::set#include <set>

似乎没有必要包含某些 header 文件,因为另一个包含包含它们。例如,既然 iostream 已经包含了字符串,为什么还要同时包含 iostream 和字符串呢?实际上这可能无关紧要,包含了字符串,但它现在是一个维护问题。并非给定 header 的所有实现都具有相同的包含。您会发现自己遇到一些有趣的情况,其中代码在一个编译器或编译器版本中编译,而不是另一个编译器,因为包含依赖项在 header 可能有两个或三个包含链中发生变化。

最好为您和所有可能跟随您维护代码的人避免这种情况。

如果您不关心维护,请查看 y2k 错误。这是一个问题,因为为满足 1960 年代和 1970 年代的需求和限制而编写的软件在 40 年后仍在需求和限制截然不同的世界中使用。您会惊讶于有多少代码超过了预期的生命周期。

虽然在你的余生中有一份固定的工作是件好事,但在夏威夷度假的周末却很糟糕,因为 co-op 本来可以完成的修改变成了一场噩梦,因为 GCC 7.12 与您最初编写程序时所用的 GCC 5.2 完全不同。

您还会发现,随着包含长文件链,构造不当的 header 可能会在 header 上注入顺序依赖性。如果 header Y 包含在 header Z 之前,程序 X 工作正常。这是因为 Z 需要 Y 包含的 Z 没有声明的东西。不要成为写Z的人。

但是,不要包含所有内容。您包含的内容越多,编译所需的时间就越长,并且您对无法预料的组合反应不良的风险就越大。不管喜欢与否,总是有人写 header Z,如果不包含 Z,您不希望调试 Z 导致的日程安排受到影响。

为什么编译时间变长了?将每个包含视为编译器(实际上是预处理器)将包含文件粘贴到包含文件中的命令。然后编译器编译组合文件。每个包含都需要从磁盘加载(慢),可能需要进行安全扫描(通常非常慢),然后合并到要编译的文件中(比您预期的要快)。所有被包含文件的包含都被粘贴进去,在你知道之前,你有一个巨大的文件母亲要被编译器解析。如果不需要那个巨大文件的很大一部分,那就是浪费精力。

避免像需要 header Y including header Z 和 header Z including Y 的瘟疫结构。一个 include 守卫,稍后会详细介绍,将保护明显的递归问题(Y includes Z includes Y includes Z includes...),但也确保您不能及时包含 Y 或 Z 以满足 Z 或 Y。

一些常见的headers,字符串是最喜欢的,一遍又一遍地包含在内。您不想重新包含 header 及其依赖项,但您还想确保它已包含(如果未包含)。为了解决这个问题,你用 Header 守卫包围你的 header。这看起来像:

#ifndef UNIQUE_NAME 
#define UNIQUE_NAME
// header contents goes here
#endif

如果UNIQUE_NAME没有定义,则定义它,并复制要编译的文件中的header内容。这有几个问题。如果 UNIQUE_NAME 不是唯一的,您将收到一些非常奇怪的错误 "not found" 消息,因为第一个 header 守卫将阻止下一个

的包含。

唯一性问题已通过 #pragma once 解决,但 #pragma once 自身存在一些问题。它不是标准的 C++,因此并未在所有编译器中实现。 #pragma 如果编译器不支持 pragma,指令将被静默忽略(除非编译器警告被调高,有时甚至不会)。随着 headers 被重复包含在内,随之而来的是彻底的混乱,你会收到警告。 #pragma once 也可能被包括网络地图和链接在内的复杂目录结构所愚弄。

所以... Stroustrup 使用 #include "std_lib_facilities.h" 是因为它包含了他书中早期课程所需的所有细节,而不必冒着覆盖这些细节 nitty-gritty 的信息过载的风险。这是经典的鸡肉和鸡蛋。 Stroustrup 希望通过减少学生的信息负荷以可控的方式教授这些早期课程,直到 Stroustrup 能够涵盖实际理解背景所需的 material这些早期课程的基本细节。

很棒的教学策略。在某种程度上,该策略值得在代码中效仿:他通过添加一个间接层消除了一个问题。

但在程序员的滑动发布日期、pointy-haired 老板、死亡行军和诉讼的现实世界中,这可能是一个糟糕的策略。您不想浪费时间来解决一开始不需要存在的非问题。不存在的代码没有错误。或者是一个错误,但那是另一个问题。

Bjarne Stroustrup 可以逃脱一些事情,因为他 Bjarne 咒骂删除了 Stroustrup。他知道自己在做什么。他证明了这一点。他可能已经考虑、衡量和权衡了他的 megaheader 的含义,因为他知道它们是什么。如果他担心里面有什么东西会影响他的学生,他就不会这样做了。

一年级编程学生不是 Bjarne Stroustrup。即使他们有一颗行星那么大的大脑,他们也缺乏多年的经验。它通常是您不知道的东西,您需要担心,而 Stroustrup 的 "don't know" 池将明显变小。

当您知道自己在做什么并且可以诚实地证明所做的事情合理时,您可以做各种不安全的事情。并且只要您考虑到您可能不是唯一对代码有既得利益的人。如果在您被公共汽车撞到后拿起您的投资组合的编码员无法阅读它,那么您的 super-elegant-but-super-arcane 解决方案并不多。您可爱的代码将进入垃圾箱,您的遗产将是,"What the smurf was that smurfing idiot trying to do with that smurf?"至少留下一些笔记。

Stroustrup 也有页数和字数限制,要求他在印刷版中压缩文本。一个 Header 来统治他们的一切帮助极大。

您的老师在学生超载方面也有类似的问题,但可能没有打印 space 约束。对于您目前看到的代码片段,所需要的只是需要 iostream 的基本输入和输出。可能还有字符串,但即使我承认如果不包含字符串也很难编写 iostream header。

很快您将开始看到 #include <vector>#include <fstream>#include<random>,并开始编写您自己的 header。

努力学会正确地做事,否则毕业后你的软件工程学习曲线很糟糕。