C++ Header Files

C++ Header Files

C++ 头文件


介绍

头文件是什么?为什么我们需要他们?为什么他们存在C++中?您可能已经习惯的许多其他语言,例如Java或C#,它们实际上没有头文件或没有两种不同文件类型的概念,他们也有编译文件。实际上也像C++文件一样有编译翻译单元。C++还有一个头文件,它就是这种奇怪的文件,我们总是包含在某些地方,为什么它会在那里?头文件实际上更有用,它们不仅仅是声明你想要在多个 Cpp 文件中使用的某些声明,随着系列的进行,我们只会学习很多新概念,这些概念确实需要头文件才能工作。因此,就C++代码的基础知识而言,不要太快忽略它们。头文件传统上用于声明某些类型的函数,以便它们可以在整个程序中使用

声明

我们在之前得连接器的内容中讨论过我们如何需要某些声明的存在,以便我们知道 我们可以使用哪些功能和类型 。例如,如果我们在一个文件中创建函数并且我们想在另一个文件中使用它,C++ 不知道。当我们尝试编译另一个文件时,该函数甚至存在,所以我们需要一个公共位置来存储只是声明而不是定义,因为请记住我们只能定义一个函数一次。当在一个公共的地方存储函数的声明时,只是声明没有实际的定义,没有函数的主体,而只是一个说这个函数存在的地方。

//Main.cpp
#include <iostream>

void Log(const char* message)
{
	std::cout << message << std::endl;
} 

int main()
{
	std::cout << "Hello World!" << std::endl;
	std::cin.get();
}

//Log.cpp
void InitLog()
{
    Log("Initializing log"); //报错
}

我们在Main.cpp下写一个Log函数,是将字符串输出到控制台。如果我创建一个文件Log.cpp,创建一个InitLog函数把我们的日志记录到控制台的内容。我们会得到一个错误,因为这个日志函数实际上并不存在在这个文件中,这个文件不知道Log函数。因为Log函数在Main.cpp中

//Main.cpp
#include <iostream>

void Log(const char* message)
{
    std::cout << message << std::endl;
} 

int main()
{
    Log("Hello World!");
    std::cin.get();
}

将打印Hello World替换为Log函数,我们能够编译成功。我们回到Log.cpp文件我们编译会发现错误,因为当然,就此文件而言,Log不存在。

Log.cpp实际上需要什么才能不出错我们如何告诉它,Log函数确实存在,但它只是在其他地方定义。这就是函数声明的用武之地。我需要简单地声明Log确实存在,如果我们回到 main 并查看这个实际签名,您会发现 Log 是一个返回 void 的函数,并接受一个参数,该参数是一个常量是指针。

//Log.cpp
void Log(const char* message); //函数声明

void InitLog()
{
    Log("Initializing log"); 
}

我们将Log函数的签名和参数一起复制粘贴到Log.cpp文件中,以分号结尾。事实是这个函数并没有函数体意味着这只是这个函数的声明。我们还没有定义函数实际上是什么函数实际上做了什么,仅仅是有一个名为 log 的函数,返回 void 并接受一个常量指针。我们有那个函数存在

编译

我们编译Log.cpp文件没有错误,并且build项目也没有问题。我们已经找到了一种方法来实际告诉我们的日志是否似乎存在于某个地方。但是如果我们新增别的文件使用此日志函数的文件怎么办,这是否意味着我还必须在任何地方复制并粘贴此 Log 函数声明?

答案是肯定的,你实际上确实需要这样做,但是有一种方法可以做到这一点,使用头文件,传统上什么是头文件,我应该说,因为真的这是C ++你可以做的,你可以做任何事情,头文件通常包含在cpp文件中的文件,所以基本上我们要做的是将头文件的内容复制并粘贴到CPP文件中,我们通过#include来使用头文件。所以#include有能力将文件复制并粘贴到其他文件中,这正是我们似乎需要在这里做的,我们需要将此日志声明复制并粘贴到每个需要使用该日志功能的文件中。

创建头文件

我们创建一个Log.h的头文件,并将Log.cpp中的函数声明剪切到这里

//Log.h
#pragma once

void Log(const char* message);

我可以在其他任何想使用Log函数的地方引用这个Log.h文件。当然,它会为我做我不想手动做的事情,那就是在需要它的每个文件中复制并粘贴到任何地方。我不想自己复制和粘贴,所以我基本上找到了一种方法,让它看起来有点整洁
我们在Log.cpp中加入这个头文件

//Log.cpp
#include "Log.h"

void InitLog()
{
    Log("Initializing log");
}

我们也可以把这个头文件加到main.cpp中,但是现在main.cpp中包含Log函数的定义,所以并不需要把头文件引入,我们可以在这个文件任何地方调用Log函数。但是引用进来也没有坏处。
在Log.cpp中我们定义了一个InitLog的函数,除了这个文件,没有其他地方可以知道这里有这个函数,如果我们在main函数中调用它,我们需要声明这个函数。我们在Log.h文件中声明这个函数。然后把Log函数定义移到Log.cpp文件中,并且引入iostream文件。

//Log.h
#pragma once

void InitLog();
void Log(const char* message);

//Log.cpp
#include "Log.h"
#include <iostream>

void InitLog()
{
    Log("Initializing log");
}

void Log(const char* message)
{
    std::cout << message << std::endl;
}

//main.cpp
#include <iostream>
#include "Log.h"

int main()
{
    InitLog();
    Log("Hello World!");
    std::cin.get();
}

运行程序,输出

Initializing log
Hello World!

pragma once

回到头文件看下#pragma once语句,Visual Studio似乎已经为我们插入了#pragma once。所以首先任何以#开头的东西都被称为预处理器命令或预处理器指令,这意味着在实际编译该文件之前C++预处理器将对其进行评估,pragma本质上是发送到编译器或预处理器的指令,而pragma本质上意味着只包含此文件一次。所以 Pragma 想要的是一种叫做标题指南的东西,它的作用是防止我们将单个头文件多次包含在单个翻译单元中。因为你必须明白,这并不妨碍我们将头文件包含在程序中的多个位置,它只是到一个翻译单元中,所以说是单个CPP文件。原因是如果我们不小心将一个文件多次包含在单个翻译单元中,我们可能会得到重复的错误,因为我们将多次复制和粘贴整个头文件
如果头文件A中声明了Log函数,B头文件中包含了A头文件,C头文件包含了A和B两个头文件,那么A中得Log函数其实是声明了2次。所以头文件得头部会有 #pragma once 确保每个头文件中得声明或者其他得只有一次

traditional

添加标题参考的传统方法实际上是通过 ifdef。因此,我们可以做的是实际上可以输入ifndef,然后我们可以让它检查某种标符号。

//Log.h
#ifndef _LOG_H
#define _LOG_H

void InitLog();
void Log(const char* message);

#endif // !_LOG_H

首先要做的是检查是否实际定义了一个名为_LOG_H的符号,如果未定义,它将继续在编译中包含以下代码,如果已定义,则这些都不会包括在内,它将全部禁用。因此,一旦我们通过了这个初始检查,我们将定义_LOG_H这基本上意味着下次我们浏览此代码时,它将被定义,因此不会重复。这种操作指南是我们过去会经常使用的东西,但是现在我们有一个新的预处理语句,称为 pragma once,所以我们主要使用它。在某种程度上,您使用哪一个并不重要,尽管pragma once更简洁,所以这是我个人使用的东西,也是大多数人在行业中使用的东西。
现在几乎每个编译器都支持pragma once,所以它不仅仅 只是在visual studio中得东西,GCC Clang MSVC他们都支持pragma once,所以不要害怕使用它

header guides

最后一件事是使用标题指南,我们在include语句之间存在这种差异 一些包含语句使用引号 一些包含语句使用尖括号。当我们编译器我们的程序时,我们能够告诉编译器某些包含部分,这些基本上是我们计算机中文件的的一部分,如果我们要包含的include文件位于这些文件夹之一内,我们可以使用尖括号告诉编译器搜索我们的包含功能强大的文件夹哪里。另一方面,引号通常用于包含相对于当前文件的文件。
现在引号也可以用来指定相对于包含通过我们编译器传递的目录的文件。所以你实际上可以在任何地方使用引号,我可以在引号中替换这个iostream,它完全可以工作。尖括号只针对于编译器,引号是对一切都可以用。引号推荐相对位置使用,标准库推荐使用尖括号

iostream

您可能会注意到的另一件事是,iostream实际上看起来不像文件,因为它不包含任何扩展名,它只是称为iostream。这是怎么回事,它实际上是一个文件,它没有扩展名,这是写这个关于标准库的人决定做的事情。

今天我把这些 C 标准库头文件和 C++ 标准库头文件分开,在C标准库中的末尾往往会有一个.h扩展名。但是C++文件不会,所以这只是另一种方法,你可以区分C标准库和C ++标准库中的内容,它是否具有该扩展名。iostream是一个文件,就像其他任何东西一样,在visual studio 中你可以查看iostream得内容,然后再文件资源管理器中找到这个文件。
所以就是这样,头文件非常简单,我们将在本系列中广泛使用它,因此将有很多示例可以遵循,您将看到我如何使用它们,您将看到您应该如何使用它们,但您现在应该了解它们的工作原理以及它们的用处
h扩展名。但是C++文件不会,所以这只是另一种方法,你可以区分C标准库和C ++标准库中的内容,它是否具有该扩展名。iostream是一个文件,就像其他任何东西一样,在visual studio 中你可以查看iostream得内容,然后再文件资源管理器中找到这个文件。
所以就是这样,头文件非常简单,我们将在本系列中广泛使用它,因此将有很多示例可以遵循,您将看到我如何使用它们,您将看到您应该如何使用它们,但您现在应该了解它们的工作原理以及它们的用处

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值