面向对象 - 多态

C++中多态是通过虚函数实现,所以我们先了解虚函数。

虚函数

在函数前面加上virtual关键字后,函数变成虚函数。

如果通过对象调用,虚函数和普通函数没有区别。写代码验证,反汇编后代码都是E8 call,调用方式是直接调用,没有区别。

class MyClazz
{
public:
    void fun1() {
        printf("fun1...\n");
    }
    virtual void fun2() {
        printf("fun2...\n");
    }
};

int main()
{
    MyClazz myClazz;
    myClazz.fun1();
    myClazz.fun2();
}

反汇编代码:
18:     MyClazz myClazz;
006319BF 8D 4D F4             lea         ecx,[ebp-0Ch]  
006319C2 E8 FF F8 FF FF       call        006312C6  
    19:     myClazz.fun1();
006319C7 8D 4D F4             lea         ecx,[ebp-0Ch]  
006319CA E8 D8 F9 FF FF       call        006313A7  
    20:     myClazz.fun2();
006319CF 8D 4D F4             lea         ecx,[ebp-0Ch]  
006319D2 E8 82 F7 FF FF       call        00631159

如果通过指针来调用,调用虚函数时使用间接调用:

class MyClazz
{
public:
    void fun1() {
        printf("fun1...\n");
    }
    virtual void fun2() {
        printf("fun2...\n");
    }
};

int main()
{
    MyClazz myClazz;
    MyClazz* ptr = &myClazz;
    ptr->fun1();
    ptr->fun2();
}

18:     MyClazz myClazz;
004919BF 8D 4D F4             lea         ecx,[ebp-0Ch]  
004919C2 E8 FF F8 FF FF       call        004912C6  
    19:     MyClazz* ptr = &myClazz;
004919C7 8D 45 F4             lea         eax,[ebp-0Ch]  
004919CA 89 45 E8             mov         dword ptr [ebp-18h],eax  
    20:     ptr->fun1();
004919CD 8B 4D E8             mov         ecx,dword ptr [ebp-18h]  
004919D0 E8 D2 F9 FF FF       call        004913A7  
    21:     ptr->fun2();
004919D5 8B 45 E8             mov         eax,dword ptr [ebp-18h]  
004919D8 8B 10                mov         edx,dword ptr [eax]  
004919DA 8B F4                mov         esi,esp  
004919DC 8B 4D E8             mov         ecx,dword ptr [ebp-18h]  
004919DF 8B 02                mov         eax,dword ptr [edx]  
004919E1 FF D0                call        eax  

类的大小

如果类中有虚函数的时候,对象大小会增加4个字节。多出来的4字节是一个地址,指向所有虚函数的地址,这个地址叫虚函数表,虚函数表中存的值是函数地址。

代码验证:

class MyClazz
{
public:
    int x;
    int y;
    MyClazz() {
        x = 1;
        y = 2;
    }
    virtual void VirtualFun() {
        printf("VirtualFun...\n");
    }
};

int main()
{
    MyClazz myClazz;
    
    MyClazz* ptr = &myClazz;
    ptr->VirtualFun();
}

查看ptr数据,最前面4个字节0xd57b34是多出来的虚函数表。
在这里插入图片描述

查看0xd57b34虚函数表数据,虚函数表中存储的是函数地址。
在这里插入图片描述

程序执行的时候,编译器按照序号寻找虚函数,调用虚函数:

class MyClazz
{
public:
    int x;
    int y;
    MyClazz() {
        x = 1;
        y = 2;
    }
    virtual void VirtualFun1() {
        printf("VirtualFun1...\n");
    }
    virtual void VirtualFun2() {
        printf("VirtualFun2...\n");
    }
};

int main()
{
    MyClazz myClazz;
    MyClazz* ptr = &myClazz;
    printf("虚函数表地址为:%x\n", *(int*)&myClazz);

    typedef void(*pFunction)(void);
    //调用第1个虚函数
    pFunction pFn;
    pFn = (pFunction) * ((int*)(*(int*)&myClazz) + 0);
    pFn();

    //调用第2个虚函数
    pFunction pFn2;
    pFn2 = (pFunction) * ((int*)(*(int*)&myClazz) + 1);
    pFn2();
}

终端打印:

虚函数表地址为:eb7b34
VirtualFun1...
VirtualFun2...

总结虚函数特性:

1、虚函数表的数量是编译时候确定的。
2、单继承时,子类会生成1个虚函数表,子类会继承基类虚函数。如果函数原型相同,子类虚函数会覆盖基类虚函数。
3、多继承时会产生多个虚函数表。同样会覆盖基类虚函数。
4、多重继承,子类会继承所有基类虚函数,如果子类虚函数和基类虚函数原型相同,会覆盖基类虚函数。
5、如果有继承多个基类,子类会有多个虚函数表。

动态绑定

1、调用的代码和真正的函数关联到一起的过程叫绑定。

2、如果编译的时候能确定绑定,叫做编译期绑定,或者叫前期绑定。类的普通的成员和函数编译之后,地址已经确定,是编译器绑定。

3、如果编译的时候无法确定,叫做动态绑定,或者叫运行期绑定。虚函数在编译的时候无法确定。能够体现不同的行为,称为多态,动态绑定也叫多态。多态通过虚函数表来实现。

class Base
{
public:
    int x;
public:
    Base()
    {
        x = 1;
    }
    void Function_1()
    {
        printf("Base:Function_1...\n");
    }
    virtual void Function_2()
    {
        printf("Base:Function_2...virtual\n");
    }
};
class Sub :public Base
{
public:
    int x;
public:
    Sub()
    {
        x = 2;
    }
    void Function_1()
    {
        printf("Sub:Function_1...\n");
    }
    virtual void Function_2()
    {
        printf("Sub:Function_2...virtual\n");
    }
};

void TestBind(Base* pb)
{
    int n = pb->x;
    printf("%x\n", n);
    pb->Function_1();
    pb->Function_2();	

    /*
    pb->Function_1();
    8B 4D 08             mov         ecx,dword ptr [ebp+8]
    E8 AF E8 FF FF       call        00E71406

    pb->Function_2();		
    8B 45 08             mov         eax,dword ptr [ebp+8]
    8B 10                mov         edx,dword ptr [eax]
    8B F4                mov         esi,esp
    8B 4D 08             mov         ecx,dword ptr [ebp+8]
    8B 02                mov         eax,dword ptr [edx]
    FF D0                call        eax
    
    */

}
int main(int argc, char* argv[])
{
    /*
    打印结果:
    1
    Base:Function_1...
    Base:Function_2...virtual
    */
    Base base;
    TestBind(&base);

    /*
    打印结果:
    1
    Base:Function_1...
    Sub:Function_2...virtual
    */
    Sub sub;
    TestBind(&sub);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值