多态、虚函数表、对象模型

来源:互联网 发布:天下3男捏脸数据 编辑:程序博客网 时间:2024/05/19 09:40

多态:

不同的对象收到相同的消息时,产生不同的动作。就是说有两个函数,同名,但是参数列表不同。能根据不同的参数传递实现不同的功能。也就是所谓“一个接口,多种方法”
静态多态:函数重载、模板
动态多态:虚函数

虚函数、虚表:

C++中虚函数的主要作用就是实现多态。简单说父类的指针/引用调用重写的虚函数,当父类指针/引用指向父类对象时调用的是父类的虚函数,指向子类对象时调用的是子类的虚函数;c++中用虚表来实现多态。虚函数表是通过一块连续内存来存储虚函数的地址。

虚函数要点

  • 使用虚函数来实现多态时,派生类必须从它的基类公有派生
  • 首先必须在基类中定义虚函数
  • 在派生类中对基类中声明的虚函数进行重定义时,关键字virtual可以加也可以不加。为了不引起混乱,最好加上
  • 只有通过基类指针或引用访问虚函数时才能获得运行时的多态性
  • 一个虚函数无论被继承多少次,它仍然保持其虚函数的特性
  • 虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数
  • 内联函数不能是虚函数,因为内联函数是不能在运行中动态确定其位置的
  • 构造函数不能是虚函数,但是析构函数可以是虚函数,而且通常说明为虚函数

函数重写、重载、重定义:

  作用域 函数体 重载 在同一作用域 函数名相同,参数列表不同,与返回值类型无关 重写(覆盖) 在不同作用域(基类和派生类) 返回值、参数、函数名都相同(协变例外),基类函数必须有virtual修饰 重定义(隐藏) 不同作用域(基类和派生类) 在基类和派生类内不构成重写的同名函数就是重定义

协变:返回值类型可以不同,基类返回基类类型的指针或引用、派生类返回派生类类型的指针或引用
重载举例

int Add(int a, int b);double Add(double a, double b);

重写举例

class Base{public:    virtual void show()//基类必须是虚函数    {        cout<<"Base::show()"<<endl;    }};class Derived:public Base{public:    void show()    {        cout<<"Derived::show()"<<endl;    }};int main(){    Derived d;    d.show();//调用的是自己的show();而不是继承自基类的show()    return 0;}

运行结果
这里写图片描述

重定义(同名隐藏)举例

class Base{public:    void show()    {        cout<<"Base::show()"<<endl;    }};class Derived:public Base{public:    int show()    {        cout<<"Derived::show()"<<endl;        return 1;    }};int main(){    Derived d;    d.show();//此处调用的是派生类内重定义的函数show()而不是继承自基类的show()    return 0;}

运行结果如下:
这里写图片描述

多态对象模型剖析:

1.单继承构成虚表的方式

  • 虚函数的顺序是类中声明顺序
  • 没有重写基类的虚函数之前,派生类的虚表和基类的虚表相同;重写之后,用派生类重写后的虚函数替换原来基类的虚表,定义新的虚函数按照自己声明次序排列在基类虚表的后面在基类虚表的后面
#include <iostream>using namespace std;class Base{public:    virtual void Funtest1()    {        cout<<"Base::Funtest1()"<<endl;    }    virtual void Funtest2()    {        cout<<"Base::Funtest2()"<<endl;    }    virtual void Funtest3()    {        cout<<"Base::Funtest3()"<<endl;    }    int _b;};class Derived:public Base{public:    virtual void Funtest2()    {        cout<<"Derived::Funtest2()"<<endl;    }    virtual void Funtest4()    {        cout<<"Derived::Funtest4()"<<endl;    }    virtual void Funtest5()    {        cout<<"Derived::Funtest5()"<<endl;    }    int _d;};typedef void (*pvFt)();void print(Base& b)//打印虚表内容{    pvFt* p=(pvFt*)*(int*)&b;    while (*p)    {        (*p)();        p++;    }} void print(Derived& d)//此处打印是在清楚对象模型的基础上实现的{    pvFt* p=(pvFt*)*(int*)&d;    while (*p)    {        (*p)();        p++;    }} int main(){    Base b;    Derived d;    print(b);    print(d);    return 0;}

运行结果:
这里写图片描述
那么虚表到底在哪,我们来研究一下基类和派生类的对象模型:
主函数添加如下代码,计算类对象模型大小

cout<<sizeof(b)<<endl;cout<<sizeof(d)<<endl;//结果如下

这里写图片描述
在内存窗口可以看到对象模型,我们来分析一下:
这里写图片描述

2.多继承

#include <iostream>using namespace std;class C1{public:    virtual void Funtest1()    {        cout<<"C1::Funtest1()"<<endl;    }    virtual void Funtest2()    {        cout<<"C1::Funtest2()"<<endl;    }    int _c1;};class C2{public:    virtual void Funtest3()    {        cout<<"C2::Funtest3()"<<endl;    }    int _c2;};class D:public C1,public C2{public:    virtual void Funtest4()    {        cout<<"D::Funtest4()"<<endl;    }    int _d;};typedef void (*pvFt)();void print(C1& c1)//打印C1的虚表{    pvFt* p=(pvFt*)*(int*)(&c1);    while (*p)    {        (*p)();        p++;    }} void print(C2& c2)//打印C2的虚表{    pvFt* p=(pvFt*)*(int*)(&c2);    while (*p)    {        (*p)();        p++;    }} void print(D& d)//此处打印是在清楚对象模型的基础上实现的{    pvFt* p=(pvFt*)*(int*)&d;//打印继承自C1的虚表    while (*p)    {        (*p)();        p++;    }    pvFt* q=(pvFt*)*(((int*)&d)+2);//打印继承自C2的虚表    while (*q)    {        (*q)();        q++;    }} int main(){    C1 c1;    c1._c1=1;    C2 c2;    c2._c2=2;    D d;    d._d=3;    cout<<sizeof(d)<<endl;    return 0;}

如果派生类有自己新的虚函数,将跟在他的第一张虚表的后面,如上面程序中的Funtest4();对象模型大小也复合计算结果,是20字节

这里写图片描述

3.虚拟单继承

#include <iostream>using namespace std;class Base{public:    virtual void Funtest1()    {        cout<<"Base::Funtest1()"<<endl;    }    virtual void Funtest2()    {        cout<<"Base::Funtest2()"<<endl;    }    int _b;};class D:virtual public Base{public:    virtual void Funtest3()    {        cout<<"D::Funtest4()"<<endl;    }    int _d;};typedef void (*pvFt)();void print(Base& b){    pvFt* p=(pvFt*)*(int*)&b;    while (*p)    {        (*p)();        p++;    }} void print(D& d)//此处打印是在清楚对象模型的基础上实现的{    pvFt* p=(pvFt*)*(int*)&d;//打印D自己的虚表    while (*p)    {        (*p)();        p++;    }    pvFt* q=(pvFt*)*(((int*)&d)+3);//打印继承自Base的虚表    while (*q)    {        (*q)();        q++;    }} int main(){    D d;    d._d=3;    Base b;    b._b=1;    cout<<sizeof(d)<<endl;    print(d);    return 0;}

下面分析d的对象模型,对象模型大小也复合计算结果,是20字节
点击查看关于偏移量表

这里写图片描述

4.菱形非虚拟继承

点击查看关于菱形继承相关概念
下面具体分析菱形非虚拟继承的派生类对象模型

#include <iostream>using namespace std;class Base{public:    virtual void Funtest1()    {        cout<<"Base::Funtest1()"<<endl;    }    virtual void Funtest2()    {        cout<<"Base::Funtest2()"<<endl;    }    int _b;};class C1:public Base{public:    virtual void Funtest2()    {        cout<<"C1::Funtest2()"<<endl;    }    virtual void Funtest3()    {        cout<<"C1::Funtest3()"<<endl;    }    int _c1;};class C2:public Base{public:    virtual void Funtest4()    {        cout<<"C2::Funtest4()"<<endl;    }    int _c2;};class D:public C1,public C2{public:    virtual void Funtest5()    {        cout<<"D::Funtest5()"<<endl;    }    int _d;};typedef void (*pvFt)();void print(Base& b){    pvFt* p=(pvFt*)*(int*)&b;    while (*p)    {        (*p)();        p++;    }} void print(C1& c1){    pvFt* p=(pvFt*)*(int*)(&c1);    while (*p)    {        (*p)();        p++;    }} void print(C2& c2){    pvFt* p=(pvFt*)*(int*)(&c2);    while (*p)    {        (*p)();        p++;    }} void print(D& d)//此处打印是在清楚对象模型的基础上实现的{    pvFt* p=(pvFt*)*(int*)&d;//打印继承自C1的虚表    while (*p)    {        (*p)();        p++;    }    pvFt* q=(pvFt*)*(((int*)&d)+3);//打印继承自C2的虚表    while (*q)    {        (*q)();        q++;    }} int main(){    C1 c1;    c1._c1=11;//B    C2 c2;    c2._c2=12;//C    Base b;    b._b=1;    D d;    d._d=2;    cout<<sizeof(d)<<endl;    print(c1);    cout<<endl;    print(c2);    cout<<endl;    print(d);    return 0;}

菱形非虚拟继承其实是由两次单继承然后加一次多继承构成,符合单继承和多继承的继承特点,对象模型大小也复合计算结果,是28字节

这里写图片描述

5.菱形虚拟继承

#include <iostream>using namespace std;class Base{public:    virtual void Funtest1()    {        cout<<"Base::Funtest1()"<<endl;    }    virtual void Funtest2()    {        cout<<"Base::Funtest2()"<<endl;    }    int _b;};class C1:virtual public Base{public:    virtual void Funtest4()    {        cout<<"C1::Funtest4()"<<endl;    }    virtual void Funtest3()    {        cout<<"C1::Funtest3()"<<endl;    }    int _c1;};class C2:virtual public Base{public:    virtual void Funtest5()    {        cout<<"C2::Funtest5()"<<endl;    }    int _c2;};class D:public C1,public C2{public:    virtual void Funtest5()    {        cout<<"D::Funtest5()"<<endl;    }    int _d;};typedef void (*pvFt)();void print(Base& b){    pvFt* p=(pvFt*)*(int*)&b;    while (*p)    {        (*p)();        p++;    }} void print(C1& c1){    pvFt* p=(pvFt*)*(int*)(&c1);    while (*p)    {        (*p)();        p++;    }} void print(C2& c2){    pvFt* p=(pvFt*)*(int*)(&c2);    while (*p)    {        (*p)();        p++;    }} void print(D& d)//此处打印是在清楚对象模型的基础上实现的{    pvFt* p=(pvFt*)*(int*)&d;//打印继承自C1的虚表    while (*p)    {        (*p)();        p++;    }    pvFt* q=(pvFt*)*(((int*)&d)+3);//打印继承自C2的虚表    while (*q)    {        (*q)();        q++;    }    q=(pvFt*)*(((int*)&d)+7);//打印Base的虚表    while (*q)    {        (*q)();        q++;    }} int main(){    C1 c1;    c1._c1=11;//B    C2 c2;    c2._c2=12;//C    Base b;    b._b=1;    D d;    d._d=2;    cout<<sizeof(d)<<endl;    print(c1);    cout<<endl;    print(c2);    cout<<endl;    print(d);    return 0;}

关于菱形虚拟继承在我的另一篇博客里有详细解述(点击查看)
菱形虚拟继承满足多态和继承的对象模型构建特性,事实也验证了对象模型的正确性。我们先看一下继承关系:

        这里写图片描述

这里写图片描述

原创粉丝点击