【问题标题】:Can someone help clarify how header files work?有人可以帮助澄清头文件的工作原理吗?
【发布时间】:2011-03-07 02:40:41
【问题描述】:

我已经使用 C++ 工作了好几个星期了,但是头文件背后的机制(或者我想是链接器?)让我很困惑。我已经养成了创建一个“main.h”来对我的其他头文件进行分组并保持 main.cpp 整洁的习惯,但有时这些头文件抱怨无法找到不同的头文件(即使它已声明在“main.h”中)。我可能没有很好地解释它,所以这里是我正在尝试做的事情的精简版:

//main.cpp

#include "main.h"
int main() {
    return 0;
}

-

//main.h

#include "player.h"
#include "health.h"
#include "custvector.h"

-

//player.h

#include "main.h"
class Player {
    private:
        Vector playerPos;
    public:
        Health playerHealth;
};

-

//custvector.h

struct Vector {
   int X;
   int Y;
   int Z;
};

-

//health.h
class Health {
    private:
        int curHealth;
        int maxHealth;
    public:
        int getHealth() const;
        void setHealth(int inH);
        void modHealth(int inHM);
};

我不会包含 health.cpp,因为它有点长(但确实有效),它确实有 #include "health.h"

无论如何,编译器(Code::Blocks)抱怨“player.h”找不到“Health”或“Vector”类型。我认为如果我在“player.h”中使用#include "main.h",它将能够找到HealthVector 的定义,它们包含在“main.h”中。我想他们会以他们的方式走隧道(player.h -> main.h -> health.h)。但这并没有很好地工作。是否有某种图表或视频可以说明应该如何设置? Google 帮不上什么忙(对我的书也没有)。

【问题讨论】:

  • 它没有回答您的问题,但您应该将 Vector 结构更改为其他名称。当您开始使用 std::vector 时,它会变得令人困惑,而且 3D 空间中的点无论如何都不是真正的向量。
  • 谢谢,我会这样做的。你说得对,应该是点什么的。
  • 其实一个向量就是由一个点来定义的。
  • 不确定你的意思是什么蜘蛛侠。矢量具有大小和方向。一个点只是一些标量值,比如速度。它的矢量形式是速度。 en.wikipedia.org/wiki/Vector_(mathematics_and_physics)
  • @reuscam:N 维中所有点的空间同构于所有 N 维向量的空间。它点指定从原点到该点的向量,为您提供长度和方向。当然,许多人希望有一个向量位于某个点(即起点、长度和方向)。为此,您需要两点。

标签: c++ file header


【解决方案1】:

你有一个循环依赖。 Player 包含 main.h,但 main.h 包含 player.h。通过删除一个或另一个依赖项来解决此问题。\

Player.h 应该包含 health.h 和 custvector.h,在这一点上,我认为 main.h 不需要任何包含。最终它可能需要 player.h。

【讨论】:

  • #ifndef ... #endif 是另一种稍微简单的方法
  • 如果其他东西需要使用health.h,这不是问题吗?那不是将它两次包含在程序中吗?
  • 避免在 C 或 C++ 中多次包含头文件的方法是使用包含保护。每个头文件都以#ifndef UNIQUE_STRING#define UNIQUE_STRING 两行开头(将UNIQUE_STRING 替换为您在其他地方没有#define 的名称)。然后文件以#endif 结尾。 en.wikipedia.org/wiki/Include_guard
【解决方案2】:

includes 的工作非常简单,它们只是命令预处理器将文件的内容添加到设置 include 的位置。基本思想是包含您所依赖的标题。在player.h 中,您应该包括custvector.hHealth.h。在 main 中只有player.h,因为所有需要的包含都将与播放器一起携带。而且您根本不需要在player.h 中包含main.h

最好确保标头只包含一次。在这个问题中,一般的解决方案是How to prevent multiple definitions in C?,如果是Visual Studio,你可以使用#pragma once,如果Borland c++也有一个技巧,但我忘记了。

【讨论】:

  • 我想这就是我关心的问题,如果我决定制作一个像 Enemy 类这样也需要 Vector 和 Health 的东西,我不想添加两次。我想如果我把所有的定义都放到 main.h 中,就会消除这个问题。
  • @Karl Menke main.h 不是放置它的好地方,因为它还会包含 main.cpp 的东西。您应该创建类似common.h 的内容并将公共引用移到那里。然后使用它。
【解决方案3】:

将头文件视为“自动复制和粘贴”的最佳方式。

考虑它的一个好方法(尽管不是实际实现的方式)是,当您编译 C 文件或 C++ 文件时,预处理器首先运行。每次遇到#include 语句时,它实际上都会粘贴该文件的内容,而不是#include 语句。这样做直到没有更多的包含。最终缓冲区被传递给编译器。

这带来了一些复杂性:

首先,如果 A.H 包含 B.H 而 B.H 包含 A.h,那么您就有问题了。因为每次你想粘贴 A 时,你都需要 B 并且它内部会有 A !那是一个递归。因此,头文件使用#ifndef,以确保不会多次读取同一部分。这很可能发生在您的代码中。

其次,你的 C 编译器会在所有头文件都“展平”后读取文件,因此在推理什么之前声明的内容时需要考虑这一点。

【讨论】:

  • 这是我更喜欢的解释,也是我最能描述我如何理解这个问题的解释。很简单,编译器获取文本并编译它。预处理器获取文本并对其进行预处理。包含只是一个预处理指令,用于将外部文件中的一些文本导入当前文件。
【解决方案4】:

这里的其他答案有效地解释了头文件和预处理器的工作方式。您遇到的最大问题是循环依赖关系,根据经验,我知道这可能是一种痛苦。此外,当这种情况开始发生时,编译器开始以非常奇怪的方式运行并抛出不是很有帮助的错误消息。我在大学时被 C++ 大师教过的方法是,每个文件(例如头文件)都以

开头
//very beginning of the file
#ifndef HEADER_FILE_H //use a name that is unique though!!
#define HEADER_FILE_H
...
//code goes here
...
#endif
//very end of the file

这使用预处理器指令来自动防止循环依赖。基本上,我总是使用文件名的全大写版本。 custom-vector.h变成了

#ifndef CUSTOM_VECTOR_H
#define CUSTOM_VECTOR_H

这允许你在不创建循环依赖的情况下包含文件 willie-nillie,因为如果一个文件被多次包含,它的预处理器变量已经定义,所以预处理器会跳过该文件。它还使以后使用代码更容易,因为您不必筛选旧的头文件以确保您尚未包含某些内容。不过,我会再重复一遍,请确保您在 #define 语句中使用的变量名称对您来说是唯一的,否则您可能会遇到无法正确包含某些内容的问题 ;-)。

祝你好运!

【讨论】:

  • 希望您不介意:我编辑了您的 CUSTOM_VECTOR_CPP 示例。 *.c*.cpp 文件不需要包含保护,因为您不需要#include 它们。
  • 例如,如果我要创建一个需要 Vector 和 Health 的 Enemy 类,而 Player 类已经包含 Vector 和 Health,我不需要为 Enemy 包含它们吗?
  • @John 没问题!好电话,我习惯于模板编程,您必须在 *.h 文件中包含 *.cpp 文件。我刚刚养成了这样做的习惯:-)
  • @Karl 对,想想创建一个包含所有包含文件的巨型文件的预处理器。然后编译器对该大文件进行操作。因此,一旦包含了一个文件,无论来自何处,编译器都能够找到它。这有意义吗?
  • @Karl 我倾向于在任何需要的地方都包含它,并使用#ifndef ... #define 块来防止循环包含。这样以后就没有被剪掉的可能了。同样,您的代码是半自给自足的,而不是依赖于其他不相关的代码。不过,对于 SO 来说,这可能是一个很好的“最佳实践”问题。无论如何,只要你正确使用#define 块,如果你包含一个文件太多次,你不会有任何问题
【解决方案5】:

您想在DAG(有向无环图)中组织您的#includes(以及库)。这就是说“避免头文件之间的循环”的复杂方式:

如果 B 包含 A,则 A 不应包含 B。

因此,使用“一位大佬main.h”不是正确的方法,因为很难#include 仅直接依赖项。

每个 .cpp 文件都应包含自己的 .h 文件。该 .h 文件应该只包含它本身编译所需的内容。

通常没有main.h,因为main.cpp没有人需要main的定义。

此外,您还需要include guards 来保护您免受多次包含。

例如

//player.h
#ifndef PLAYER_H_
#define PLAYER_H_
#include "vector.h"  // Because we use Vector
#include "health.h"  // Because we use Health
class Player {
    private:
        Vector playerPos;
    public:
        Health playerHealth;
};
#endif

-

//vector.h
#ifndef VECTOR_H_
#define VECTOR_H_
struct Vector {
   int X;
   int Y;
   int Z;
};
#endif

-

//health.h
#ifndef HEALTH_H_
#define HEALTH_H_
class Health {
    private:
        int curHealth;
        int maxHealth;
    public:
        int getHealth() const;
        void setHealth(int inH);
        void modHealth(int inHM);
};
#endif

您希望将一堆 #includes 聚合到一个标头中的唯一情况是您为一个非常大的库提供便利。

在您当前的示例中,您有点过火了——每个类都不需要自己的头文件。它可以全部进入 main.cpp。

c 预处理器将文件从#include 直接插入到包含它的文件中(除非它已经被插入,这就是你需要包含保护的原因)。它允许您使用这些文件中定义的类,因为您现在可以访问它们的定义。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-23
    • 2014-05-21
    • 1970-01-01
    • 1970-01-01
    • 2012-02-14
    相关资源
    最近更新 更多