菱形虚拟继承和多态

26 篇文章 0 订阅

一.菱形继承(多继承)

class AA
{
public:
    int _aa;
};
class BB : public AA
{
public:
    int _bb;
};
class CC : public AA
{
public:
    int _cc;
};
class DD :public BB, public CC
{
public:
    int _dd;
};
int main()
{
    DD d;
    d.BB::_aa = 0;
    d._bb = 1;
    d.CC::_aa = 2;
    d._cc = 3;
    d._dd = 4;
  cout << sizeof(DD) << endl;
    system("pause");
    return 0;
}

通过内存我们可以看到菱形继承的缺点是:
1.二义性,
2.数据的冗余



为了更加直观我们画图看一下:


2.如何解决菱形继承的缺点

二.虚继承:

class AA
{
public:
    int _aa;
};
 class BB : virtual public AA
{
public:
    int _bb;
};
class CC : virtual public AA
{
public:
    int _cc;
};
class DD :public BB, public CC
{
public:
    int _dd;
};
int main()
{
    DD d;
    d.BB::_aa = 0;
    d._bb = 1;
    d.CC::_aa = 2;
    d._cc = 3;
    d._dd = 4;
  cout << sizeof(DD) << endl;
    system("pause");
    return 0;
}

通过内存我们发现此时_aa的对象只有一个就是CC,这样二义性就解决了,同时我们发现派生类BB中的_aa消失,这样就解决了数据的冗余的问题。
但是我们发现有多处两组地址。
通过查看oxo13bdd94和oxo13be308的地址我们可以看到一个偏移了第一个偏移20个字节刚好到基类AA,第二个偏移12个字节刚好到基类AA.

菱形的虚继承我们可以通过图形表示如下:



结果是24,按虚继承的优点内存的大小应该是18,为什么会多出8个字节,根据前面我们知道,多出来的字节用于存放偏移地址。
通过虚继承我们发现虽然解决了菱形继承的问题,但是一定程度上是空间换时间为代价的。

三.虚函数&多态

class Shopping
{
public:
   void Price()
    {
        cout << "不优惠" << endl;
    }
};
class OnLine :public Shopping
{
public:
   void Price()
    {
        cout << "优惠" << endl;
    }
};
void fun(Shopping &s)
{
    s.Price();
}
int main()
{
    Shopping s;
    OnLine  o;
    fun(s);
    fun(o);
    system("pause");
    return 0;
}

结果显示“不优惠”调了两次,按照兼容规则可知道父类的指针和引用可以指向子类的对象。

1.虚函数:

类的成员函数前面加关键字virtual,那么这个成员函数就是虚函数。
如:
#include<iostream>
using namespace std;
class Shopping
{
public:
    virtual void Price()
    {
        cout << "不优惠" << endl;
    }
};
class OnLine :public Shopping
{
    virtual void Price()
    {
        cout << "优惠" << endl;
    }
};
void fun(Shopping &s)
{
    s.Price();
}
int main()
{
    Shopping s;
    OnLine  o;
    fun(s);
    fun(o);
    system("pause");
    return 0;
}

结果显示“优惠”和“不优惠”虚函数的重写:当子类的虚函数与父类相同时,子类的这个函数重写了父类的虚函数。可以理解为把这个父类的虚函数覆盖了。

2.多态:

当基类的指针或引用调用的重写的虚函数时,当指向父类调用的就是父类的虚函数,当指向子类调用的就是子类的虚函数。

3.协变:

协变也是一种重写,只不过父类和字类中的返回值不同。
class Person
{
public:
    Person()
        :_id(1)
    {}
    virtual void Display()
    {
        cout << "_id(1)"<< endl;
    }
protected:
    int  _id; // 身份证号
};
class Student : public Person
{
public:
    Student()
        :_id(2)
    {}
    virtual void Display()
    {
        cout << "_id(2)" << endl;
    }
protected:
    int _id; // 身份证号
};
int main()
{
    Student s;
    s.Display();
    cout << sizeof(s) << endl;
    system("pause");
    return 0;
}

通过结果我们可以看到,协变其实就是重写,基类函数的虚函数被子类的虚函数覆盖了。id(1)只是被隐藏但仍然存在。
查看监视窗口我们会发现基类下面除了内存外还存在一个虚函数表指针_vfptr指向虚函数表,用于存放函数地址

查看内存窗口我们可以看到原来多出的4个字节用于存放虚表指针的地址。


通过上面我们知道多态的原理实际就是虚函数的重写,(协变例外)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值