IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 面向对象 - 多态 -> 正文阅读

[C++知识库]面向对象 - 多态

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;
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-04 11:50:13  更:2022-04-04 11:50:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 1:09:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码