头文件互相引用

  1. 现象
  2. 原因
    1. 先说一下 #ifndef #endif
    2. #include的作用
    3. 问题解释

写在前头 本文中的编译二字基本都带加粗, 因为目前为止我还没做学到过编译器相关的东西,
姑且将那个执行过程称为编译, 所以加粗

现象

二月六号的时候 我在写那个小游戏, 写头文件a.h发现即使引用了一个头文件b.h, 也没有办法使用定义在那个头文件之中的结构体.

其实有点我没在意: 当我在a.h文件中引用b.h的时候b.h会变成invaild, 这里其实就已经提示了

// a.h
#ifndef UNTITLED1_A_H
#define UNTITLED1_A_H
#include "b.h"
class A
{
B *b;
};
#endif //UNTITLED1_A_H

// b.h
#ifndef UNTITLED1_B_H
#define UNTITLED1_B_H
#include "a.h"
class B
{
A *a;
};

#endif //UNTITLED1_B_H
// main.cpp
#include "a.h" // a.h中有b.h就只include a.h
int main()
{
A a{};
B b{};
}

error: ‘A’ does not name a type
error: ‘B’ does not name a type

原因

先说一下 #ifndef #endif

ifndef全称if not defined
意思是如果#ifndef后面的宏没有被定义 就继续编译其中的内容
继续编译#ifndef的下一句就是#define这个宏, 这样这个宏就被定义了
第二次编译遇到这个头文件的时候, #ifndef后面的宏已经被定义了就跳过了if中的内容

#include的作用

将 #include右边的文件展开到此文件中

问题解释

这样当你在main函数中 #include "a.h"的时候a.h之中的内容就被展开
变成如下

// main.cpp
#ifndef UNTITLED1_A_H
#define UNTITLED1_A_H
#include "b.h"
class A
{
B *b;
};
#endif //UNTITLED1_A_H

int main()
{
A a{};
B b{};
}

还有一个include同样操作得到如下

// main.cpp
#ifndef UNTITLED1_A_H
#define UNTITLED1_A_H
#ifndef UNTITLED1_B_H
#define UNTITLED1_B_H
#include "a.h"
class B
{
A *a;
};

#endif //UNTITLED1_B_H
class A
{
B *b;
};
#endif //UNTITLED1_A_H
int main()
{
A a{};
B b{};
}

我猜测编译的时候是逐行执行的, 至少在头文件的这部分是逐行执行
这样执行完了行号为2 3 4 5的四个宏 准备执行行号为6的这一行#include "a.h"继续展开
得到如下

// main.cpp
#ifndef UNTITLED1_A_H
#define UNTITLED1_A_H
#ifndef UNTITLED1_B_H
#define UNTITLED1_B_H
#ifndef UNTITLED1_A_H
#define UNTITLED1_A_H
#include "b.h"
class A
{
B *b;
};
#endif //UNTITLED1_A_H
class B
{
A *a;
};

#endif //UNTITLED1_B_H
class A
{
B *b;
};
#endif //UNTITLED1_A_H
int main()
{
A a{};
B b{};
}

可能有些乱 但仔细理解还是能到这里. 这下继续逐行执行 准备是行号为6的这一行(也可能是行号7的部分, 区别就是在include的下一行展开还是本行展开)#ifndef UNTITLED1_A_H
这个宏就判断UNTITLED1_A_H是不是被定义了, 恩被定义了(第三行代码)跳到对应的#endif, 这样就防止了头文件
无穷无尽的调用 我将跳过的部分注释掉方便继续分析

// main.cpp
#ifndef UNTITLED1_A_H
#define UNTITLED1_A_H
#ifndef UNTITLED1_B_H
#define UNTITLED1_B_H
//#ifndef UNTITLED1_A_H
//#define UNTITLED1_A_H
//#include "b.h"
//class A
//{
// B *b;
//};
//#endif //UNTITLED1_A_H
class B
{
A *a;
};

#endif //UNTITLED1_B_H
class A
{
B *b;
};
#endif //UNTITLED1_A_H
int main()
{
A a{};
B b{};
}

下一行就是第十四行class B编译, 那么问题来了第16行A是啥东西??.

这里报错报了两行 我大胆推测一下 class B编译的出错后, 依然在继续这个过程, 到了class A的时候
发现其中的B又是啥呢? 推测不同头文件编译互不影响 所以出现了两行报错

我们回到class B的头文件在class B的前面加上一行class A; 得到如下代码

// main.cpp
#ifndef UNTITLED1_A_H
#define UNTITLED1_A_H
#ifndef UNTITLED1_B_H
#define UNTITLED1_B_H
//#ifndef UNTITLED1_A_H
//#define UNTITLED1_A_H
//#include "b.h"
//class A
//{
// B *b;
//};
//#endif //UNTITLED1_A_H
class A;
class B
{
A *a;
};

#endif //UNTITLED1_B_H
class A
{
B *b;
};
#endif //UNTITLED1_A_H
int main()
{
A a{};
B b{};
}

到了这里你会发现A的报错消失了, 因为这次在编译class B的时候事先知道了A是一个类并且你必须把
class B之中的这一句A *a;写成指针, 因为指针大小确定, 而且没有初始化, 所以编译就能通过, 如果你写成
A a;依然会报错, 因为不知道A具体是个啥 最起码的内存都没法分配

好的下面继续, 你发现虽然A的报错消失了 但是B的报错还在, 我这里依然推测下, 不同头文件之间的编译互不影响,
所以依然不知道B是什么此时按照上面操作加入class B;前置声明, 编译正确通过.

虽然两个头文件编译互不影响, 但是既然是main.cpp把他们引进来的 就同时对main.cpp起作用 自然就知道了A和B是啥

虽然这个问题网上答案很多, 但我对此还是很多不理解, 所以在做某些推测的情况下, 分析出来了这些原因. 如果有误,请直接评论中指出. 这也算是本人的第一篇写的认真的博客


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。