C++ 多继承类 虚基类

1.多继承派生类有多个基类或者虚基类,同一个类不能多次作为某个派生类的直接基类,但可以作为一个派生类的间接基类;
class QUEUE: STACK, STACK{/*…*/}; //错误,出现两次
class Q: S {S d;/*…*/}; //正确:采用委托d代理的方式
2. 委托代理在多数情况下能够满足需要,但当对象成员和基类存在共同的基类时,就可能对同一个物理对象重复初始化(可能是危险的和不必要的);
3.两栖机车AmphibiousVehicle继承基类陆用机车LandVehicle,委托对象成员水上机WaterVehicle完成水上功能。两栖机车可能对同一个物理对Engine初始化(启动)两次。
class Engine{ /*...*/};
class LandVehicle: Engine{/*...*/};
class WaterVehicle: Engine{/*...*/};
class AmphibiousVehicle: LandVehicle{WaterVehicle wv; /*...*/};
4.仅靠多继承仍然不能解决同一个物理对象初始化两次的问题 可以采用全局变量、静态数据成员,解决同一个物理对象初始化两次的问题,而如此解决相关析构问题则更使程序逻辑复杂化。

在这里插入图片描述

5.上述定义存在的问题:两栖机车要安装两个引擎Engine,可引入虚基类解决该问题。
6.虚基类
  • 虚基类用virtual声明,把多个逻辑对象映射成同一个物理对象;
  • 映射成的这个物理对象尽可能早的构造、尽可能晚的析构,构造和析构都只进行一次。若虚基类的构造函数都有参数,必须在派生类构造函数的初始化列表中列出虚基类构造参数。
  • 同一颗派生树中的同名虚基类,共享同一个存储空间;
    其构造和析构仅执行1次,
  • 有虚基类的派生类构造函数不能使用consexpr定义;
class Engine{ /*...*/ };
class LandVehicle: virtual public Engine{ /*...*/ };
class WaterVehicle: public virtual Engine{ /*...*/ };
class AmphibiousVehicle: LandVehicle, WaterVehicle { /*...*/ };
继承关系

在这里插入图片描述

示例代码
class Engine
{
    int power;

public:
    Engine(int p) : power(p) {}
};
class LandVechicle : virtual public Engine
{
    int speed;
public: //如从AmphibiousVehicle调用LandVehicle,则不会在此调用Engine(p)
    LandVechicle(int s, int p) : Engine(p), speed(s) {}
};
class WaterVechicle : virtual public Engine
{
    int speed;
public: //如从AmphibiousVehicle调用LandVehicle,则不会在此调用Engine(p)
    WaterVechicle(int s, int p) : Engine(p), speed(s) {}
};
struct AmphibiousVechicle:LandVechicle,WaterVechicle{
    AmphibiousVechicle(int s1,int s2,int p): //先构造虚基类再基类
    WaterVechicle(s2,p),LandVechicle(s1,p),Engine(p){} //整个派生树Engine(p)只1次
};//初始化顺序: Engine(p), LandVehicle(s1, p), WaterVehicle(s2, p),而且进入两个
//LandVehicle, WaterVehicle 后,不再初始化这两个基类的基类Engine
7.派生类成员同名问题

当派生类成员和基类成员同名时,优先访问作用域小
的成员,即优先访问派生类的成员。当派生类数据成
员和派生类函数成员的参数同名时,在函数成员内优
先访问函数参数

示例代码
struct A{
	int a, b, c, d;
};
struct B{
	int b, c;
	protected:
	int e;
};
class C: public A, public B{
	int a;
	public:
	int b; int f(int c);
};
int C::f(int c){
	int i=a; //访问C::a
	i=A::a;
	i=b+c+d; //访问C::b和参数c,A::d
	i=A::b+B::b; //访问基类成员
	return A::c;
}
void main(void){
	C x;
	int i=x.A::a;
	i=x.b; //访问C::b
	i=x.A::b+x.B::b;
	i=x.A::c;
}
8.析构和构造
析构和构造的顺序相反,派生类对象的构造顺序
  • 按自左至右、自下而上地构造倒派生树中所有虚基类;
  • 按定义顺序构造派生类的所有直接基类;
  • 按定义顺序构造(初始化)派生类的所有数据成员,包括对象成员、const成员和引用成员(少数编译按程序员指定顺序);
  • 执行派生类自身的构造函数体;
示例代码1
#include <iostream>

using namespace std;
struct A{
    A(){ cout << "A";}
};
struct B{
    B(){ cout << "B";}
};
struct C{
    int a;
    int &b;
    const int c;
    C(char d):c(d),b(a)
    {
        a = d;
        cout << d;
    }
};
struct D{
    D(){ cout << "D";}
};
struct E:A,virtual B,C,virtual D{
    A x,y;
    B z;
    E():z(),y(),C('C'){ cout << "E";}
};
void test()
{
    E e;
}
int main()
{
    test();
    system("pause");
    return 0;
}
输出
BDACAABE
分析

类E的派生图
在这里插入图片描述

  • 依次构造e的虚基类B和D,B和D不再是派生类,执行B D构造输出函数B D;
  • 依次构造e的基类A,C,A和C不再是派生类,C有const成员,这些成员初始化不产生输出,执行A C的构造函数输出A C;
  • 依次构造e的对象成员x,y,z 输出A A B
  • 最后输出e的构造函数E;
示例代码2
#include <iostream>
using namespace std;
struct A { A() { cout << 'A'; } };
struct B { const A a; B( ) { cout << 'B'; } }; //对象成员a将作为新根
struct C: virtual A { C() { cout << 'C'; } };
struct D { D() { cout << 'D'; } };
struct E: A { E() { cout << 'E'; } };
struct F: B, virtual C { F() { cout << 'F'; } };
struct G: B { G() { cout << 'G'; } };
struct H: virtual C, virtual D { H() { cout << 'H'; } };
struct I: E, F, virtual G, H { E e; F f; I() { cout << 'I'; } };
int main() 
{ 
	I i;
	return 0;
}
输出
ACABGDAEABFHAEACABFI
分析:
1). 做出派生树

在这里插入图片描述

2). 构造顺序
对象 i 有六棵派生树,树根分别为 I, a(A), a(A) , a(A) , e(E), 和f(F),同名虚基类 C 被合并;
  • 自左向右,自上而下,依次构造 I 的虚基类 C, G,D。C 和 D 不是派生类,其数据成员也不用调用构造函数,故直接执行其构造函数;G 为派生类,构造G先构造B,构造B线构造对象成员 a;构造类 C输出 C,构造类G 输出 ABG,构造G输出 ABG,构造D 输出 D;综上, 构造I的虚基类输出 CABGD;
  • 按照自左向右构造基类 E ,F, H 。E为派生类,先构造A,故构造E输出:AE;F为派生类,虚类C已经构造好,只需构造B,而B 需先构造 成员a,够构造F输出ABF;H虚基类已经全部构造完毕,只需输出 H;综上,构造 I 的基类输出 AEABFH;
  • 按定义输出构造 I 的对象成员e,f,构造 e 输出AE,构造f 输出CABF;综上,构造对象成员e , f 的输出为AECABF;
  • 最后 i 的构造体 I;
9.类的存储空间
  • 多继承派生类包含多个基类的存储空间;
  • 如果存在虚基类和同名基类,虚基类和同名基类空间是相互独立的。
  • 如果派生类存在同名的虚基类,同一棵派生树的所有虚基类共享存储空间,虚基类通过偏移指向共享存储空间,该存储空间出现在所有直接基类之后
  • 如果基类或派生类存在虚函数,则在派生类存储空间中,包含一个单元存放虚函数入口地址表首地址;
  • 派生类的存储空间不包括基类、虚基类和对象成员的静态数据成员。
发布了52 篇原创文章 · 获赞 15 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/jzj_c_love/article/details/102749513