一个派生类的基类只有一个则称之为单继承。
多重继承:一个派生类有两个或多个基类,派生类从两个或多个基类中继承所需的属性。
多重继承要和多级派生区分,多级派生是指一个类派生出一个子类,这个子类再派生出另一个子类,以此类推。
对于多重继承,c++和python等支持多重继承,但是Java是不支持的。Java 用接口解决多重继承的问题,但是不是通过多重继承这种方式做的。
声明多重继承的方法,比如:
class D:public A,private B,protected C
{
D():A().B(),C(){}//用初始化列表的方式调用A B C的构造函数
}
派生类构造函数的具体形式规定为:
派生类构造函数名(总形式参数表列):
基类1构造函数(实际参数表列),
基类2构造函数(实际参数表列),
基类3构造函数(实际参数表列){ 派生类中新增数据成员初始化语句}
对于上面的形式来说,实际参数表列通常都是来自于总形式参数表列的。派生类中新增数据成员的初始化也可以和基类构造函数一样写在初始化表列中。
二义性:二义性问题的发生时很正常的。
两个基类中的数据成员名相同,比如上面的例子中的Student和Teacher类中的名字如果都定义为变量name,那么就需要区分了,解决方式如下:
在标识符前用基类名做前缀,借助于域运算符:Teacher::name和Student::name
二义性发生的情况:
1. 两个基类有同名成员:包括成员函数和成员数据
解决方式为:(1)在main函数中,可以用基类名来限定,比如下面的程序:
c1. A::a=3;
c1. A::display();
在派生类中针对当前对象,比如下面的程序:
A::a=3;
A::display();
this->B::display();//调用当前对象中基类B中的display。
(2)另一种更清晰的表达,在C类中:
数据成员:
int b;
int A::a;
int B::a;
成员函数:
void show();
void A::display()
void B::display()
上面的表达让我们对(1)中的程序有了更清晰的认识。
2. 基类和派生类有同名的成员
此时C类中的数据成员:
int a;
int A::a;
int B::b;
C类中的成员函数:
void display();
void A::void display();
void B::void display();
此时的解决方式如下面的程序所示:
在派生类外访问派生类C中的成员:
C c1;
c1. a=3;
c1. display()'
在派生类外访问基类A中的成员:
c1.A::a=3;
c1 . A::display();
规则:同名覆盖:
基类的同名成员在派生类中被屏蔽,成为“不可见的”;
对于成员函数,只有函数名和参数个数相同、类型向匹配的时候才是同名函数!!!
如果只有函数名相同而参数不同美术与函数重载!!
3. 两个基类从同一个基类派生
对于上面的例子来说,派生类C中的数据成员:
int A::a;
int A::a1;
int B::a;
int B::a2;
int a3;
派生类C中的 成员函数有:
void A::display()
void B::display()
void show();
因此,c1.A::a=3是合法的,但是c1.a=3时不合法的,c1.N::a=3也是不合法的。
C直接从A B继承,它看不到N,所以c1.N::a=3也是不合法的。
A::a和B::a是N类成员的拷贝,A::a和B::a占用不同的空间,因此合法访问为:c1.A::a=3;c1.B::display();
但是实际上,A B中的a和display是来自于同一个基类N的,是同源的,那么A和B中的来自同一基类的成员是否可以看成一个呢?这就引出了虚基类!!!!
也就是说我们希望实现对于上面的C来说:
数据成员:
int a;
int A::a1;
int B::a2;
int a3;
成员函数:
display()
void show();
这样带来的好处就是C在引用a、display
的时候不必再区分A和B类,直接说它们来自于N 就可以。所以为了达成这样的希望引入虚基类:继承间接共同基类时只保留一份成员。引入虚基类之后的程序如下图所示:
上面的程序中,A和B对N是虚继承。N就是虚基类。上面的程序带来的效果就是我们希望的:
C类中数据成员:
int a;
int A::a1;
int B::a2;
int a3;
成员函数:
display()
void show();
虚基类定义的一般形式为:
class 派生类名:virtual 继承方式 基类名
效果:当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次。
需要注意的第一点是由于最后N中的成员在C中只保留一份,因此C的构造函数中在调用N A B的构造函数的时候,对于N中数据成员的初始化应该都是同一个参数i,而最好不要分别赋予不同的值,因为最后C只保留一份N中成员,这个值肯定是唯一的。所以如果N A B的构造函数中对于N中数据成员初始化的参数不同的话就没有意义了。就像下图所示:就没有意义了,不要这样做。虽然实际上,下面的程序也能运行出正确的结果,因为C中构造函数在运行的时候,根本不会调用A B中对N构造函数的调用,只会调用C中自己对N构造函数的调用,但是为了更完美额程序设计,最好不要设计出下图中的程序。这样无疑是多此一举的,没有任何的实际意义与作用。
对于上面第一幅图中的两个函数来说,如果,不是虚继承,那么对于C来说,它的构造函数中只调用A B的构造函数就可以了,不需要调用N的构造函数;
而现在是虚继承,间接基类中的成员只会保留一份,所以间接基类N中构造函数也要调用。
规定:在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。
c++编译系统只执行最后的派生类(如C)对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类A,B)对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化,只保留一份。
谨慎使用多重继承,不提倡。能用单一继承不要使用多重继承。Java等并不支持多重继承,它们用别的方式实现这种功能。