有人可以帮助澄清头文件的工作原理吗?

时间:2010-06-23 01:13:47

标签: c++ file header

我已经使用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'。我认为如果我将#include "main.h"用于“player.h”,它将能够找到HealthVector的定义,感觉它们包含在“main.h”中。我认为他们会按照自己的方式进行隧道挖掘(player.h - > main.h - > health.h)。但这并没有奏效。是否有某种图表或视频可以澄清应该如何设置?谷歌帮助不大(也不是我的书)。

5 个答案:

答案 0 :(得分:10)

考虑头文件的最佳方式是“自动复制粘贴”。

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

这引入了几个复杂性:

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

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

答案 1 :(得分:8)

此处的其他答案有效地解释了头文件和预处理器的工作方式。你遇到的最大问题是循环依赖,从经验来看,我知道这可能是一种巨大的痛苦。此外,当开始发生时,编译器开始以非常奇怪的方式运行并抛出非常有用的错误消息。我在大学里由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语句中使用的变量名称对您来说是唯一的,否则您可能会遇到无法正确包含某些内容的问题; - )。

祝你好运!

答案 2 :(得分:3)

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

Player.h应该包含health.h和custvector.h,此时,我认为main.h不需要任何包含。最终它可能需要player.h。

答案 3 :(得分:2)

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

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

答案 4 :(得分:1)

您希望在DAG(有向,非循环图)中整理#includes(以及库)。这是说“避免头文件之间的循环”的复杂方式:

如果B包括A,则A不应包括B.

因此,使用“一个大主人main.h”并不是正确的方法,因为#incined只包含直接依赖关系。

每个.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

您希望将一堆#include聚合到一个标题中的唯一时间就是当您提供它作为非常大的库的便利时。

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

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