• C++——多态|多态的概念|多态的定义及实现|虚函数|多态的原理|虚函数表构成虚表的条件


    目录

    多态的概念

    多态的定义及实现 

    虚函数 

    多态的原理 

     虚函数表

    构成虚表的条件 


    多态的概念

     多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。

    多态的定义及实现 

     多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了
    Person。Person对象买票全价,Student对象买票半价。
    那么在继承中要构成多态还有两个条件:
    1. 必须通过基类的指针或者引用调用虚函数
    2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

    虚函数 

     虚函数:即被virtual修饰的类成员函数称为虚函数

    1. class Person {
    2. public:
    3. virtual void BuyTicket() { cout << "买票-全价" << endl;}
    4. };


    虚函数的重写(覆盖)派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数.

    不符合重写,就是隐藏关系。

    继承中要构成多态还有两个条件:
    1. 必须通过基类的指针或者引用调用虚函数
    2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

    1. class Person {
    2. public:
    3. virtual void BuyTicket() { cout << "买票-全价" << endl; }
    4. };
    5. class Student : public Person {
    6. public:
    7. virtual void BuyTicket() { cout << "买票-半价" << endl; }
    8. };

     不满足多态

     当子类没有关键字virtual修饰,但父类有,此时也构成虚函数重写

    特例1:子类虚函数不加virtual,依旧构成重写,实际中最好加上

     特例2: 协变(基类与派生类虚函数返回值类型不同)

    派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指
    针或者引用,派生类虚函数返回派生类对象的指针或者引用
    时,称为协变。(了解)

     以下程序输出结果是什么(B)

    1. class A
    2. {
    3. public:
    4. virtual void func(int val = 1)
    5. { std::cout<<"A->"<< val <
    6. virtual void test()
    7. { func();}
    8. };
    9. class B : public A
    10. {
    11. public:
    12. void func(int val=0)
    13. { std::cout<<"B->"<< val <
    14. };
    15. int main(int argc ,char* argv[])
    16. {
    17. B*p = new B;
    18. p->test();
    19. return 0;
    20. }

    A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确


    子类指针调用test,但test不满足虚函数重写,为什么能调到呢?

    test调用需要传参,调用virtual void test(A*this),,func是A的成员函数,所以参数是A* this,test里面调用func(),其实是this->func();这里的this指向子类,因为是p->test();p是B*类型,所以打印B->

    为啥val会是1?

    因为虚函数重写是接口继承(主要继承接口),普通函数继承是实现继承(主要继承函数的实现),接口继承就是说子类继承父类的接口,这里用的接口是virtual void func(int val = 1)。所以会打印B->1。

    虚函数是接口继承,重写实现。

    虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数.

    不符合重写,就是隐藏关系。

    虚函数重写跟缺省参数,形参名无任何关系。

    把父类func参数改为0,此时会打印0,这是调用了父类接口 virtual void func(int val = 1),我们传了参数,所以会打印

    1. class A
    2. {
    3. public:
    4. virtual void func(int val = 1)
    5. {
    6. std::cout << "A->" << val << std::endl;
    7. }
    8. void test()
    9. {
    10. func();
    11. }
    12. };
    13. class B : public A
    14. {
    15. public:
    16. void func(int val = 0)
    17. {
    18. std::cout << "B->" << val << std::endl;
    19. }
    20. };
    21. int main(int argc, char* argv[])
    22. {
    23. A* p = new B;
    24. p->test();
    25. return 0;
    26. }

    打印结果:B->1

    注意这里P是A*,P指向B类型的空间

    这里满足:父类指针或引用调用虚函数,func是虚函数重写,所以是多态。P指向B,

    所以调用子类的func。

     此时调用父类,因为this指向父类

    多态的原理 

     虚函数表

     

     大小不是4,而是8,因为除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。

    创建一个对象,我们可以看到对象中含有_vfptr,我们还可以看到func1的地址被存进了虚表中,虚表是一个函数指针数组

    1. class Person {
    2. public:
    3. virtual void BuyTicket()
    4. { cout << "买票-全价" << endl; }
    5. virtual void Func()
    6. { cout << "Func()" << endl; }
    7. };
    8. class Student : public Person
    9. {
    10. public:
    11. virtual void BuyTicket()
    12. { cout << "买票-半价" << endl; }
    13. };
    14. void Func(Person& p)
    15. {
    16. p.BuyTicket();
    17. }
    18. int main()
    19. {
    20. Person Mike;
    21. Func(Mike);
    22. Student Johnson;
    23. Func(Johnson);
    24. return 0;
    25. }

     这里父对象和子对象的Func函数地址一样,Buyticket地址不一样,因为Buyticket被重写了

     当对象调用函数的时候,会call一个地址,这个地址存在虚表中,通过地址找到虚函数。

    多态的本质原理:当符合多态条件时,调用时,到指向对象的虚表中找到对应的函数地址,进行调用。

    普通函数在编译链接时会确定函数的地址,运行时直接调用。

    构成多态时调用

    非多态调用函数

    构成虚表的条件 

    虚函数不是存在虚表中,而是在代码段,虚表中存的是虚函数的地址。

    当基类没有被virtual修饰,而子类被virtual所修饰,此时子类没有虚表,因为不构成虚函数。就跟调用普通函数一样。

     当父类有虚函数,子类没有重写,但此时有虚表。编译器会通过虚表找到虚函数。当父类有虚函数,并且是通过父类的指针或引用调用虚函数,就会通过虚表去找该函数。此时调用的是父类的虚函数。

     虚表的条件:父类函数为虚函数,是否父类指针或引用调用虚函数

  • 相关阅读:
    云原生之旅 - 13)基于 Github Action 的自动化流水线
    如何构建最佳的SaaS联盟计划
    salesforce是什么
    IOT Core-设备接入网关
    成都易佰特的坑——E103-W06
    苍穹外卖学习
    在同一台机器上部署OGG并测试
    自学SLAM(8)《第四讲:相机模型与非线性优化》作业
    【数据结构】B树与B+树的联系与区别
    基于Jeecgboot前后端分离的ERP系统开发代码生成(一)
  • 原文地址:https://blog.csdn.net/weixin_49449676/article/details/127376755