类:多继承、虚继承(虚基类、公共祖先)

一、多继承

1、概念

        当一个派生类具有多个基类时,这种派生方法称为多基派生或多继承。

                                     

class z:private x, public y {   
    //…
};

2、二义性

class X {       //思考题:
public:
    int f();
};

class Y {
public:
    int f();
    int g();
};

class Z∶public X, public Y {
public:
    int g();
    int h();
};

main()
{ 
    Z obj;
    obj.f();             // 不知调用的是 X、Y 里哪一个f()
} 

使用成员名限定可以消除二义性,例如:

obj.X∷f();    //调用类X的f()  
obj.Y∷f();    //调用类Y的f()

3、构造/析构顺序

派生类名(参数总表) : 基类名1(参数表1), 基类名2(参数表2), …, 基类名n(参数表)
{ 
      派生类新增成员的初始化语句 
}

        多继承构造函数的执行顺序与单继承构造函数的执行顺序相同:

       (1)先执行基类的构造函数(按继承从左到右顺序);

       (2)再执行对象成员的构造函数;

       (3)最后执行派生类构造函数。    

       析构函数的执行顺序则刚好与构造函数的执行顺序相反。

class Hard {
protected:
	int a;
public:
	Hard(int a) { this->a = a; }      //基类Hard构造函数
};

class Soft {
protected:
	int b;
public:
	Soft(int b) { this->b = b; }      //基类Soft的构造函数 
};


class System : public Hard, public Soft {         //派生类,多继承
public:
	int c;
	string str;
	System(int a, int b, int c, string s) : Hard(a), Soft(b), str(s)
	{
		this->c = c;
	}
};

(1)Hard中a初始化;(2)Soft中b初始化;(3)然后str初始化,在初始化表中找到str项,于是str初始化成功;(4)this->c相当于是给c第一次赋值,是后初始化。

 

二、虚基类 (虚继承)

1、共同祖先二义性

        如果一个派生类是从多个基类派生出来的,而这些基类又有一个共同的基类,则存在来自同一个基类的多份数据的拷贝,则在这个派生类中访问这个共同的基类中的成员时,可能会产生二义性

class B {
  protected:  int a;
public:
   B(){ a=5; cout<<"B a="<<a<<endl;}  
};

class B1:public B {
   public:
    B1(){ a=a+10; cout<<"B1 a="<<a<<endl;} 
};

class B2:public B {
public:
    B2(){ a=a+20; cout<<"B2 a="<<a<<endl;} 
};

class D:public B1,public B2 {
public:
  D(){ cout << "D a=" << a << endl; }        不知道调的是哪一个a,来自B1还是B2
};

int main() {
    D obj;  
    return  0;  
}

应该使用作用域修饰符

class D : public B1, public B2 {
public:
    D() { 
        cout << “B1 a=" << B1::a << endl;      来自B1 
        cout << “B2 a=" << B2::a << endl;      来自B2
    }  
};

运行结果为

B a=5
B1 a=15
B a=5
B2 a=25
B1 a=15
B2 a=25

2、虚基类

        为了解决这种二义性,使从不同的路径继承的基类的成员在内存中只拥有一个拷贝, C++引入了虚基类的概念。

        如果将公共基类说明为虚基类。那么,对同一个虚基类的构造函数只调用一次,这样从不同的路径继承的虚基类的成员在内存中就只拥有一个拷贝。从而解决了以上的二义性问题。

3、构造顺序

        对同一个虚基类的构造函数只调用一次,且是在第一次出现时调用。

class B {
protected:
   int a;
public:
   B(){ a=5; cout<<"B a="<<a<<endl;}  
};

class B1 : public  virtual B {                        虚继承
public:
    B1(){ a=a+10; cout<<"B1 a="<<a<<endl;} 
};

class B2 : virtual public  B {                        虚继承
public:
    B2(){ a=a+20; cout<<"B2 a="<<a<<endl;} 
};

class D : public B1, public B2 {                      继承
public:
    D(){ cout<<"D a="<< a<<endl;} 
};

main()
{ 
    D  obj; 
    return  0;  
}    

运行结果为: 

    base    a=5 
    base1   a=15 
    base2   a=35 
    derived   a=35

       若同一层次中同时包含虚基类和非虚基类,应先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类构造函数;

       对于多个虚基类,构造函数的执行顺序仍然是先左后右,自上而下;

       对于非虚基类,构造函数的执行顺序仍是先左后右,自上而下。

 

4、底层实现原理 —— 虚基类指针、虚基类表

       虚继承用于解决多继承条件下的公共祖先问题(浪费存储空间、存在二义性)。

       底层实现原理与编译器相关,一般通过虚基类指针虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。

       实际上,vbptr 指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值