多态+多态对象模型

多态的实现条件
多态(与对象有关 到对象的虚表中找到)=动态联编+虚函数重写
动态联编(运行时决议):指针/引用+虚函数
静态联编(编译时决议)与类型有关,像函数重载和通过对象名调用虚函数

1.定义一个父类的指针,如果这个指针指向父类,调用父类的虚函数,指针指向子类,调用子类的虚函数,这样可以使一个函数有不同的实现,即多态的体现。
2.根据模板的不同实例,实现不同类型的使用。

用一个例代码更好地理解

class A
{
public:
     virtual void f1()
    {
        cout<<"A::f1()"<<endl;
    }
     void f2()
     {
         cout<<"A::f2()"<<endl;
     }
      void f3()
     {
         cout<<"A::f3()"<<_a<<endl;
     }
private:
      int _a;

};
int main()
{
    A* p = NULL;
    //p->f1();(运行不通过,此处动态联编,寻找虚表)
    p->f2();//静态联编,不是虚函数,f2存在于代码段中,直接可以调用
    //p->f3();//(静态联编,this指针解引用后成空)
    return 0;
}

虚析构函数
这里我们先来清楚一个问题,为什么最好把父类的析构函数定义成虚函数呢?

#include<iostream>
using namespace std;
class A
{
public:
     virtual~A()
    {
        cout<<"~A()"<<endl;
    }
};
class B:public A
{
public:
    ~B()
    {
        cout<<"~B()"<<endl;
    }
};
int main()
{
    A* p=new B;
    delete p;
    return 0;
}

现象:给父类加上virtual后,会先析构子类的析构函数,再析构父类的析构函数。

分析:把父类的析构函数声明成虚函数,这将使所有派生类的析构函数自动成为虚函数。这样,如果想要显式地通过delete删除一个对象,delete的操作对象用了指向派生类对象的基类指针,则会调用基类的析构函数。

探索虚函数表
通过一块连续内存来存储虚函数的地址。
这里写图片描述
正如上图中监视显示,虚函数表_vfptr中存放着虚函数fun1和fun2的地址。
单继承有虚函数但是没有重写虚函数
这里写图片描述
通过监视窗口可以看到,父类中有一个虚函数,父类的实例化对象中有一个虚表指针和一个成员变量,然后子类中有一个函数,但不是虚函数,但为什么子类的成员变量里会有一个虚表指针呢?因为它是从父类里继承而来的。需要说明的是,这个函数指针,指向的内容是父类的虚函数。
单继承有虚函数有重写虚函数

class A
{
public:
    virtual void show()
    {
        cout<<"A show()"<<endl;
    }
public:
    int _a;
};
class B:public A
{
public:
    void show()
    {
        cout<<"B show()"<<endl;
    }
    void show1()
    {
        cout<<"B show()"<<endl;
    }
public:
    int _b;
};

int main()
{
    A a;
    B b;
    return 0;
}

这里写图片描述
通过和上面的继承虚函数相比,子类的虚函数表中的函数地址已经变成了子类的函数了。
探索单继承对象模型

class Base
{
public:
    virtual void fun1()
    {
        cout<<"Base::fun1"<<endl;
    }
    virtual void fun2()
    {
        cout<<"Base::fun2"<<endl;
    }
private:
    int a;
};
class Derive:public Base
{
public:
    virtual void fun1()
    {
        cout<<"Derive::fun1"<<endl;
    }
    virtual void fun3()
    {
        cout<<"Derive::fun3"<<endl;
    }
    virtual void fun4()
    {
        cout<<"Derive::fun4"<<endl;
    }
private:
    int b;
};
void Test()
{
    Base b1;
    Derive d1;
}

对于Derive类来说,我们预想的是,它的虚表里会有子类重写父类的fun1,父类的fun2,子类的fun3和fun4.
这里写图片描述

但是,通过监视窗口可知只有fun1()和fun2()。这里是因为编译器的问题所以没有显示出fun3和fun4。
所以接下来我们需要实现一个可以打印虚函数表的函数。

typedef void(*FUNC) ();
void PrintVTable(int* VTable)
{
    cout<<"虚表地址"<<VTable<<endl;
    for(int i=0;VTable[i]!=0;++i)
    {
        printf("低%d个虚函数地址:0X%x->",i,VTable[i]);

        FUNC f=(FUNC) VTable[i];//转为指针类型
        f();
    }
    cout<<endl;
}
void Test()
{
    Base b1;
    Derive d1;
    int* VTable1=(int*)(*(int*)&b1);
    int* VTable2=(int*)(*(int*)&d1);
    PrintVTable(VTable1);
  PrintVTable(VTable2);//显式打印
}

这里写图片描述
函数的实现:
这里写图片描述
这里还有一点不好理解,就是传参问题

这里写图片描述
探索多继承的内存布局
学习了单继承对象模型,接下来我们看看多继承,同样,通过代码调试观察监视从而了解多继承的内存布局。

class Base1
{
public:
    virtual void fun1()
    {
        cout<<"Base::fun1"<<endl;
    }
    virtual void fun2()
    {
        cout<<"Base::fun2"<<endl;
    }
private:
    int b1;
};
class Base2
{
public:
    virtual void fun1()
    {
        cout<<"Base::fun1"<<endl;
    }
    virtual void fun2()
    {
        cout<<"Base::fun2"<<endl;
    }
private:
    int b2;
};
class Derive:public Base1,public Base2
{
public:
    virtual void fun1()
    {
        cout<<"Derive::fun1"<<endl;
    }
    virtual void fun3()
    {
        cout<<"Derive::fun3"<<endl;
    }

private:
    int d1;
};
typedef void(*FUNC) ();
void PrintVTable(int* VTable)
{
    cout<<"虚表地址"<<VTable<<endl;
    for(int i=0;VTable[i]!=0;++i)
    {
        printf("低%d个虚函数地址:0X%x->",i,VTable[i]);

        FUNC f=(FUNC) VTable[i];//转为指针类型
        f();
    }
    cout<<endl;
}
void Test()
{

    Derive d1;
    int* VTable=(int*)(*(int*)&d1);
    PrintVTable(VTable);
    VTable = (int*)(*((int*)&d1+sizeof(Base1)/4));
    PrintVTable(VTable);//显式打印
}
int main()
{
    Test();
    return 0;
}

多继承中有两个虚函数表,分别是Base1和Base2的虚函数表,我们猜想子类里的fun1()会覆盖父类Base1和Base2中的fun1(),但是子类里的fun3()会放在Base1里还是Base2里还是自己开辟一个虚函数表呢?
接下来,我们通过监视窗口看看:
这里写图片描述
结果证实了子类里的fun1()会覆盖父类Base1和Base2中的fun1(),但是,同单继承中出现的问题一样,编译器依旧无法在监视窗口中显示出fun3()的情况。
所以,靠自己吧!
这里写图片描述
通过上图的打印结果可知,fun3()存在于Base1的虚函数表中。当涉及多继承时,子类的虚函数会存放于先继承的那个类的虚函数表里。
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值