C++-C++头文件相互包含
三个类HelloWorld,BaseLayer,HeritedLayer分别在对应的XXX.h头文件及XXX.cpp文件中申明定义,HeritedLayer继承自BaseLayer。
其中HelloWorld中有一个BaseLayer指针类型的成员变量;
BaseLayer中有一个HelloWorld指针类型的成员变量;
现问题如何在头文件中包含彼此才能够顺利通过编译?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
前置声明是你要的答案
首先我们需要问一个问题是:为什么两个类不能互相包含头文件?所谓互相包含头文件,我举一个例子:我实现了两个类:图层类CLayer和符号类CSymbol,它们的大致关系是图层里包含有符号,符号里定义一个相关图层指针,具体请参考如下代码(注:以下代码仅供说明问题,不作为类设计参考,所以不适宜以此讨论类的设计,编译环境为Microsoft Visual C++ 2005,,Windows XP + sp2,以下同):
//Layer.h
// 图层类
#pragma once
#include "Symbol.h"
class CLayer
{
public:
CLayer(void);
virtual ~CLayer(void);
void CreateNewSymbol();
private:
CSymbol* m_pSymbol; // 该图层相关的符号指针
};
// Symbol.h
// 符号类
#pragma once
#include "Layer.h"
class CSymbol
{
public:
CSymbol(void);
virtual ~CSymbol(void);
public:
CLayer *m_pRelLayer; // 符号对应的相关图层
};
// TestUnix.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "Layer.h"
#include "Symbol.h"
void main( void )
{
CLayer MyLayer;
}
现在开始编译,编译出错,现在让我们分析一下编译出错信息(我发现分析编译信息对加深程序的编译过程的理解非常有好处)。
首先我们明确:编译器在编译文件时,遇到#include "x.h"时,就打开x.h文件进行编译,这相当于把x.h文件的内容放在#include "x.h"处。
编译信息告诉我们:它是先编译TestUnix.cpp文件的,那么接着它应该编译stdafx.h,接着是Layer.h,如果编译Layer.h,那么会编译Symbol.h,但是编译Symbol.h又应该编译Layer.h啊,这岂不是陷入一个死循环?
呵呵,如果没有预编译指令,是会这样的,实际上在编译Symbol.h,再去编译Layer.h,Layer.h头上的那个#pragma once就会告诉编译器:老兄,这个你已经编译过了,就不要再浪费力气编译了!那么编译器得到这个信息就会不再编译Layer.h而转回到编译Symbol.h的余下内容。
当编译到CLayer *m_pRelLayer;这一行编译器就会迷惑了:CLayer是什么东西呢?我怎么没见过呢?那么它就得给出一条出错信息,告诉你CLayer没经定义就用了呢?
在TestUnix.cpp中#include "Layer.h"这句算是宣告编译结束(呵呵,简单一句弯弯绕绕不断),下面轮到#include "Symbol.h",由于预编译指令的阻挡,Symbol.h实际上没有得到编译,接着再去编译TestUnix.cpp的余下内容。
当然上面仅仅是我的一些推论,还没得到完全证实,不过我们可以稍微测试一下,假如在TestUnix.cpp将#include "Layer.h"和#include "Symbol.h"互换一下位置,那么会不会先提示CSymbol类没有定义呢?实际上是这样的。当然这个也不能完全证实我的推论。
照这样看,两个类的互相包含头文件肯定出错,那么如何解决这种情况呢?一种办法是在A类中包含B类的头文件,在B类中前置盛明A类,不过注意的是B类使用A类变量必须通过指针来进行,具体见拙文:类互相包含的办法。为何不能前置声明只能通过指针来使用?通过分析这个实际上我们可以得出前置声明和包含头文件的区别。我们把CLayer类的代码改动一下,再看下面的代码:
// 图层类
//Layer.h
#pragma once
//#include "Symbol.h"
class CSymbol;
class CLayer
{
public:
CLayer(void);
virtual ~CLayer(void);
// void SetSymbol(CSymbol *pNewSymbol);
void CreateNewSymbol();
private:
CSymbol* m_pSymbol; // 该图层相关的符号
// CSymbol m_Symbol;
};
// Layer.cpp
#include "StdAfx.h"
#include "Layer.h"
CLayer::CLayer(void)
{
m_pSymbol = NULL;
}
CLayer::~CLayer(void)
{
if(m_pSymbol!=NULL)
{
delete m_pSymbol;
m_pSymbol=NULL;
}
}
void CLayer::CreateNewSymbol()
{
}
然后编译,出现一个编译警告:>f:mytestmytestsrctestunixlayer.cpp(16) : warning C4150: 删除指向不完整“CSymbol”类型的指针;没有调用析构函数
1> f:mytestmytestsrctestunixlayer.h(9) : 参见“CSymbol”的声明
看到这个警告,我想你一定悟到了什么。下面我说说我的结论:类的前置声明和包含头文件的区别在于类的前置声明是告诉编译器有这种类型,但是它没有告诉编译器这种类型的大小、成员函数和数据成员,而包含头文件则是完全告诉了编译器这种类型到底是怎样的(包括大小和成员)。
这下我们也明白了为何前置声明只能使用指针来进行,因为指针大小在编译器是确定的。上面正因为前置声明不能提供析构函数信息,所以编译器提醒我们:“CSymbol”类型的指针是没有调用析构函数。如何解决这个问题呢?在Layer.cpp加上#include "Symbol.h"就可以消除这个警告。
#ifndefine HELLO_WORLD
#ifdefine HELLO_WORLD
.......
//所有的其他头文件内容
#endif
头文件这样的书写方式,估计是能解决你的问题的,这个能防止头文件多次被引用
楼上@风雨飘零 正解,就算都引用了这些头文件,也不会重复编译的,实在不放心的话就都引用吧
//A.H
#pragma once
#include "B.h"//不加也可
class B;//前置声明类B
class A{
public:
B *a1;
int a;
};
//B.H
#pragma once
#include "A.h"//不加也可
class A;//前置声明类A
class B
{
public:
A* b1;
int b;
};
再加入头文件Ct.h
#pragma once
#include "B.h"
class Ct:public B
{
public:
int c;
};
//Main.CPP修改为
int _tmain(int argc, _TCHAR* argv[])
{
A c;
c.a1= new Ct;
c.a1->b=123;
printf("%d",c.a1->b);
getch();
return 0;
}
感谢kazaff大哥,我又学到东西了,加分加给楼上的吧