6、虚基类
从上面的学习中,我们发现如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员。C++提供虚基类(virtual base class)的方法,使得在继承间接共同基类时只保留一份成员。
class A//声明基类A
{…};
class B :virtual public A //声明类B是类A的公用派生类,A是B的虚基类
{…};
class C :virtual public A //声明类C是类A的公用派生类,A是C的虚基类
{…};
注意: 虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。因为一个基类可以在生成一个派生类时作为虚基类,而在生成另一个派生类时不作为虚基类。
声明虚基类的一般形式为
class 派生类名: virtual 继承方式 基类名
经过这样的声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次。为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。
如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在其所有派生类(包括直接派生或间接派生的派生类)中,通过构造函数的初始化表对虚基类进行初始化。
class A//定义基类A
{A(int i){ } //基类构造函数,有一个参数
…};
class B :virtual public A //A作为B的虚基类
{B(int n):A(n){ } //B类构造函数,在初始化表中对虚基类初始化
…};
class C :virtual public A //A作为C的虚基类
{C(int n):A(n){ } //C类构造函数,在初始化表中对虚基类初始化
…};
class D :public B,public C //类D的构造函数,在初始化表中对所有基类初始化
{D(int n):A(n),B(n),C(n){ }
…};
在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类B和类C) 对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。
class Student1:virtual public Person
{
Student1(string nam,char s,int a,float sco): Person(nam,s,a), score (sco){}
protected:
float socre;
}
不提倡在程序中使用多重继承,只有在比较简单和不易出现二义性的情况或实在必要时才使用多重继承,能用单一继承解决的问题就不要使用多重继承。也是由于这个原因,有些面向对象的程序设计语言(如Java,Smalltalk)并不支持多重继承。
7、基类与派生类的转换
由于派生类中包含从基类继承的成员,因此可以将派生类的值赋给基类对象,在用到基类对象的时候可以用其子类对象代替。
(1) 派生类对象可以向基类对象赋值。
可以用子类(即公用派生类)对象对其基类对象赋值。如
A a1; //定义基类A对象a1
B b1; //定义类A的公用派生类B的对象b1
a1=b1; //用派生类B对象b1对基类对象a1赋值
在赋值时舍弃派生类自己的成员。赋值只是对数据成员赋值,对成员函数不存在赋值问题。子类型关系是单向的、不可逆的。B是A的子类型,不能说A是B的子类型。只能用子类对象对其基类对象赋值,而不能用基类对象对其子类对象赋值,因为基类对象不包含派生类的成员,无法对派生类的成员赋值。同理,同一基类的不同派生类对象之间也不能赋值。
(2) 派生类对象可以替代基类对象向基类对象的引用进行赋值或初始化。
A a1; //定义基类A对象a1
B b1; //定义公用派生类B对象b1
A& r=a1; //定义基类A对象的引用变量r,并用a1对其初始化
A& r=b1;//定义基类A对象的引用变量r,并用派生类B对象b1,对其初始化
此时r并不是b1的别名,也不与b1共享同一段存储单元。它只是b1中基类部分的别名,r与b1中基类部分共享同一段存储单元,r与b1具有相同的起始地址。
(3)如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象。(4) 派生类对象的地址可以赋给指向基类对象的指针变量,也就是说,指向基类对象的指针变量也可以指向派生类对象。
通过指向基类对象的指针,只能访问派生类中的基类成员,而不能访问派生类增加的成员。
8、继承和组合
对象成员的类型可以是本派生类的基类,也可以是另外一个已定义的类。在一个类中以另一个类的对象作为数据成员的,称为类的组合(composition)。
继承是纵向的,组合是横向的。
class Professor:public Teacher //教授类
{public:
┆
private:
BirthDate birthday; //BirthDate类的对象作为数据成员
};
对类库中类的声明一般放在头文件中,类的实现(函数的定义部分)是单独编译的,以目标代码形式存放在系统某一目录下。