• 【C++ 学习 ⑲】- 多态(下)


    目录

    一、虚函数表和多态的原理

    1.1 - 虚函数表

    1.2 - 多态的原理

    二、单继承和多继承关系中的虚函数表

    2.1 - 单继承关系中的虚函数表

    2.2 - 多继承关系中的虚函数表

    三、纯虚函数和抽象类



    一、虚函数表和多态的原理

    1.1 - 虚函数表

    1. 问:sizeof(b) 是多少?

      1. #include
      2. using namespace std;
      3. ​
      4. class Base
      5. {
      6. public:
      7. virtual void func1() { cout << "Base::func1()" << endl; }
      8. virtual void func2() { cout << "Base::func2()" << endl; }
      9. void func3() { cout << "Base::func3()" << endl; }
      10. protected:
      11. int _i = 1;
      12. };
      13. ​
      14. int main()
      15. {
      16. Base b;
      17. cout << sizeof(b) << endl;
      18. return 0;
      19. }

      通过调试可以发现,在 b 对象内存模型中,除了 _i 成员,还有一个名为 _vfptr 的成员,它是虚函数表指针,所以 sizeof(b) 是 8 或 16 字节。

      一个含有虚函数的类对象至少有一个指向虚函数表的指针,虚函数表本质上是一个存放虚函数地址的函数指针数组,一般情况下在这个数组的最后面还放了一个 nullptr。

      虚函数表可以简称为虚表。因为 func3 不是虚函数,所以没有放进虚表中。

    2. 提一个很容易混淆的问题:虚函数存在哪里?虚表又存在哪里?虚函数和普通函数一样,都是存在代码段的;而虚表存在哪里可以通过以下代码得知:

      1. #include
      2. using namespace std;
      3. ​
      4. class Base
      5. {
      6. public:
      7. virtual void func1() { cout << "Base::func1()" << endl; }
      8. virtual void func2() { cout << "Base::func2()" << endl; }
      9. void func3() { cout << "Base::func3()" << endl; }
      10. protected:
      11. int _i = 1;
      12. };
      13. ​
      14. typedef void(*VFPTR)();
      15. ​
      16. int main()
      17. {
      18. int m = 0;
      19. printf("栈:%p\n", &m);
      20. ​
      21. int* p1 = new int;
      22. printf("堆:%p\n", p1);
      23. ​
      24. static int n = 0;
      25. printf("静态区:%p\n", &n);
      26. ​
      27. const char* str = "abcdef";
      28. printf("常量区:%p\n", str);
      29. ​
      30. Base b;
      31.    // 通过对象的地址获取虚函数表的地址
      32. VFPTR* p2 = (VFPTR*)*(int*)&b;  
      33. printf("虚表:%p\n", p2);
      34. return 0;
      35. }

      根据输出结果,我们有理由相信虚表也是存在代码段的。

    3. 让派生类 Derive 继承自 Base,然后在派生类中重写基类虚函数 func1:

      1. #include
      2. using namespace std;
      3. ​
      4. class Base
      5. {
      6. public:
      7. virtual void func1() { cout << "Base::func1()" << endl; }
      8. virtual void func2() { cout << "Base::func2()" << endl; }
      9. void func3() { cout << "Base::func3()" << endl; }
      10. protected:
      11. int _i = 1;
      12. };
      13. ​
      14. class Derive : public Base
      15. {
      16. public:
      17. virtual void func1() { cout << "Derived::func1()" << endl; }
      18. protected:
      19. int _j = 2;
      20. };
      21. ​
      22. int main()
      23. {
      24. Base b;
      25. Derive d;
      26. return 0;
      27. }

      因为在派生类中重写了基类的虚函数 func1,所以基类对象 b 和派生类对象 d 的虚表是不一样的。

      d 的虚表中存的是重写的 Derive::func1,所以虚函数的重写也叫作覆盖,覆盖就是虚表中虚函数的覆盖。重写是语法上的叫法,覆盖是原理层的叫法。

    1.2 - 多态的原理

    1. #include
    2. using namespace std;
    3. ​
    4. class Base
    5. {
    6. public:
    7. virtual void func1() { cout << "Base::func1()" << endl; }
    8. virtual void func2() { cout << "Base::func2()" << endl; }
    9. void func3() { cout << "Base::func3()" << endl; }
    10. protected:
    11. int _i = 1;
    12. };
    13. ​
    14. class Derive : public Base
    15. {
    16. public:
    17. virtual void func1() { cout << "Derive::func1()" << endl; }
    18. protected:
    19. int _j = 2;
    20. };
    21. ​
    22. int main()
    23. {
    24. Base b;
    25. Base* pb = &b;
    26. pb->func1();  // Base::func1()
    27. ​
    28. Derive d;
    29. pb = &d;
    30. pb->func1();  // Derive::func1()
    31. return 0;
    32. }

    当基类指针 pb 指向基类对象 b 时,pb->func1(); 就是在 b 的虚表中找到虚函数 Base::func1。

    当基类指针 pb 指向派生类对象 d 时,pb->func1(); 就是在 d 的虚表中找到虚函数 Derive::func1。

    这样就让基类指针表现出了多种形态。

    注意:不满足多态的函数调用是编译时确定好的,满足多态的函数调用是运行时去对象中找的:

    1. Base b;
    2. b.func1();
    3. // 00195182 lea ecx,[b]
    4. // 00195185 call Person::func1 (01914F6h)
    5. // 汇编代码分析:
    6. // 虽然 func1 是虚函数,但是 b 是对象,不满足多态的条件,所以这里是普通函数的调用,
    7. // 编译时就确定好了函数的地址,直接 call。
    8. ​
    9. Base* pb = &b;
    10. pb->func1();
    11. // 注意:不相关的汇编代码被省去了
    12. // 001940DE mov eax,dword ptr [pb]
    13. // 001940E1 mov edx,dword ptr [eax]
    14. // 00B823EE mov eax,dword ptr [edx]
    15. // 001940EA call eax
    16. // 汇编代码分析:
    17. // 1、pb 中存的是 b 对象的地址,将 pb 移动到 eax 中
    18. // 2、[eax] 就是取 eax 值指向的内容,相当于把 b 对象中的虚表指针移动到 edx
    19. // 3、[edx] 就是取 edx 值指向的内容,相当于把虚表中第一个虚函数的地址移动到 eax
    20. // 4、call eax 中存的虚函数地址
    21. // 由此可以看出满足多态的函数调用,不是在编译时确定的,而是运行起来后去对象中找的。

     


    二、单继承和多继承关系中的虚函数表

    2.1 - 单继承关系中的虚函数表

    1. #include
    2. using namespace std;
    3. ​
    4. class Base
    5. {
    6. public:
    7. virtual void func1() { cout << "Base::func1()" << endl; }
    8. virtual void func2() { cout << "Base::func2()" << endl; }
    9. protected:
    10. int _i = 1;
    11. };
    12. ​
    13. class Derive : public Base
    14. {
    15. public:
    16. virtual void func1() { cout << "Derive::func1()" << endl; }
    17. virtual void func3() { cout << "Derive::func3()" << endl; }
    18. virtual void func4() { cout << "Derive::func4()" << endl; }
    19. protected:
    20. int _j = 2;
    21. };
    22. ​
    23. int main()
    24. {
    25. Base b;
    26. Derive d;
    27. return 0;
    28. }

    在 d 的虚表中,我们看不到虚函数 func3 和 func4,这可能是监视窗口故意隐藏了这两个函数,也可能是一个小 bug,我们可以通过以下代码进行验证:

    1. typedef void(*VFPTR)();
    2. ​
    3. void PrintVftable(VFPTR vftable[])
    4. {
    5. for (size_t i = 0; vftable[i] != nullptr; ++i)
    6. {
    7. printf("第 %d 个虚函数地址:0X%p, -->", i, vftable[i]);
    8. vftable[i]();
    9. }
    10. cout << endl;
    11. }
    12. ​
    13. void test()
    14. {
    15.    Base b;
    16. VFPTR* p1 = (VFPTR*)*(int*)&b;
    17. PrintVftable(p1);
    18. ​
    19. Derive d;
    20. VFPTR* p2 = (VFPTR*)*(int*)&d;
    21. PrintVftable(p2);
    22. }

     

    2.2 - 多继承关系中的虚函数表

    1. #include
    2. using namespace std;
    3. ​
    4. class Base1
    5. {
    6. public:
    7. virtual void func1() { cout << "Base1::func1()" << endl; }
    8. virtual void func2() { cout << "Base1::func2()" << endl; }
    9. protected:
    10. int _i1;
    11. };
    12. ​
    13. class Base2
    14. {
    15. public:
    16. virtual void func1() { cout << "Base2::func1()" << endl; }
    17. virtual void func2() { cout << "Base2::func2()" << endl; }
    18. protected:
    19. int _i2;
    20. };
    21. ​
    22. class Derive : public Base1, public Base2
    23. {
    24. public:
    25. virtual void func1() { cout << "Derive::func1()" << endl; }
    26. virtual void func3() { cout << "Derive::func3()" << endl; }
    27. protected:
    28. int _j;
    29. };
    30. ​
    31. typedef void(*VFPTR)();
    32. ​
    33. void PrintVftable(VFPTR vftable[])
    34. {
    35. for (size_t i = 0; vftable[i] != nullptr; ++i)
    36. {
    37. printf("第 %d 个虚函数地址:0X%p, -->", i, vftable[i]);
    38. vftable[i]();
    39. }
    40. cout << endl;
    41. }
    42. ​
    43. int main()
    44. {
    45. Derive d;
    46. VFPTR* p1 = (VFPTR*)*(int*)&d;
    47. PrintVftable(p1);
    48. ​
    49. // VFPTR* p2 = (VFPTR*)*(int*)((char*)&d + sizeof(Base1));
    50. // PrintVftable(p2)
    51. // 或者:
    52. Base2* p2 = &d;
    53. PrintVftable((VFPTR*)*(int*)p2);
    54. return 0;
    55. }

    1. 派生类中的虚函数 func3 放在第一个继承自基类部分的虚函数表中。

    2. 假设有以下场景:

      1. Derive d;
      2. Base1* p1 = &d;
      3. p1->func1();
      4. Base2* p2 = &d;
      5. p2->func1();

      首先要确定的是:

      所以在语句 p2->func1(); 中,需要修正 this 指针:

      这也是为什么在 d 的两个虚表中,重写的虚函数 func1 的地址不一样。


    三、纯虚函数和抽象类

    纯虚函数是一种特殊的虚函数,在某些情况下,在基类中不能对虚函数给出有意义的实现,就可以把它声明为纯虚函数。纯虚函数只有函数名、参数和返回值类型,没有函数体,具体实现留给派生类去做。具体语法:virtual 返回值类型 函数名(参数列表) = 0;。

    含有纯虚函数的类被称为抽象类(或接口类),不能实例化对象,但可以创建指针和引用。

    派生类必须重写抽象类中的纯虚函数,否则也属于抽象类。

    1. #include
    2. using namespace std;
    3. ​
    4. class Car
    5. {
    6. public:
    7. virtual void Drive() = 0;
    8. };
    9. ​
    10. class AITO : public Car
    11. {
    12. public:
    13. virtual void Drive() { cout << "Intelligent" << endl; }
    14. };
    15. ​
    16. class AVATR : public Car
    17. {
    18. public:
    19. virtual void Drive() { cout << "Comfortable" << endl; }
    20. };
    21. ​
    22. void func1(Car* p) { p->Drive(); }
    23. ​
    24. void func2(Car& c) { c.Drive(); }
    25. ​
    26. int main()
    27. {
    28. AITO aito;
    29. AVATR avatr;
    30. ​
    31. func1(&aito);  // Intelligent
    32. func1(&avatr);  // Comfortable
    33. ​
    34. func2(aito);  // Intelligent
    35. func2(avatr);  // Comfortable
    36. return 0;
    37. }
  • 相关阅读:
    【电力系统状态估计与PMU(相量测量单元)】使用WLS和PMU来估计系统的电压幅值和角度还将这些值与使用Newton-Raphson方法获得的状态进行比较(Matlab代码实现)
    proxy解决跨域问题
    关于面向对象的方法并行执行的问题
    深度学习篇之tensorflow(3) ---架构介绍篇二
    说说你对关键字this的认识?
    MySQL 创建用户并设置权限
    PHP对接企业微信创建审批应用和对接提交审批
    Charles的奇巧淫技
    如何用Python语音合成,以及文字转语音~
    Java系列 超简单说人话的 异常类详细讲解 Exception MyExcepyion try catch语句
  • 原文地址:https://blog.csdn.net/melonyzzZ/article/details/132672025