对于直接继承,一个基类只能被派生类继承一次;
对于间接继承,一个基类可以被派生类继承多次。
如图所示的继承结构中,基类A在类D中会产生两个副本。这时如果在D类中使用从A类继承的成员时,如果不指定其作用域,会出现同名冲突。
虚基类的基本思想是:将一个基类声明为虚基类时,不管它在派生类中被继承多少次,该基类中的成员在该派生类中始终只有一个副本,如图。
虚基类是通过关键字virtual实现的,定义虚基类的格式为:
class <派生类>:virtual <access><基类名>《,---,virtual<accesss<基类名>>》
{ --- } ;
或
class<派生类名>:<access>virtual <基类名>《,---,<access>virtual<基类名>》
{---} ;
例1:使用虚基类,使派生类中只有基类一个副本。
# include <iostream.h>
class CFurniture
{ protected:
int weight ;
public:
CFurniture( ){ }
void SetWeight (int i ) { weight=i ;}
int GetWeight ( ) {return weight ;}
};
{cout<<”sleeping ---\n’ ;}
class CBed:virtual public CFurniture //A 定义虚基类
{public :
CBed ( ){ }
void sleep ( )
} ;
class CSofa:virtual public CFurniture //B 定义虚基类
{ public:
CSofa ( ) { }
void WatchTV()
{cout<<”Watch TV . \n “ ;}
} ;
class CSleepSofa :public CBad ,public Csofa
{public :
CSleepSofa ( ){ }
void FoldOut ( )
{cout<<”Fold out the sofa 。 \n ” ;}
};
void main ()
{CSleepSofa ss ;
ss.SetWeight (20) ; //C
cout<<ss.GetWeight( )<<’\n’ ; //D
}
执行结果:
20
例 2
#include <iostream.h>
class A {
public:
int i;
void showa(){cout<<"i="<<i<<endl;}
};
class B: virtual public A //对类A进行了虚拟继承
{
public:
int j;
};
class C: virtual public A //对类A进行了虚拟继承
{
public:
int k;
};
class D : public B, public C
//派生类D的二基类B、C具有共同的基类A,但采用了虚拟继承
//从而使类D的对象中只包含着类A的1个实例
{
public:
int n;
void showall(){cout<<"i,j,k,n="<<i<< “ ,”<<j<< “ ,”<<k<< “ ,”<<n<<endl;}
};
void main() {
D Dobj; //说明D类对象
Dobj.i=11;
//若非虚拟继承时会出错!
// -- 因为“D::a”具有二义性
Dobj.j=22;
Dobj.k=33;
Dobj.n =44;
Dobj.showa();
//若非虚拟继承时会出错!
// -- 因为“D::showa”具有二义性
Dobj.showall();
}
程序执行后的显示结果如下:
/*
i=11
i,j,k,n=11 ,22 ,33 ,44
Press any key to continue
*/
虚基类的初始化
虚基类的初始化与一般的多重继承的初始化在语法上是一致的,但构造函数的调用次序有不同。
1.虚基类的构造函数在非虚基类的构造函数之前调用。如下例的B行
2.若同一层次中包含多个虚基类,这些虚基类的构造函数按它们的说明次序调用。
3.若虚基类由非虚基类派生而来,则仍然先调用基类的构造函数,再调用派生类的构造函数,如下例的A行。
4.对图的类层次结构,由于此时虚基类A在类D中只有一个副本,因此在创建D的对象时,无法确定是通过类B还是通过类C调用A的构造函数,为了解决这个矛盾,C++规定在这种情况下可以在类D中,直接调用类A的构造函数。
即在D类的构造函数的初始化成员列表中,不仅要调用类B和类C的构造函数,还必须调用类A的构造函数而且是最先调用。
因为在类D只有A的一个版本,所以调用类B和类C的构造函数时不再重复调用类A的构造函数了,即类A的构造函数只运行一次。
下面通过一个例子来说明这一个问题。
例3 虚基类的构造函数的调用次序
# include <iostream.h>
class CBase1
{public:
CBase1 ( ){cout<<”This is CBase1 class! \ n “ ;}
};
class CBase2
{public:
CBase2 ( ) {cout<<”This is CBase2 class ! \n “ ;}
};
class CDerive1:public CBase2 ,virtual public CBase1 //A
{
public:
CDerive1( )
{cout<<”This is CDerive1 class ! \n “ ;}
};
class CDerive2:public CBase2 ,virtual public CBase1 //A
{
public:
CDerive2 ( )
{cout<<”This is CDerive2 class ! \n “ ;}
};
class CTop:public CDerive1 ,virtual public CDerive2 //B
{public:
CTop ( )
{cout<<”This is CTop class ! \n “ ;}
};
void main ( )
{ CTop topObj ; }
执行结果:
This is CBase1 class !
This is CBase2 class !
This is CDerive2 class !
This is CBase2 class !
This is CDerive1 class !
This is CTop class !
例4 初始化虚基类成员
# include <iotream.h>
class CBase
{int x ,y ;
public:
CBase ( int a , int b )
{x=a , y=b ;}
show ( )
{cout<<x<<’\t’<<y<<endl ;}
};
class CDerive1:virtual public CBase //A声明CBase为虚基类
{public:
CDerive1( ):CBase(2,3)
{ }
};
class CDerive2:virtual public CBase //B声明CBase为虚基类
public:
CDerive2 ( ):CBase (4 ,5)
{ }
} ;
class CTop :public CDerive1,public CDerive2
{public:
CTop ( ):CBase (6 , 7 ) //C直接调用虚基类CBase的构造函数,
{ } //如果A、B行没有声明CBase为虚基类,则不可这样使用。
} ;
void main ( )
{CTop topObj ;
topObj.show() ;
}
执行结果;
6 7