栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

UE5 学习笔记【C++语法篇】:头文件间相互包含时的编译出错

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

UE5 学习笔记【C++语法篇】:头文件间相互包含时的编译出错

头文件间相互包含时的编译出错
  • 错误示例
    • 描述
    • 代码
    • 结果
  • 错误原因
    • 结果分析
    • 编译过程
    • 预处理代码分析
  • 可供参考的解决方法
  • 关于作者
  • 相关知识参考博客

错误示例 描述

当一个类A需要多次访问另一个类B时,习惯性会给类A分配一个B的指针类型的成员变量。

同样,如果类B也需要对A进行多次访问,就在类B中分配一个A的指针类型的成员变量。

代码

类A的头文件 A.h

#pragma once

#ifndef A_H
#define A_H

#include "B.h"

class A {

public:
	A();
	~A();

private:
	B* b;

};

#endif 

类B的头文件 B.h

#pragma once

#ifndef B_H
#define B_H

#include "A.h"

class B{

public:
	B();
	~B();

private:
	A* a;
	
};

#endif
结果

根据上述类型结构和代码编写,经过编译器编译后得到如下报错。

已启动生成…
1>------ 已启动生成: 项目: UE5_CODE_TEST, 配置: Debug x64 ------
1>A.cpp
1>G:UE5_CODE_TESTUE5_CODE_TESTB.h(15,3): error C2143: 语法错误: 缺少“;”(在“*”的前面)
1>G:UE5_CODE_TESTUE5_CODE_TESTB.h(15,3): error C4430: 缺少类型说明符 - 假定为 int。注意: C++ 不支持默认 int
1>G:UE5_CODE_TESTUE5_CODE_TESTB.h(15,6): error C2238: 意外的标记位于“;”之前
1>B.cpp
1>G:UE5_CODE_TESTUE5_CODE_TESTA.h(15,3): error C2143: 语法错误: 缺少“;”(在“*”的前面)
1>G:UE5_CODE_TESTUE5_CODE_TESTA.h(15,3): error C4430: 缺少类型说明符 - 假定为 int。注意: C++ 不支持默认 int
1>G:UE5_CODE_TESTUE5_CODE_TESTA.h(15,6): error C2238: 意外的标记位于“;”之前
1>Main.cpp
1>G:UE5_CODE_TESTUE5_CODE_TESTB.h(15,3): error C2143: 语法错误: 缺少“;”(在“*”的前面)
1>G:UE5_CODE_TESTUE5_CODE_TESTB.h(15,3): error C4430: 缺少类型说明符 - 假定为 int。注意: C++ 不支持默认 int
1>G:UE5_CODE_TESTUE5_CODE_TESTB.h(15,6): error C2238: 意外的标记位于“;”之前
1>正在生成代码...
1>已完成生成项目“UE5_CODE_TEST.vcxproj”的操作 - 失败。
========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ==========
错误原因 结果分析

编译报错中显示,当编译器编译到类B头文件的第15行时,发现A为未被定义的类型,导致指针a声明失败。那么为什么A未被定义呢?

编译过程

在我们写完代码后,当前的代码并不是编译器直接能够编译的。

c++在进行编译之前,会根据我们指定的预处理标识,对代码进行预处理操作,形成可编译的代码,进而送给编译器进行编译。

举个简单的例子: 编写代码的时候,我们通常都会给代码添加注释以便于理解,而在编译的时候机器是不看这些注释的代码的,也就是这些代码对于编译是没有意义的。那么预处理操作就会把这些注释给去掉,留下机器可以识别的代码进行编译。

常见的预处理标识有:#ifndef、#define、#endif、#include……

由这些预处理标识定义了一个又一个的宏,一个宏对应一片代码段。在代码预处理的时候,这些宏会被替代成对应的代码段。所有的宏都被替代完毕后,便形成了最终用于编译的完整代码。

而本文要分析遇到的主要问题,便是典型的 头文件包含(#include) 问题。

预处理代码分析

按照预处理操作的原理,我们首先将类A头文件A.h中的 #include “B.h” 替换成对应的代码段,结果如下:

#pragma once

#ifndef A_H
#define A_H

//#include "B_h"区域头部
#pragma once

#ifndef B_H
#define B_H

#include "A.h"

class B{

public:
	B();
	~B();

private:
	A* a;
	
};

#endif
//#include "B_h"区域尾部

class A {

public:
	A();
	~A();

private:
	B* b;

};

#endif 

然后我们再将 #include "B.h"区域 中的 #include “A.h” 替换为对应的代码段,结果如下:

#pragma once

#ifndef A_H
#define A_H

//#include "B_h"区域头部
#pragma once

#ifndef B_H
#define B_H

//#include "A.h"区域头部
#pragma once

#ifndef A_H
#define A_H

#include "B.h"

class A {

public:
	A();
	~A();

private:
	B* b;

};

#endif
//#include "A.h"区域尾部

class B{

public:
	B();
	~B();

private:
	A* a;
	
};

#endif
//#include "B_h"区域尾部

class A {

public:
	A();
	~A();

private:
	B* b;

};

#endif 

接着,我们根据 #ifndef 等宏定义,对代码需要简化的部分进行注释表示,结果如下:

#pragma once

#ifndef A_H
#define A_H

//#include "B_h"区域头部
#pragma once

#ifndef B_H
#define B_H

//#include "A.h"区域头部

//该部分由于重复定义类型A而被去除
//#include "A.h"区域尾部

class B{

public:
	B();
	~B();

private:
	A* a;
	
};

#endif
//#include "B_h"区域尾部

class A {

public:
	A();
	~A();

private:
	B* b;

};

#endif 

我们将被注释的代码段进行去除,得到最终简化后的、用于编译的完整代码 (这里为便于演示,将注释留下,实际情况下注释也将去除),如下:

#pragma once

#ifndef A_H
#define A_H

//#include "B_h"区域头部
#pragma once

#ifndef B_H
#define B_H

//#include "A.h"区域头部
//该部分由于重复定义类型A而被去除
//#include "A.h"区域尾部

class B{

public:
	B();
	~B();

private:
	A* a;
	
};

#endif
//#include "B_h"区域尾部

class A {

public:
	A();
	~A();

private:
	B* b;

};

#endif 

现在,我们便可以担当编译器,对上面的代码进行人工编译操作。
编译的方式是 从上到下顺序编译 ,和一般程序的顺序执行一样。

根据代码顺序编译的结构,我们可以看出,类A和类B的定义次序,是 类B在先,类A在后 。

那么当编译进行到第23行时,需要为类B分配一个A的指针类型的成员变量。而此时还未对类A进行定义,对类A的定义操作还未被执行。因此编译器就会报错,提示类A为不明确的类型。

到这里我们可以知道,报错是因为两个类头文件相互包含,且各自定义了对方类型的成员变量时,编译器编译发现了 类型定义次序的混乱 。

可供参考的解决方法
  1. 只在一个类中定义访问另一个类的指针。也就是只在类A中定义B的指针类型的成员变量,B中不定义,将A与B的双向访问逻辑,改善成A到B的单项访问逻辑。
  2. 在源文件中包含彼此的头文件。也就是A和B的双向访问,不再使用彼此的成员函数指针,而是通过在源文件中动态定义对方类型的指针来实现。
关于作者

感谢阅读!本文是我作为UE5底层开发初学者的学习笔记,希望对你有所帮助。
当然,内容比较冗长,如有不严谨、不正确的地方,还望多多指正,非常感谢!

相关知识参考博客

1. C++(1):认识include、ifndef和ifdef
2. #pragma once用法总结

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/655944.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号