• C++多态分析(polymorphisn)


    前言

    借着这次学校的生产实习来回顾下C++的多态,这里讨论下C++的多态以及实现原理。我都是在QT下使用C++的,就直接在QT下进行演示了

    多态介绍

    面向对象语言中最核心的几个理念就是:封装、继承、多态,其中我感觉多态是真正的核心,第一第二个只是它的辅助。同时多态又是不容易懂的,所以在这就简单的介绍下啦(虽然我也懂得不多,呵呵)

    静态联编

    第一个简单的小程序(重载的多态)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    
    #include <QCoreApplication>
    #include <QDebug>
    class Person {
    public :
        int age;
        Person() : age(10){}
        void getAge() {
            qDebug() << "Person: " << age;
        }
    };
    
    class VellBibi : public Person {
    public :
        int age;
        VellBibi() : age(21){}
        void getAge() {
            qDebug() << "VellBibi: " << age;
        }
    };
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        Person p;
        p.getAge();
    
        VellBibi v;
        v.getAge();
    
        Person *pPtr = &v;
        pPtr->getAge();
    
        return a.exec();
    }
    
    

    运行结果

    说明

    VellBibi类继承了Person类,VellBibi里重载了PersongetAge()方法,这样就实现了静态的多态,它的实现过程是在编译期间的。这种多态存在硬伤,就是当使用父类指针指向子类对象时,访问的是父类的东西,而不是子类的。这个算是C++的一个特性,在java里面就没有这个情况,因为java直接就是是动态联编,java的多态里面就不存在静态联编

    动态联编

    第二个简单的小程序(虚函数的多态)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    
    #include <QCoreApplication>
    #include <QDebug>
    class Person {
    public :
        int age;
        Person() : age(10){}
        virtual void getAge() {
            qDebug() << "Person: " << age;
        }
    };
    
    class VellBibi : public Person {
    public :
        int age;
        VellBibi() : age(21){}
        virtual void getAge() {
            qDebug() << "VellBibi: " << age;
        }
    };
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        Person p;
        p.getAge();
    
        VellBibi v;
        v.getAge();
    
        Person *pPtr = &v;
        pPtr->getAge();
    
        return a.exec();
    }
    
    

    运行结果

    说明

    这个小程序和上一个一样,唯一变得就是在PersongetAge()方法前加了一个virtual关键字,VellBibi可加可不加,但最好加上。在C++中virtual关键字就是用来声明虚函数的,所谓虚函数就是虚的函数嘛,呵呵,就是这个函数不是在编译期间就确定下来了,而是在运行期间动态指定的。这就是导致这个小程序和上个小程序的运行结果不同的原因,当使用父类指针指向子类对象时,调用父类虚函数时系统会自动寻找到子类对象的函数。

    接下来介绍下C++是怎么实现这个动态指定过程的

    多态的实现

    第三个小程序(静态联编时对象的大小)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    
    #include <QCoreApplication>
    #include <QDebug>
    class Person {
    public :
        int age;
        Person() : age(10){}
        void getAge() {
            qDebug() << "Person: " << age;
        }
    };
    
    class VellBibi : public Person {
    public :
        int age;
        VellBibi() : age(21){}
        void getAge() {
            qDebug() << "VellBibi: " << age;
        }
    };
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        Person p;
        p.getAge();
    
        VellBibi v;
        v.getAge();
    
        Person *pPtr = &v;
        pPtr->getAge();
    
        qDebug() << "PersonSize: " << sizeof(p);
        qDebug() << "VellBibiSize: " << sizeof(v);
    
        return a.exec();
    }
    
    

    运行结果

    说明

    这个程序只是在第一个程序上加了sizeof(),看出来了神马?Person是4个字节,也就是int age;的字节数;VellBibi是8个字节,其实就是Person的字节数加上VellBibiint age;的字节数。

    第四个小程序(动态联编时对象的大小)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    
    #include <QCoreApplication>
    #include <QDebug>
    class Person {
    public :
        int age;
        Person() : age(10){}
        virtual void getAge() {
            qDebug() << "Person: " << age;
        }
    };
    
    class VellBibi : public Person {
    public :
        int age;
        VellBibi() : age(21){}
        virtual void getAge() {
            qDebug() << "VellBibi: " << age;
        }
    };
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        Person p;
        p.getAge();
    
        VellBibi v;
        v.getAge();
    
        Person *pPtr = &v;
        pPtr->getAge();
    
        qDebug() << "PersonSize: " << sizeof(p);
        qDebug() << "VellBibiSize: " << sizeof(v);
    
        return a.exec();
    }
    
    

    运行结果

    说明

    这个程序只是在第二个程序上加了sizeof(),这时会发现Person变8个字节了,为神马呢?这是两个int型的大小啊,why?好了不卖关子了,这是静态Person的大小加上一个指针的大小,那哪来的指针呢?在Person里面也没有定义啊!呵呵,这是C++编译器自动加上的,加上用来动态指定的,只要存在virtual关键字的类最上面都是有一个这样的指针,指向一个vtable虚拟表,里面记录着这个类所有包含的虚函数地址。

    动态联编内存示例图:

    第五个小程序(动态联编内存分析)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    
    #include <QCoreApplication>
    #include <QDebug>
    class Person {
    public :
        int age;
        Person() : age(10){}
        virtual void getAge() {
            qDebug() << "Person: " << age;
        }
    };
    
    class VellBibi : public Person {
    public :
        int age;
        VellBibi() : age(21){}
        virtual void getAge() {
            qDebug() << "VellBibi: " << age;
        }
    };
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        Person p;
        VellBibi v;
    
        int *pfv = (int *)&p;
        int *vfv = (int *)&v;
        qDebug() << "PersonFirstValue: " << *pfv;
        qDebug() << "VellBibiFirstValue: " << *vfv;
    
        qDebug() << "PersonSecondValue: " << *(pfv + 1);
        qDebug() << "VellBibiSecondValue: " << *(vfv + 1);
    
        qDebug() << "PersonThirdValue: " << *(pfv + 2);
        qDebug() << "VellBibiSThirdValue: " << *(vfv + 2);
    
        int *pt = (int *)*pfv;
        int *vt = (int *)*vfv;
        qDebug() << "PersonVTableFirstValue: " << *pt;
        qDebug() << "VellBibiVTableFirstValue: " << *vt;
    
        qDebug() << "PersonVTableSecondValue: " << *(pt + 1);
        qDebug() << "VellBibiVTableSecondValue: " << *(vt + 1);
        return a.exec();
    }
    
    

    运行结果

    说明

    将对象第地址强制转换成int型指针来探寻对象内部数据的情况。前两行是对象的头地址的值,很明显这是一个指针;3、4行是第二个值,都是Personint age;变量; 5行是一个随机值,说明Person对象里面其实就只有一个int age;变量而已,其实在C++中类的实现也就是一个C语言的struct而已。再将头地址指向的vtable里的值取出来看看,7、8行就是各自vtable的第一个值,可以看出还是一个指针,指向的肯定是代码段相对应的函数啦~这两个指针指向了不同的函数,这也就是动态联编啦~当然还需要一个程序来说明,等会再说;再看看9、10行,都是0,也就是NULL很好理解,是不是似曾相识啊,没错就是字符串里的结束符啦!

    第六个小程序(动态联编的美妙)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    
    #include <QCoreApplication>
    #include <QDebug>
    class Person {
    public :
        int age;
        Person() : age(10){}
        virtual void getAge() {
            qDebug() << "Person: " << age;
        }
    };
    
    class VellBibi : public Person {
    public :
        int age;
        VellBibi() : age(21){}
        virtual void getAge() {
            qDebug() << "VellBibi: " << age;
        }
    };
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        Person p;
        VellBibi v;
        Person *vp = &v;
    
        int *pfv = (int *)&p;
        int *vfv = (int *)vp;
        qDebug() << "PersonFirstValue: " << *pfv;
        qDebug() << "VellBibiFirstValue: " << *vfv;
        qDebug();
    
        qDebug() << "PersonSecondValue: " << *(pfv + 1);
        qDebug() << "VellBibiSecondValue: " << *(vfv + 1);
        qDebug();
    
        qDebug() << "PersonThirdValue: " << *(pfv + 2);
        qDebug() << "VellBibiSThirdValue: " << *(vfv + 2);
        qDebug();
    
        int *pt = (int *)*pfv;
        int *vt = (int *)*vfv;
        qDebug() << "PersonVTableFirstValue: " << *pt;
        qDebug() << "VellBibiVTableFirstValue: " << *vt;
        qDebug();
    
        qDebug() << "PersonVTableSecondValue: " << *(pt + 1);
        qDebug() << "VellBibiVTableSecondValue: " << *(vt + 1);
    
        return a.exec();
    }
    
    

    运行结果

    说明

    这个应该很好理解了,只是定义了一个Person的指针vp,指向了VellBibi的对象v,然后将&v换成了vp,其他的都没变;也就是使用父类指针指向子类对象,结果可以看出和第五个程序是一样的,说明了C++的动态联编。

    来道分析题

    第七个小程序(问题代码)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    
    #include <QCoreApplication>
    #include <QDebug>
    class Person {
    public :
        int age;
        Person() : age(10){}
        virtual void getAge() {
            qDebug() << "Person: " << age;
        }
    };
    
    class VellBibi : public Person {
    public :
        int suiShu;
        VellBibi() : suiShu(21){}
        virtual void getAge() {
            qDebug() << "VellBibi: " << suiShu;
        }
    };
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        Person p;
        VellBibi v;
    
        Person *pptr = &p;
    
        int *pp = (int *)&p;
        int *vp = (int *)&v;
    
        *pp = *vp;
    
        pptr->getAge();
    
        return a.exec();
    }
    
    

    运行结果

    问题分析

    结果可以看出该打印的suiShu变随机数了;我这里使用了点小计俩,我先将对象的头地址当做int看待,然后将VellBibi对象的头地址赋值给了Person对象,最后使用Person对象的指针去调它自己的函数,会发现出错了。上内存分析图吧:

    没有改变Person对象头指针

    改变了Person对象头指针后

    继续分析,Person对象的vtable指针指向了VellBibivtable,当使用指针去访问时虚函数时就会做动态联编,Person对象会找到VellBibivtable然后找到了VellBibigetAge(),而VellBibigetAge()回去调它自己的变量也就是suishu变量,这下就出问题了,Person对象哪来的suishu变量?所以就系统就在那个不是Person对象内容的地方取了个值,当然这是随机的啦


    结束语

    写这篇帖子是为了明天实习的每人一讲啦~不过更是一种分享,我是从别人那得到的这份知识,想更好的让更多的人去收获这份知识,这就是分享的意义;当得知我做的努力给别人带来了帮助,我会非常高兴,这就是分享的乐趣。一起分享一起学习,Come on~~~

    原文地址: http://vview.ml/2014/07/20/polymorphisn.html
    written by Vell Bibi posted at VBlog

  • 相关阅读:
    java 单向链表实现
    super、this
    Java程序员们最常犯的10个错误
    Codeforces-1323D Present
    Codeforces-1323E Instant Noodles
    Codeforces-1312E Array Shrinking
    Codeforces-1327D Infinite Path
    Codeforces-1326D Prefix-Suffix Palindrome
    HDU-5885 XM Reserves
    NTT(快速数论变换)用到的各种素数及原根
  • 原文地址:https://www.cnblogs.com/VellBibi/p/polymorphisn.html
Copyright © 2020-2023  润新知