写在前头 本文中的编译二字基本都带加粗, 因为目前为止我还没做学到过编译器相关的东西,
姑且将那个执行过程称为编译, 所以加粗
现象
二月六号的时候 我在写那个小游戏, 写头文件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是啥
虽然这个问题网上答案很多, 但我对此还是很多不理解, 所以在做某些推测的情况下, 分析出来了这些原因. 如果有误,请直接评论中指出. 这也算是本人的第一篇写的认真的博客