007 - C++头文件

这期我们一起学习一下头文件。

什么是头文件呢?我们为什么需要它们?它们存在于 C++中的意义是什么?

你可能学过很多其他语言,比如 Java 或 C#,它们都没有头文件。

头文件是一种很奇怪的文件,我们总是把它包含在某些地方,为什么要这样呢,它们的用途远不止只是声明一些你想要的声明,然后在多个 CPP 文件中使用,随着这个系列的进行,我们要学习的很多新概念确实需要头文件才能工作,所以不要忽略它们。

就 C++ 的基础而言,头文件通常用于声明某些类型的函数,以便它们能够被使用在你的程序中,如果你回想一下 C++ 编译器和链接器那两讲,里面涉及到了我们需要某些声明存在,以便我们知道它们的功能和类型是否可供我们使用。

01 函数声明

例如,如果我们在一个文件中创建函数,我们想在另一个文件中使用它,C++ 不会知道这个函数存在,当我们尝试编译另一个文件的时候,所以我们需要公共的地方来存放东西,只是声明就行,没有定义也可以。因为我们只能定义函数一次,一旦我们需要公共的地方来存储函数声明。也就相当于是在表达:这个函数没有实际的定义,没有函数体,但是这个函数是存在的。

让我们举一个简单的例子。
请添加图片描述
请添加图片描述

在 log.cpp 中 Ctrl+F7 ,我们会得到一个错误,因为这个 Log 函数在此文件中实际上不存在,这个文件不知道 Log 函数是什么东西。

当然,在 main.cpp 中,Log 函数是存在的,如果我尝试编译我的程序按下 Ctrl + F7 你会看到它工作得很好,我们没有任何错误。

log.cpp 到底需要怎么处理才能不出错呢?我们怎么知道 Log 函数是确实存在的,它只是在别处定义呢?需要函数声明

我们做以下的修改。
请添加图片描述
这个函数实际上在本文件中没有实体,表示它是函数的声明,我们还没有定义这个函数,这个函数的作用就是说,有个函数叫 Log,返回 void 并接受一个 const char * 指针,这个函数确实存在

你可以看到我们的 vs 智能感知,错误已经消失了。点击 Ctrl+F7, 我们可以编译通过。如果我右键点击项目,然后点击生成,它找到了 Log 函数,神奇。

我们找到了一种方法,可以告诉 log.cpp,这个Log 函数存在,但是如果我们创建另一个文件呢,如果其他的文件也需要用到 Log 函数,这是否意味着我们要一直把这个 void Log 函数声明复制粘贴到其他地方,答案是肯定的,你确实需要这样做,但是,有一种方法可以更简单,使用头文件

02 头文件

什么是头文件?我们应该怎样看待头文件呢?

头文件通常会被包含在 CPP 文件中,我们做的通过 #include 预处理器指令来实现将头文件的内容复制粘贴到 CPP 文件中。#include 具有复制和粘贴的能力,把文件放入另一个文件,而这正是我们所需要做的事情,我们需要复制并粘贴这个 Log 函数声明到每个需要使用 Log 函数的文件,让我们来创建一个头文件。
请添加图片描述
现在的想法是这样的,这个头文件 Log.h 可以包含在任何我希望使用 Log 函数的地方,对我来说我不想手动地复制并粘贴到每个需要它的文件。所以我用这种方法,在某种程度上,它看起来有点整洁和自动化。
请添加图片描述
你可以跑一下编译, 完全没有问题,我们还能做什么呢?同样可以包含在 main.cpp 中,虽然它已经有了函数的定义了,看起来它并不需要包含 Log 的声明了,我们可以直接调用Log,但你要知道,将 Log.h 头文件包含进来也不会有什么问题的。
请添加图片描述

编译成功。

好了,回到 log.cpp 中,你可以看到我们定义了 lnitLog这个函数,然而,除了 log.cpp,没有人真正知道它, 如果我想要在 main 函数中调用它的话,我就需要提前声明,我们需要做以下修改。
请添加图片描述
请添加图片描述
请添加图片描述
现在一切看起来都很好,程序可以通过编译并运行。

我们再多做一些更加合理的修改,将 Log 函数的定义转移到 log.cpp 文件中,这样代码就会更整齐一点。
请添加图片描述

编译通过,没有问题。

03 pragma

好的,让我们回到实文件,看下那个 #pragma 声明到底是什么,看上去是VS为我们插入了 #pragma ,这是怎么回事呢?

首先任何以 # 开头的东西被称为预处理器或预处理器指令,这意味着再实际编译此文件之前它将先被处理。

Pragma 本质上是一个被发送到编译器或预处理器的预处理指令,它的任务就是监督:只包括这个文件一次

#pragma 会监督这个头文件,阻止我们单个头文件多次被包含,并转换为单个转换单元,你要明白这并不妨碍我们将头文件放到程序的多个位置,而只是说放在一个转换单元。原因是如果我们不小心多次包含了一个文件,并转换成一个转换单元,我们会得到 duplicate 复制错误,因为我们会复制粘贴整个头文件多次,验证这一点的最好方法是我们创建一个结构体来试一下。
请添加图片描述
请添加图片描述

Log.h 被包含了两次,如果我尝试编译我的文件, 它说我们重复定义了player 结构体的错误。
请添加图片描述

我们只能定义一个名为 player 的结构体,结构体名必须是唯一的。

好了,你会说, 我为什么要这么做,我不是一个勤奋的程序员,我不像你想的那么笨,为什么我会包含一个文件两次?

你确定?

现在回忆一下 include 是如何工作的,记住 include 的工作原理是复制和和粘贴文件到其他文件,这意味着你可以创建一链条的头文件,

假设我们有一个名叫 player 的头文件,里面有player 结构体、Log 函数等等,而这些东西也被包含进了其他头文件,然后第三个头文件就会包含所有。

看看下面的例子。

我创建了一个头文件,这个头文件包含一些其他的头文件。
请添加图片描述
请添加图片描述
如果我编译我的文件,我仍然会得到那个错误,还是那个原因,player 结构体被重新定义了。

将 #pragma 解除注释,就不会得到错误了,因为它识别了 Player 已经被包含,所以后面没有重复包含。

还有另一种方法可以做头文件的监督,实际上,出于教学目的,我喜欢这个,它比 pragma 更有意义,虽然 pragma once 看起来更简洁,那就是 ifndef

04 ifndef

先看下面的代码。
请添加图片描述

这样写的的含义是:是否有一个叫做 LOG H 的符号被定义了, 如果它没有被定义,将继续在编译中包含以下代码,如果被定义了,那么下面中间所有这些都不会被包含进来, 将全部被禁用。

一旦我们通过了初始检查,下次我们再用到这些代码的时候,它将被定义, 因此不会重复,这很容易证明。
请添加图片描述

你甚至可以看到,第一次的时候,一切都很好。它包含了文件,一切正常, 第二次就变成灰色了,因为 LOG H 已经定义。

这个头文件保护符在过去被广泛使用,但是现在我们有了这个新的预处理语句 Pragma once,现在已经被广泛使用,在某种程度上,你用哪一个并不重要, 程序看起来更加简洁点就行,我个人喜欢用 Pragma,因为这是大多数人在工业中使用的,几乎每个编译器现在都支持 pragma once, Visual Studio、 GCC、Clang、MSVC,他们都支持 pragma once。 所以不要害怕使用它,也就是说,如果有一天你找到历史遗留代码或人们用不同的风格写的代码,可能会遇到 ifndef 这样的头文件保护符,要知道它是什么意思。

05 还有一些事

在 include 语句中的一些差异也需要我们认识一下,有些 include 使用引号,有些 include 语句使用尖括号,到底是怎么回事?

其实很简单,当我们编译程序时,它们有两种不同的含义,我们有能力告诉编译器,包括文件的路径是什么。

如果我们要包含的文件是在其中一个文件夹里,我们可以使用尖括号来告诉编译器,搜索包含路径文件夹,而引号则通常用于包含相对于当前文件的文件。

例如,如果我有一个名为 Log.h 文件, 例如,如果我有一个名为 Log.h 文件,如果它在 Log.cpp 文件所在目录的上层目录下, 我可以使用 #include “…/Log.h”, 就可以返回到当前文件的上级目录去寻找包含,因为这是相对于当前文件的路径。而有了尖括号,这里就没有相对于当前文件的了, 他们只需要在其中一个包含目录里面就行了, 我们将在未来讨论更多关于设置包含目录,我不想把事情复杂化,但这就是头文件工作的基本要点,你可以使用引号,来指定编译器包含目录的相对路径里面的文件。

我当然也可以将 iostream 替换为引号表示,这完全可行,尖括号只用于编译器包含路径,引号可以做一切。但我通常只用它在相对路径,主要还是用尖括号。

还有一件事要注意, iostream 实际上看起来不像一个文件,它不包含任何扩展,这又是怎么回事?

它实际上是一个文件,只是它没有扩展名,写 C++ 标准库的家伙决定要这么做,将 C++ 标准库与 C 标准库进行区分,C 标准库通常会有 .h 扩展,但是,C++ 文件没有,这是一种区分 C 标准库和 C++ 标准库的方法,即他们是否有 .h 扩展。iostream 是一个文件,就像其他任何东西,事实上,在 Visual Studio 中,如果我们右键点击它,点击打开文档,你可以看到它带我们去到了 iostream 头文件中,我们可以看到它的具体内容。
请添加图片描述

06 后话

好了,就是这样。头文件很容易,也很有用,我们将在这个系列中广泛地使用它,后面会有很多相关的例子,你将看到我是如何使用它们的,你现在应该明白它们是如何工作的了吧。

好了,本期就到这里,下期见。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值