c++之超前引用(头文件相互包含)

定义:

        超前引用是指一个类型在定义之前就被用来定义变量和声明函数。一般情况下,C/C++要求所有的类型必须在使用前被定义,但是在一些特殊情况下,这种要求无法满足,例如,在类A中类B的对象指针,但是类B中也同时包含类A的对象.

类A.h头文件定义如下:

#ifndef A_H
#define A_H
class B;
class A 
{ 
    int i; 
    B *b; 
}

类B.h头文件定义如下:

#ifndef B_H
#define B_H
#include <A.h>
class B 
{ 
    int i; 
    A b; 
};

      上例分析:类A和类B需要彼此互相引用,这样必然有一个类会先被定义,而另外一个类后被定义,这样在先被定义的类引用后被定义的类的时候,就导致了所谓的超前引用

解决办法:

1) 使用类声明

在超前引用一个类之前,首先用一个特殊的语句说明该标识符是一个类名,即将被超前引用。其使用方法是:

       a)  用class ClassB;声明即将超前引用的类名
       b)  定义class ClassA
       c)  定义class ClassB;
       d)  编制两个类的实现代码。

上述方法适用于所有代码在同一个文件中,一般情况下,ClassA和ClassB分别有自己的头文件和cpp文件,这种
方法需要演变成:

       a) 分别定义ClassA和ClassB,并在cpp文件中实现之
       b) 在两个头文件的开头分别用class ClassB;和class ClassA;声明对方
       c) 在两个cpp文件中分别包含另外一个类的头文件

 NOTE:这种方法切记不可使用类名来定义变量和函数的变量参数,只可用来定义引用或者指针。

2) 使用全局变量
       由于全局变量可以避免超前引用,不用赘述。我的习惯是,把类对象的extern语句加在该类头文件的最后,大家喜欢
怎样写那都没有什么大问题,关键是保证不要在头文件中胡乱包含。

3) 使用基类指针。
    这种方法是在引用超前引用类的地方一律用基类指针。而一般情况下,两个互相引用的类并不涉及其基类,因此不会造成
超前引用。

 

一、类嵌套的疑问

假设我们有两个类A和B,分别定义在各自的头文件A.h和B.h中,但是在A中要用到B,B中也要用到A,但是这样的写法当然是错误的:

A.h头文件包含的内容
class B;
class A
{
    public:
    B b;
};

B.h头文件包含的内容
#include <A.h>
class B
{
    public:
    A a;
};

       因为在A对象中要开辟一块属于B的空间,而B中又有A的空间,是一个逻辑错误,无法实现的。在这里我们只需要把其中的一个A类中的B类型成员改成指针形式 就可以避免这个无限延伸的怪圈了。为什么要更改A而不是B?因为就算你在B中做了类似的动作,也仍然会编译错误,表面上这仅仅上一个先后顺序的问题。

      为什么会这样呢?因为C++编译器自上而下编译源文件的时候,对每一个数据的定义,总是需要知道定义的数据的类型的大小。在预先声明语句class B;之后,编译器已经知道B是一个类,但是其中的数据却是未知的,因此B类型的大小也不知道。这样就造成了编译失败,VC++6.0下会得到如下编译错 误:error C2079: 'b' uses undefined class 'B'.  将A中的b更改为B指针类型之后,由于在特定的平台上,指针所占的空间是一定的(在Win32平台上是4字节),这样可以通过编译。

二、不同头文件中的类的嵌套

       在实际编程中,不同的类一般是放在不同的相互独立的头文件中的,这样两个类在相互引用时又会有不一样的问题。重复编译是问题出现的根本原因。为了保证头文件仅被编译一次,在C++中常用的办法是使用条件编译命令。在头文件中我们常常会看到以下语句段(以VC++6.0自动生成的头文件为例):

#if !defined(AFX_STACK_H__1F725F28_AF9E_4BEB_8560_67813900AE6B__INCLUDED_)
#define AFX_STACK_H__1F725F28_AF9E_4BEB_8560_67813900AE6B__INCLUDED_
    //很多语句……
#endif

      其中首句#if !defined也经常做#ifndef,作用相同。意思是如果没有定义过这个宏,那么就定义它,然后执行直到#endif的所有语句。如果下次再遇到这段代码,由于已经定义了那个宏,因此重复的代码不会被再次执行。这实在是一个巧妙而高效的办法。在高版本的VC++上,还可以使用这个命令来代替以上的所有:
      #pragma once
它的意思是,本文件内的代码只被使用一次。但是不要以为使用了这种机制就全部搞定了,比如在以下的代码中:

//文件A.h中的代码
#pragma once
#include "B.h"

class A
{
    public:
    B* b;
};

//文件B.h中的代码
#pragma once
#include "A.h"

class B
{
    public:
    A* a;
};

这里两者都使用了指针成员,因此嵌套本身不会有什么问题,在主函数前面使用#include "A.h"之后,主要编译错误如下:
      error C2501: 'A' : missing storage-class or type specifiers
仍然是类型不能找到的错误。其实这里仍然需要前置声明。分别添加前置声明之后,可以成功编译了。代码形式如下:

//文件A.h中的代码
#pragma once
#include "B.h"

class B;
class A
{
    public:
    B* b;
};

//文件B.h中的代码
#pragma once
#include "A.h"

class A;
class B
{
    public:
    A* a;
};

      这样至少可以说明,头文件包含代替不了前置声明。有的时候只能依靠前置声明来解决问题。我们还要思考一下,有了前置声明的时候头文件包含还是必要的吗?我们尝试去掉A.h和B.h中的#include行,发现没有出现新的错误。那么究竟什么时候需要前置声明,什么时候需要头文件包含呢?

总结:

      头文件包含其实是一件很烦(繁)琐的工作,不但我们看着都累,编译器编译的时候也很累,再加上头文件中常常出现的宏定义,感觉各种宏定义的展开是非常耗时间的,远不如自定义函数来得速度,我仅就不同头文件,原文件间的句则结构问题提出两点原则,仅供参考:

      1) 如果可以不包含头文件,那就不要包含了。这时候前置声明可以解决问题。如果使用的仅仅是一个类的指针,没有使用会这个类的具体对象(非指针,非引用),也没有访问到类的具体成员,那么前置声明就可以了。因为指针这一数据类型的大小是特定的,编译器可以获知。

      2)尽量在cpp文件中包含头文件,而非在头文件中。假设类A的一个成员是一个指向类B的指针,在类A的头文件中使用类B的前置声明便可以成功,那么在A的实现中我们需要访问B的具体成员,因此需要包含头文件,那么我们应该在类A的实现部分(CPP文件)包含类B的头文件而非声明部分。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

breakpoints_

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值