多重继承派生类
除去一个类从一个基类派生,C++还支持一个派生类同时继承多个基类
MI:有多个直接基类的类
1.多重继承派生类的定义
如果已经定义了多个基类,那么定义多重继承的派生类的形式为:
class 派生类名:访问标号1 基类名1,访问标号2 基类名2,... { //类体成员列表
};
举个例子
class A { };
class B : public A { }; //A-B
class C : public A { }; //A-C
class D : public B,public C { }; //B-D,C-D
这里的D类就是一个MI.
2.多重继承派生类的构造函数
多重继承派生类的构造函数形式与单一继承时的构造函数形式基本相同,只是在派生类的构造函数初始化列表中调用多个基类构造函数。
一般形式为:
派生类名(形式参数列表):基类名1(基类1构造函数实参列表),基类名2(基类2构造函数实参列表),...,成员对象名1(子对象1属类构造函数实参列表),...,派生类初始化列表
{派生类初始化函数体
}
其调用顺序是:
(1)调用基类构造函数,各个基类按定义时的次序先后调用;
(2)调用成员对象构造函数,各个子对象按声明时的次序先后调用;
(3)执行派生类初始化列表;
(4)执行派生类初始化函数体;
例:多重继承举例
#include<iostream>
using namespace std; class Base1 {private:int b1;public:Base1() {b1=0;cout<<"默认构造Base1:"<<"b1="<<b1<<endl;}Base1(int i) {b1=i;cout<<"构造Base1:"<<"b1="<<b1<<endl;}
};class Base2 {private:int b2;public:Base2() {b2=0;cout<<"默认构造Base2:"<<"b2="<<b2<<endl;}Base2(int j) {b2=j;cout<<"构造Base2:"<<"b2="<<b2<<endl;}
};class Base3 {public:Base3() {cout<<"默认构造Base3:"<<endl;}
};class Derive : public Base1,public Base2,public Base3 {private:Base1 memberBase1;Base2 memberBase2;Base3 memberBase3;public:Derive() {cout<<"默认构造函数Derive."<<endl; }Derive(int a,int b,int c,int d): Base1(a),Base2(b),memberBase1(c),memberBase2(d) {cout<<"构造Derive."<<endl;}
};int main()
{cout<<endl<<"创建派生类对象obj1:"<<endl;Derive obj1;cout<<endl<<"创建派生类对象(1,2,3,4):"<<endl;Derive obj2(1,2,3,4);return 0;
}
运行结果:
创建派生类对象obj1:
默认构造Base1:b1=0//基类默认构造函数,下面2个也是
默认构造Base2:b2=0
默认构造Base3:
默认构造Base1:b1=0//成员对象的构造函数,下面2个也是
默认构造Base2:b2=0
默认构造Base3:
默认构造函数Derive,//派生类默认构造函数创建派生类对象(1.2.3.4):
构造Base1:b1=1 //基类构造函数,下面2个也是
构造Base2:b2=2
默认构造Base3:
构造Base1:b1=3 //成员对象的构造函数,下面2个也是
构造Base2:b2=4
默认构造Base3:
构造Derive. //派生类构造函数
二义性问题及名字支配规则
1.二义性问题
多重继承时,多个基类可能出现同名的成员。在派生类中如果使用一个表达式的含义能解释为可以访问多个基类的成员,则这种对基类成员的访问就是不确定的,称这种访问具有二义性。C++要求派生类对基类成员的访问必须是无二义性的
例如:
class A {public:void fun() { cout<<"a.fun"<<endl; }
};
class B {public:void fun() { cout<<"b.fun"<<endl; }void gun() { cout<<"b.gun"<<endl; }
};
class C:public A,public B {public:void gun() { cout<<"c.gun"<<endl; } //重写gun()void hun() { fun(); } //出现二义性
};
int main()
{C c,*p=&c;return 0;
}
使用成员名限定可以消除二义性,例如:
//成员名限定消除二义性
c.A::fun();
c.B::fun();
p->A::fun();
p->B::fun();
基本形式为:
对象名.基类名::成员名
对象指针名->基类名::成员名
2.名字支配规则
C++对于在不同的作用域声明的名字,可见性原则是:如果存在两个或多个具有包含关系的作用域,外层声明了一个名字,而内层没有再次声明相同的名字,那么外层名字在内层可见;如果在内层声明了相同的名字,则外层名字在内层不可见,这时称内层名字隐藏(或覆盖)了外层名字,这种现象称为隐藏规则
在类的派生层次结构中,基类的成员和派生类新增的成员都具有类作用域,二者的作用域是不同的:基类在外层,派生类在内层
如果派生类声明了一个和基类成员同名的新成员,派生的新成员就覆盖了基类同名成员,直接使用成员名只能访问到派生类的成员
如果派生类中声明了与基类成员函数同名的新函数,即使函数的参数不同,从基类继承的同名函数的所有重载形式也都会被覆盖
如果要访问被覆盖的成员,就需要使用基类名和作用域限定运算符来限定
派生类D中的名字N覆盖基类B中同名的名字N,称为名字支配规则。如果一个名字支配另一个名字,则二者之间不存在二义性,当选择该名字时,使用支配者的名字,如:
c.gun(); //使用C::gun
如果要使用被支配者的名字,则应使用成员名限定,例如:
c.B::gun(); //使用B::gun
虚基类
C++引入虚基类的目的是为了解决多继承时可能出现的冲突问题。当一个类通过多个路径继承了同一个基类时,如果不使用虚基类,那么在派生类中就会存在多个基类子对象,这可能会导致数据重复和二义性的问题。
通过使用虚基类,可以确保在派生类中只有一个基类子对象,从而避免数据重复和二义性的问题。虚基类的成员在派生类中只有一个副本,不会重复出现。
另外,虚基类还可以实现多态性,能够让派生类对象按照基类的指针或引用进行使用,从而提高代码的灵活性和可扩展性。
1.虚基类的定义
虚基类是在派生类定义时,指定继承方式时声明的。声明虚基类的一般形式为:
class 派生类名: virtual 访问标签 虚基类名,... { //类体成员列表
};
还有一种形式是
class 派生类名: 访问标签 virtual 虚基类名,... { // 成员列表
};
需要注意,为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。否则仍然会出现对基类的多次继承(我们下面会举例子)
例:虚基类举例
#include<iostream>
using namespace std; class A { //声明为基类Apublic:A(int n) { //A类的构造函数 nv=n;cout<<"Member of A"<<endl;} void fun() {cout<<"fun of A"<<endl;}private:int nv;
};class B1: virtual public A { //声明A为虚基类 public:B1(int a):A(a) { //B1类的构造函数 cout<<"Member of B1"<<endl;}private:int nv1;
};class B2: virtual public A { //声明A为虚基类public:B2(int a):A(a) { //B2类的构造函数cout<<"Member of B2"<<endl; } private:int nv2;
};class C: public B1,public B2 {public://派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用C(int a):A(a),B1(a),B2(a) {cout<<"Member of C"<<endl;}void fund() {cout<<"fun of C"<<endl;}private:int nvd;
};int main()
{C c1(1);c1.fund();c1.fun(); //不会产生二义性return 0;
}
现在C对象只将包含A对象的一个副本。从更本质的说,继承的B1和B2对象共享一个A对象,而不是各种引入自己的A对象副本。这样子调用A类方法就不会有二义性了(不知道调用B!继承的还是B2继承的A类方法)。
如果我们不引入虚基类,再看看这个例子
#include<iostream>
using namespace std;class A { //声明为基类A
public:A(int n) { //A类的构造函数 nv = n;cout << "Member of A" << endl;}void fun() {cout << "fun of A" << endl;}
private:int nv;
};class B1 : virtual public A { //声明A为虚基类
public:B1(int a) :A(a) { //B1类的构造函数 cout << "Member of B1" << endl;}
private:int nv1;
};class B2 : public A { //注意这里没有声明A为虚基类
public:B2(int a) :A(a) { //B2类的构造函数cout << "Member of B2" << endl;}
private:int nv2;
};class C : public B1, public B2 {
public://派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用C(int a) :A(a), B1(a), B2(a) {cout << "Member of C" << endl;}void fund() {cout << "fun of C" << endl;}
private:int nvd;
};int main()
{C c1(1);c1.fund();c1.fun(); //产生了二义性return 0;
}
我们将B2后面的virtual去掉,使A类失去虚基类的性质。发现上面这个程序出现了二义性,编译器不知道调用B1继承的A类方法还是B2继承的A类方法。
2.虚基类的初始化
如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在其所有派生类(包括直接派生和间接派生)中,都要通过构造函数的初始化表对虚基类进行初始化。例如:
class A { public: A(int) { } }; //定义构造函数有参数的基类
class B: virtual public A {public:B(int a):A(a) { } //对基类A初始化
};
class C: virtual public A {public:C(int a):A(a) { } //对基类A初始化
};
class D: public B,public C {public:D(int a):A(a),B(a),C(a) { }
};
在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化
关于虚基类的说明:
(1)一个类可以在一个类族中即被用作虚基类,也被用作非虚基类
(2)派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;如果未列出,则表示使用该虚基类的默认构造函数
(3)在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行多重继承应用举例
#include<iostream>
using namespace std; enum Color { //颜色枚举类型 Red,Yellow,Green,White
};class Circle { //圆类Circle的定义private:float radius;public:Circle(float r) {radius=r;cout<<"Circle initialized!"<<endl;} ~Circle() {cout<<"Circle destroyed!"<<endl;}float Area() {return 3.1415926*radius*radius;}
};class Table { //桌子类Table的定义private:float height;public:Table(float h) {height=h;cout<<"Table initialized!"<<endl;}~Table() {cout<<"Table destroyed!"<<endl;}float Height() {return height;}
};class RoundTable: public Table,public Circle { //圆桌类的定义private:Color color;public:RoundTable(float h,float r,Color c);int GetColor() {return color;}~RoundTable() {cout<<"RoundTable destroyed!"<<endl;}
};RoundTable::RoundTable(float h,float r,Color c):Table(h),Circle(r) { //圆桌构造函数的定义color=c;cout<<"RoundTable initialized!"<<endl;
}int main()
{RoundTable cir_table(15.0,2.0,Yellow);cout<<"The table properties are:"<<endl;cout<<"Height="<<cir_table.Height()<<endl; //调用Table类的成员函数cout<<"Area="<<cir_table.Area()<<endl; //调用circle类的成员函数cout<<"Color="<<cir_table.GetColor()<<endl; //调用RoundTable 类的成员函数return 0;
}
运行结果:
Table initialized!
Circle initialized!
RoundTable initialized!
The table properties are:
Height=15
Area=12.5664
Color=1
RoundTable destroyed!
Circle destroyed!
Table destroyed!