美文网首页程序员我爱编程
以c的视角来诠释c++的多态

以c的视角来诠释c++的多态

作者: Linux后端研发工程实践 | 来源:发表于2017-03-22 13:50 被阅读86次
    目录
    1.概述
    2.动手前的预备知识点
    • 2.1 你知道不同类型的指针意味着什么?
    • 2.2 c文件编译成一个目标文件,目标文件里包含什么?
    3.c++静态多态
    • 3.1 实际编码操作
    • 3.2 实现原理
    • 3.3 以c的视角理解
    4.c++动态多态
    • 4.1 实际编码操作
    • 4.2 实现原理
    • 4.3 以c的视角理解
    5.编译运行环境
    • 5.1 操作系统
    • 5.2 编译器

    1.概述

    c++是一门混合型编程语言,即支持面向对象又支持面向过程,其中又以面向对象为主。c++的三大特性:“继承”,“封装”,“多态”中,又以“多态”最难以理解,本文将通过c的视角来诠释c++的多态。

    2.动手前的预备知识点

    2.1 你知道不同类型的指针意味着什么?

    不同的指针类型,意味这对同一块内存起始地址的不同解析方式,下面我们举一个栗子。

    • 代码 test4.c
    #include <stdint.h>
    #include <string.h>
    #include <malloc.h>
    #include <stdio.h>
    
    int32_t g_int = 10;
    
    int main()
    {
        //申请5个字节大小的堆内存
        void * p = malloc(5);
        //每个字节的值设置为90
        memset(p, 90, 5);       
        
        //声明一个char指针pc指向分配的内存的起始地址
        char * pc = (char *)p;
        //声明一个int32_t指针pint指向分配的内存的起始地址
        int32_t * pint = (int32_t *)p;
        
        //从起始地址开始取1个字节的内存去解析成char变量
        if (*pc == 90) 
        {
            printf("char yes\n");
        }
        
        //从起始地址开始取4个字节的内存去解析成int32_t变量
        if (*pint == 90 + (90 << 8) + (90 << 16) + (90 << 24))
        {
            printf("int yes\n");
        }
        
        return 0;
    }
    
    • 运行结果
    不同指针类型.png
    • 详细图解
    指针类型的不同.jpg

    2.2 c文件编译成一个目标文件,目标文件里包含什么?

    • 至少应该包含函数符号,全局变量(如果文件中有定义全局变量)。
    • 通过gcc和nm命令我们可以看到用上面的测试代码生成的test4.o这个目标文件中包含有一个全局变量g_int,一个有定义的函数符号main,三个未定义的函数符号malloc,memset,puts。
    • 命令执行结果
    目标文件.png

    3. c++静态多态

    3.1实际编码操作

    • 代码test5.cpp
    #include <iostream>
    using namespace std;
    
    void fun1()
    {
     cout << "fun1 call" << endl;
    }
    
    void fun1(int a)
    {
     cout << "fun1 a call" << endl;
    }
    
    int main()
    {
     fun1();
     fun1(10);
     return 0;
    }
    
    
    • 运行结果
    函数重签名.png

    3.2 实现的原理

    • 通过重载(overload)的特性来实现,在编译阶段就决定要调用那个函数,故称为静态多态。
    • c++编译器在编译代码时,会对函数符号重签名(c编译器不会),当c++编译器遇到重载调用时则直接调用重签名后的函数,使用nm命令查看可执行文件的符号我们看到两个被重签名的符号。

    3.3以c的视角理解

    #include <stdio.h>
    
    void _Z4fun1v()
    {
     printf("fun1 call\n");
    }
    
    void _Z4fun1i(int a)
    {
     printf("fun1 a call\n");
    }
    
    int main()
    {
     _Z4fun1v(); //对应之前的void fun1();
     _Z4fun1i(10); //对应之前的void fun1(int a);
     return 0;
    }
    

    4.c++动态多态

    4.1实际编码操作

    • 代码test6.cpp
    #include <iostream>
    using namespace std;
    
    class Base
    {
    public:
     virtual void sleep()
     {
     cout << "Base sleep" << endl;
     }
     virtual void eat()
     {
     cout << "Base eat" << endl;
     }
     virtual void run()
     {
     cout << "Base run" << endl;
     }
    };
    
    class Animal : public Base
    {
    public:
     size_t age;
     
     void sleep()
     {
     cout << "Animal sleep" << endl;
     }
     void eat()
     {
     cout << "Animal eat" << endl;
     }
     void run()
     {
     cout << "Animal run" << endl;
     }
    };
    
    /*
     定义一个函数指针类型,类型为 void () (Animal * );
     用于指向虚函数sleep,eat,run;
     这里之所以多出一个Animal * 参数是因为c++类的非静态成员函数,
     编译器会默认在参数列表开头加入指向类指针的参数
     */
    typedef void (* pFun)(Animal * animal);
    
    int main()
    {
     Animal dargon;
     Animal dog;
     
     Base * pBase = &dargon;
     Base & pRe = dog;
     
     //通过基类的指针指向派生类对象来实现动态多态
     pBase->sleep();
     //通过基类的引用指向派生类对象来实现动态多态
     pRe.sleep();
     
     /*
     取出Animal的虚表指针.
     (size_t *)&dargon -> dargon起始地址转换为size_t *
     *(size_t *)&dargon -> dargon起始地址开始取sizeof(size_t)个字节解析成size_t(虚表指针的值)
     (size_t *)*(size_t *)&dargon -> 把这个值转换成size_t *类型
     
     ps: size_t在32位机是4个字节,在64位机是8个字节,指针变量的大小和size_t的大小是一致的。
      */
     size_t * vptable_dargon = (size_t *)*(size_t *)&dargon;
     size_t * vptable_dog = (size_t *)*(size_t *)&dog;
     
     cout << "size_t size = " << sizeof(size_t) << endl;
     
     //一个类公用一个虚表指针
     if (vptable_dog == vptable_dargon)
     {
     cout << "vptable value is equal" << endl;
     }
     
     //遍历虚表指针
     while (*vptable_dargon)
     {
     //取出每个虚表函数
     pFun fun = (pFun)(*vptable_dargon);
     //调用每个虚表函数
     fun(&dargon);
     vptable_dargon++;
     }
     
     return 0;
    }
    
    
    • 运行结果
    c++动态多态.png

    4.2 实现原理

    • 通过c++的重写(override)的特性来实现,只有在运行时才知道真正调用是什么那个函数,故称为动态多态。
    • c++为有虚函数的每个类添加了一个虚函数表(类的静态变量),并在每个类对象的起始地址处嵌入一个虚表指针指向它,再通过这个虚表指针来实现运行时的多态。
    c++对象内存布局.jpg

    4.3 以c的视角理解

    • 代码test7.cpp
    #include <stdio.h>
    #include <malloc.h>
    #include <string.h>
    
    //全局静态的虚表指针,模拟类的静态虚表指针
    static size_t * pBaseVptable = NULL;
    static size_t * pAnimalVptable = NULL;
    
    struct Base
    {
        size_t * vptable;   //模拟虚表指针
    };
    
    struct Animal
    {
        struct Base base;   //模拟Animal继承Base
        size_t age;
    };
    
    typedef void (* pFun)(Base * pBase);
    
    void baseSleep(Base * pBase)
    {
        printf("Base sleep\n");
    }
    void baseEat(Base * pBase)
    {
        printf("Base eat\n");
    }
    void baseRun(Base * pBase)
    {
        printf("Base run\n");
    }
    
    void animalSleep(Animal * pAnimal)
    {
        printf("Animal age[%d] sleep\n", pAnimal->age);
    }
    void animalEat(Animal * pAnimal)
    {
        printf("Animal age[%d] eat\n", pAnimal->age);
    }
    void animalRun(Animal * pAnimal)
    {
        printf("Animal age[%d] run\n", pAnimal->age);
    }
    
    //Base结构体初始化
    void baseInit(Base * pBase)
    {
        pBase->vptable = pBaseVptable;
    }
    
    //Animal结构体初始化
    void AnimalInit(Animal * pAnimal)
    {
        pAnimal->base.vptable = pAnimalVptable;
    }
    
    /*
        虚表指针初始化
     */
    void vptableInit()
    {
        pBaseVptable = (size_t *)malloc(sizeof(size_t) * 4);
        pAnimalVptable = (size_t *)malloc(sizeof(size_t) * 4);
        
        memset(pBaseVptable, 0x0, sizeof(size_t) * 4);
        memset(pAnimalVptable, 0x0, sizeof(size_t) * 4);
        
        //Base类全局虚表初始化
        pBaseVptable[0] = (size_t)&baseSleep;
        pBaseVptable[1] = (size_t)&baseEat;
        pBaseVptable[2] = (size_t)&baseRun;
        
        //Animal类全局虚表初始化
        pAnimalVptable[0] = (size_t)&animalSleep;
        pAnimalVptable[1] = (size_t)&animalEat;
        pAnimalVptable[2] = (size_t)&animalRun;
    }
    
    void callVirtualFun(Base * pBase, int index)
    {
        pFun fun = (pFun)pBase->vptable[index];
        fun(pBase);
    }
    
    int main()
    {
        //虚表初始化
        vptableInit();
        Base * pBase = NULL;
        Animal * pAnimal = (Animal *)malloc(sizeof(Animal));
        
        //模拟对象初始化
        AnimalInit(pAnimal);
        pAnimal->age = 99;
        
        //模拟基类指针指向派生类
        pBase = (Base *)pAnimal;
        
        //模拟调用虚函数
        callVirtualFun(pBase, 0);
        callVirtualFun(pBase, 1);
        callVirtualFun(pBase, 2);
        
        return 0;
    }
    
    • 运行结果
    c模拟动态多态.png

    5.编译运行环境

    • 操作系统

    [root@iZ940zytujjZ ~]# uname -m -s
    Linux x86_64

    • 编译器

    [root@iZ940zytujjZ ~]# gcc --version
    gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-4)
    [root@iZ940zytujjZ ~]# g++ --version
    g++ (GCC) 4.4.7 20120313 (Red Hat 4.4.7-4)

    相关文章

      网友评论

        本文标题:以c的视角来诠释c++的多态

        本文链接:https://www.haomeiwen.com/subject/nkjonttx.html