1.虚基类
考虑这样一种情况:当某个类的部分或者全部直接基类是另一个共同基类派生而来,这些直接基类从上一级基类继承而来的成员就一定拥有相同的名称,这样就会产生二义性问题。
解决办法:当派生类和直接基类产生了二义性问题-->加类的作用域。
当派生类和间接基类产生了二义性问题-->虚基类。
2.虚基类的说明:
class 派生类名:virtual 访问权限 基类名
{派生类定义};
注意:在定义派生类时将需要继承的基类进行虚化声明,虚基类的说明在派生类的定义中完成。
作用:将基类说明为虚基类之后,无论虚基类产生多少个派生类都不产生基类副本——多个派生类共用一个基类。
注意:若一个基类派生出多个派生类,必须在每个派生类的定义中将基类声明为虚基类,任何一个派生类未进行声明,那么系统就会为该派生类产生基类副本。
example 1
class One
{int b;};
class Baseone:virtual public One
{int b1;};
class Basetwo:virtual public One
{int b2;};
class Basethree:public Baseone, public Basetwo
{};
3.虚基类的初始化
(1)派生类构造函数的调用顺序:基类构造-->子对象构造-->派生类构造。
(2)虚基类的构造在非虚基类构造之前完成,若同一层有两个虚基类,按说明顺序调用构造。(3)虚基类只构造一次。
(4)若虚基类是非虚基类派生类,则先调用非虚基类构造。
(5)析构顺序严格与构造相反。
example 2
#include <iostrem.h>
class Base1
{
public:
Base1()
{
cout<<"Base1 construction."<<endl;
}
};
class Base2
{
public:
Base2()
{
cout<<"Base2 construction."<<endl;
}
};
class Level1:public Base2, virtual public Base1
{
public:
Level1() {cout<<"Level1 construction."<<endl;}
};
class Level2:public Base2, virtual public Base1
{
public:
Level2() {cout<<"Levle2 construction."<<endl;}
};
class Toplevel:public Level1, virtual public Level2
{
public:
Toplevel() {cout<<"Toplevel construction."<<endl;}
};
int main()
{
Toplevel obj;
}
程序输出:
Base1 construction.
Base2 construction.
Levle2 construction.
Base2 construction.
Level1 construction.
Toplevel construction.
分析:obj是Toplevel的对象,Toplevel有一个虚基类和一个非虚基类,按照规则,先构造虚基类,即Level2。而Level2又有一个虚基类和非虚基类,同样先构造虚基类,即Base1,Base1构造之后会构造Level2的非虚基类Base2,最后才构造Level2。至此,Toplevel的虚基类构造完成,下面该构造TopLevel的非虚基类Level1,首先构造器虚基类Base1,因为Base1已经构造过,所以不再构造,下面是Base2,最后是Level1。最后一步就是
构造Toplevel了。
example 3
#include <iostrem.h>
class A
{
public:
A(char i) {cout<<"A construction "<<i<<endl;}
~A() {cout<<"A destruction."<<endl;}
};
class B:virtual public A
{
public:
B(char i, char j):A(i)//由于虚基类A属于有参构造,所以必须在派生类B中调用A的构造
{
cout<<"B construction "<<j<<endl;
}
~B() {cout<<"B destruction."<<endl;}
private:
char b;
};
class C:virtual public A
{
public:
C(char i, char j):A(i)//尽管派生类C并没有调用虚基类A的构造函数,因为在派生类B中已经
//调用过,但C的构造函数中必须要声明对虚基类A的有参调用。
{
cout<<"C construction "<<j<<endl;
}
};
class D:public B, public C
{
public:
D(char i, char j, char k, char l, char m, char n):
C(k,l), B(i,j), A(n), aa(m)
{
cout<<"D construction."<<endl;
}
private:
A aa;
};
int main()
{
D obj(‘a‘, ‘b‘, ‘c‘, ‘d‘, ‘e‘, ‘f‘);
}
程序输出:
A construction f
B construction b
C construction d
A construction e
D construction.
A destruction.
B destruction.
A destruction.
注意:在D的构造函数中有A(i),这个位置加上A(i)是必须的。A的构造用的是f。
分析:在D构造中加A(i)是必须的,因为A的构造会在B和C的构造之前,也就是说传给B和C的参数是不能用来构造A的,所以A的有参构造必须来自于D,并且取的是D传递给A的参数值。
4.调用顺序
(1)所有虚基类构造函数按照它们被继承的顺序构造。
(2)所有非虚基类的构造函数按照它们被继承的顺序构造。
(3)所有子对象按照它们的声明顺序构造。
(4)派生类自己的构造:
A.在一个初始化列表中同时出现虚基类和非虚基类成员函数的调用,虚基类优先于非虚基类。
B.同一层的虚基类按照说明顺序调用构造,非虚基类按照继承顺序调用构造。
C.所有的虚基类在继承中只构造一次,虚基类的每个子对象都要调用一次构造。
5.虚基类的赋值兼容规则
(1)派生类地址可以赋值给虚基类指针。
(2)虚基类引用可以引用派生类对象。(都是大的可以赋值给小的)