4.4 多重继承
【定义】当一个派生类具有两个或多个基类时,这种派生方法成为多重继承或多基类派生。
4.4.1 多重继承派生类的声明
【语法形式】
class 派生类名:继承方式1 基类名1,.....,继承方式n 基类名n{
派生类新增的数据成员和成员函数
};
【示例】
#include<iostream>
using namespace std;
class X{
public:
X(int sub_x){
x=sub_x;
}
void visit(){ //同名函数
cout<<"X->x = "<<x<<endl;
}
void setX(int set_x){
x=set_x;
}
private:
int x;
};
class Y{
public:
Y(int sub_y){
y=sub_y;
}
void visit(){ //同名函数
cout<<"Y->y = "<<y<<endl;
}
void setY(int set_y){
y=set_y;
}
private:
int y;
};
class Z:private X,public Y{ //类Z私有继承类X,公有继承类Y
public:
Z(int sub_x,int sub_y,int sub_z):X(sub_x),Y(sub_y){
z=sub_z;
}
void visit(){ //同名函数
cout<<"Z->z = "<<z<<endl;
}
private:
int z;
};
int main()
{
Z z(10,20,30);
//z.X::visit(); //类Z私有继承类X,则X::visit()在派生类中的访问属性为私有,无法使用对象访问
z.Y::visit(); //使用作用域声明,防止二义性
z.Z::visit();
z.setY(40); //类Z公有继承类Y,则Y::setY()在派生类中的访问属性为公有,可以使用对象访问
z.Y::visit();
return 0;
}
【运行结果】
4.4.2 多重继承派生类的构造函数与析构函数
(1)语法形式:
派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),......,基类名n(参数表n)
{ 派生类新增成员的初始化语句 }
(2)参数总表中包含完成所有基类初始化所需的参数个数
(3)构造函数执行顺序
基类构造函数 ===>对象成员的构造函数 ===>派生类构造函数
若存在多个基类,则按照派生类声明时从左到右的顺序调用构造函数
【示例】
#include<iostream>
using namespace std;
class X{
public:
X(int sub_x){
x=sub_x;
cout<<"constructor -> x"<<endl;
}
void visit_x(){
cout<<"X->x = "<<x<<endl;
}
~X(){
cout<<"desconstructor -> x"<<endl;
}
private:
int x;
};
class Y{
public:
Y(int sub_y){
y=sub_y;
cout<<"constructor -> y"<<endl;
}
void visit_y(){
cout<<"Y->y = "<<y<<endl;
}
~Y(){
cout<<"desconstructor -> z"<<endl;
}
private:
int y;
};
class Z:public Y{ //公有继承类Y
public:
Z(int sub_x,int sub_y,int sub_z):x(sub_x),Y(sub_y){
z=sub_z;
cout<<"constructor -> z"<<endl;
}
void visit_z(){
cout<<"Z->z = "<<z<<endl;
}
void visit_y(){
Y::visit_y();
}
void visit_x(){
x.visit_x();
}
~Z(){
cout<<"desconstructor -> z"<<endl;
}
private:
int z;
X x; //类的组合,在类Z中定义类X的对象
};
int main()
{
Z z(10,20,30); //调用构造函数
z.visit_x();
z.visit_y();
z.visit_z();
return 0;
}
【运行结果】
4.4.3 虚基类
1、为什么要引入虚基类
一个类继承多个基类,而基类又有同一个基类类,则最底层的派生类中保留这个共同基类数据成员的多份同名成员,在访问同名成员时,必须在前加上基类名,否则将产生二义性。
【示例】
#include<iostream>
using namespace std;
class Base{
public:
Base(){
a=10;
cout<<" Base->a = "<<a<<endl;
}
protected:
int a;
};
class Base1:public Base{
public:
Base1(){
a=a+10;
cout<<" Base1->a = "<<a<<endl<<endl;
}
};
class Base2:public Base{
public:
Base2(){
a=a+20;
cout<<" Base2->a = "<<a<<endl<<endl;
}
};
class Derived:public Base1,public Base2{
public:
Derived(){
cout<<" Base1->a = "<<Base1::a<<endl;
cout<<" Base2->a = "<<Base2::a<<endl;
}
};
int main()
{
Derived obj;
return 0;
}
【运行结果】
【类层次图】
由类层次图可知,类Base1和类Base2同时存在同名的数据成员a,他们都是类Base成员的复制
但类Base1和类Base2中数据成员a具有不同的存储单元,存放不同的数据
由于类Derived中同时存在类Base1和类Base2的数据成员,因此在Derived构造函数中输出a的值,必须加上“::”作用域运算符
因此,为了解决二义性,C++引入虚基类
2、虚基类的概念
(1)C++中,使公共基类只产生一个复制,则可以将这个基类说明为虚基类
(2)语法形式:
class 派生类名:virtual 继承方式 基类名{
.....
}
【示例】
#include<iostream>
using namespace std;
class Base{
public:
Base(){
a=10;
cout<<" Base->a = "<<a<<endl;
}
protected:
int a;
};
class Base1:virtual public Base{
public:
Base1(){
a=a+10;
cout<<" Base1->a = "<<a<<endl<<endl;
}
};
class Base2:virtual public Base{
public:
Base2(){
a=a+20;
cout<<" Base2->a = "<<a<<endl<<endl;
}
};
class Derived:public Base1,public Base2{
public:
Derived(){
cout<<" Base1->a = "<<Base1::a<<endl;
cout<<" Base2->a = "<<Base2::a<<endl;
}
};
int main()
{
Derived obj;
return 0;
}
【运算结果】
由运算结果可知,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,可知Base中的a只保留一次。
【类层次图】
(3)虚基类与构造函数
【示例】
#include<iostream>
using namespace std;
class Base{
public:
Base(int sa){
a=sa;
cout<<" Base Constructor Called"<<endl;
}
protected:
int a;
};
class Base1:virtual public Base{
public:
Base1(int sa,int sb1):Base(sa){
b1=sb1;
cout<<" Base1 Constructor Called"<<endl;
}
protected:
int b1;
};
class Base2:virtual public Base{
public:
Base2(int sa,int sb2):Base(sa){
b2=sb2;
cout<<" Base2 Constructor Called"<<endl;
}
protected:
int b2;
};
class Derived:public Base1,public Base2{
public:
Derived(int sa,int sb1,int sb2,int sd):Base(sa),Base1(sb1,sa),Base2(sb2,sa){
d=sd;
cout<<" Derived Constructor Called"<<endl;
}
protected:
int d;
};
int main()
{
Derived obj(1,2,3,4);
return 0;
}
【运行结果】
【注意】
示例中:
【1】Base是一个虚基类,它只有一个带参数的构造函数,因此要求在派生类Base1、Base2和Derived的构造函数的初始化列表中,都必须带有对类Base的构造函数的调用。
【2】如果Base不是一个虚基类,在派生类Derived中的构造函数的初始化列表中调用Base的构造函数是错误的
【原因】:派生类只负责直接基类的构造函数的调用,即对直接基类中的成员变量赋值
【3】虚基类Base的构造函数只被Derived的构造函数调用一次,而Base1和Base2的调用被忽略。
(4)虚基类使用说明
【1】一个基类可作为派生类的虚基类同时,也可以作为派生类的非虚基类
【2】virtual与继承方式关键字的先后顺序无关紧要
virtual public或public virtual都是合法的