C++深度解析(41)—C++对象模型分析(下)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_22847457/article/details/97019993

1.继承对象模型

  • 在C++编译器的内部类可以理解为结构体
  • 子类是由父类成员叠加子类新成员得到的

  • 编程实验——继承对象模型初探

 
 
  1. #include<iostream>
  2. using namespace std;
  3. class Demo
  4. {
  5. protected:
  6. int mi;
  7. int mj;
  8. public:
  9. void print()
  10. {
  11. cout << "mi = " << mi << ","
  12. << "mj = " << mj << endl;
  13. }
  14. };
  15. class Derived : public Demo
  16. {
  17. int mk;
  18. public:
  19. Derived( int i, int j, int k)
  20. {
  21. mi = i;
  22. mj = j;
  23. mk = k;
  24. }
  25. void print()
  26. {
  27. cout << "mi = " << mi << ","
  28. << "mj = " << mj << ","
  29. << "mk = " << mk << endl;
  30. }
  31. };
  32. struct Test
  33. {
  34. int mi;
  35. int mj;
  36. int mk;
  37. };
  38. int main()
  39. {
  40. cout << "sizeof(Demo) = " << sizeof(Demo) << endl;
  41. cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
  42. cout << "sizeof(Tset) = " << sizeof(Test) << endl;
  43. Derived d(1, 2, 3);
  44. Test *p = reinterpret_cast<Test *>(&d); // 将类指针强制类型转换为结构体指针
  45. cout << "Before changing ..." << endl;
  46. d.print();
  47. p->mi = 10;
  48. p->mj = 20;
  49. p->mk = 30;
  50. cout << "After changing ..." << endl;
  51. d.print();
  52. system( "pause");
  53. return 0;
  54. }
  • 运行结果

  • 很巧妙的一个实验,通过比较所占字节大小得出结论。另外,强制类型转换后,更改数据,打印结果显示数据改变,说明类的内存结构和结构体的内存结构是相同的,强化这一思想。

2.多态对象模型

  • C++多态的实现原理
    • 当类中声明虚函数时,编译器会在类中生成一个虚函数表
    • 虚函数表是一个存储成员函数地址的数据结构
    • 虚函数表是由编译器自动生成与维护的
    • virtual成员函数会被编译器放入虚函数表中
    • 存在虚函数时,每个对象中都有一个指向虚函数表的指针

  • 编译两个类,编译器生成了两张虚函数表,并将虚函数放入虚函数表

  • 当类中有虚函数,创建对象时,在该对象中"塞"入一个指针成员变量,指向虚函数表 

  • 通过p指针找到具体对象,通过具体对象的虚函数表指针找到虚函数表,在虚函数表找到函数地址,三次寻址,C++的多态通过牺牲效率实现

3.编程实验

  • 虚函数表指针的存在,继承中的对象模型 

 
 
  1. #include<iostream>
  2. using namespace std;
  3. class Demo
  4. {
  5. protected:
  6. int mi;
  7. int mj;
  8. public:
  9. virtual void print()
  10. {
  11. cout << "mi = " << mi << ","
  12. << "mj = " << mj << endl;
  13. }
  14. };
  15. class Derived : public Demo
  16. { // 继承自父类
  17. int mk;
  18. public: // 成员函数存储在代码段 ,不占类空间
  19. Derived( int i, int j, int k)
  20. {
  21. mi = i;
  22. mj = j;
  23. mk = k;
  24. }
  25. void print()
  26. {
  27. cout << "mi = " << mi << ","
  28. << "mj = " << mj << ","
  29. << "mk = " << mk << endl;
  30. }
  31. };
  32. struct Test
  33. {
  34. void *p; // 说明那个虚函数的指针在这里,要对齐。
  35. int mi;
  36. int mj;
  37. int mk;
  38. };
  39. int main()
  40. {
  41. cout << "sizeof(Demo) = " << sizeof(Demo) << endl; // 12, 不是8,因为插入了一个虚函数表指针(指针长度4字节)
  42. cout << "sizeof(Derived) = " << sizeof(Derived) << endl; // /16,不是12,因为继承了父类,原因同上。
  43. cout << "sizeof(Tset) = " << sizeof(Test) << endl;
  44. Derived d(1, 2, 3);
  45. Test *p = reinterpret_cast<Test *>(&d); // 将类指针强制类型转换为结构体指针
  46. // 以下实验证明带有虚函数的Derived的内存模型与Test结构体是一致的!
  47. //1.大小相同,2.第1个成员变量vptr(虚函数)指针;3.往后依次为mi,mj,mk
  48. cout << "Before changing ..." << endl;
  49. d.print();
  50. p->mi = 10;
  51. p->mj = 20;
  52. p->mk = 30;
  53. cout << "After changing ..." << endl;
  54. d.print();
  55. system( "pause");
  56. return 0;
  57. }
  • 运行结果:

4.多态本质分析  

  • 下面用C实现C++的封装,继承,多态特性,深入理解C++对象模型
  • test.h

 
 
  1. #ifndef _TEST_H_
  2. #define _TEST_H_
  3. typedef void Demo;
  4. typedef void Derived;
  5. Demo *Demo_Create(int i, int j);
  6. int Demo_GetI(Demo *pThis);
  7. int Demo_GetJ(Demo *pThis);
  8. int Demo_Add(Demo *pThis, int value);
  9. void Demo_Free(Demo *pThis);
  10. Derived *Derived_Create(int i, int j, int k);
  11. int Derived_GetK(Derived *pThis);
  12. int Derived_Add(Derived *pThis, int value);
  13. #endif
  • test.c

 
 
  1. #include "test.h"
  2. #include <malloc.h>
  3. #include <stdio.h>
  4. // static修饰函数,说明函数只能被本文件所使用,和修饰全局变量一样,限定在本文件作用域,隐藏的虚函数表。
  5. static int Demo_Virtual_Add(Demo *pThis, int value);
  6. static int Derived_Virtual_Add(Derived *pThis, int value);
  7. struct VTable // 2.定义虚函数表数据结构
  8. { // 这是一张表
  9. int(*pAdd)( void *, int); // 3.虚函数表里面存储的内容是(函数指针),主要这个pAdd具体指向谁,是地址层面的具体数值。
  10. };
  11. struct ClassDemo // 父类结构体
  12. {
  13. struct VTable *vptr; // 1.数定义虚函表指针 ==》虚函数表指针的类型
  14. int mi;
  15. int mj;
  16. };
  17. struct ClassDerived // 子类结构体,拥有父类结构体里面的成员。也包括那个虚函数表指针
  18. {
  19. struct ClassDemo d; // 模拟继承,实质就是父类成员叠加到子类成员中去。
  20. int mk;
  21. };
  22. // 父类的虚函数表,static隐藏到当前文件中。// 给虚函数表初始化
  23. static struct VTable g_Demo_vtbl = // g_Demo_vtbl的类型为static struct VTable
  24. {
  25. Demo_Virtual_Add // 内部都是一个函数指针,函数名。
  26. };
  27. //子类的虚函数表
  28. static struct VTable g_Derived_vtbl = // 静态结构体变量。
  29. {
  30. Derived_Virtual_Add // 内部都是一个函数指针
  31. };
  32. /******************     父类    ***********************/
  33. Demo *Demo_Create( int i, int j) // 相当于构造函数
  34. {
  35. struct ClassDemo *ret = ( struct ClassDemo *)malloc( sizeof( struct ClassDemo));
  36. if (ret != NULL)
  37. {
  38. // 4.只要有父类的对象产生,那么调用构造函数,关联虚函数表。
  39. ret->vptr = &g_Demo_vtbl; // 4.(g_Demo_vtbl)结构体类型,关联对象和虚函数表
  40. ret->mi = i;
  41. ret->mj = j;
  42. }
  43. return ret;
  44. }
  45. int Demo_GetI(Demo *pThis)
  46. {
  47. struct ClassDemo *obj = ( struct ClassDemo*)pThis;
  48. return obj->mi;
  49. }
  50. int Demo_GetJ(Demo *pThis)
  51. {
  52. struct ClassDemo *obj = ( struct ClassDemo*)pThis;
  53. return obj->mj;
  54. }
  55. // 6.定义虚函数表中指针所指向的具体函数
  56. static int Demo_Virtual_Add(Demo *pThis, int value)
  57. {
  58. struct ClassDemo *obj = ( struct ClassDemo *)pThis;
  59. return obj->mi + obj->mj + value;
  60. }
  61. // 5.分析具体的虚函数
  62. int Demo_Add(Demo *pThis, int value) // 是个虚函数
  63. {
  64. struct ClassDemo *obj = ( struct ClassDemo*)pThis;
  65. // 从虚函数表中找到真正的实现函数
  66. return obj->vptr->pAdd(pThis, value); // 虚函数表具体的指向,这个vptr在对象的构造函数中初始化。
  67. }
  68. void Demo_Free(Demo *pThis)
  69. {
  70. free(pThis);
  71. }
  72. /******************         Derived类  ,子类      ***********************/
  73. Derived* Derived_Create( int i, int j, int k) // 构造函数
  74. {
  75. struct ClassDerived *ret = ( struct ClassDerived *)malloc( sizeof( struct ClassDerived));
  76. if (ret != NULL)
  77. {
  78. // 只要有子类的对象产生,那么调用构造函数,关联虚函数
  79. //(g_Derived_vtbl)结构体类型,关联对象和虚函数表
  80. ret->d.vptr = &g_Derived_vtbl; // 结构体指针,可以访问结构体成员。
  81. ret->d.mi = i;
  82. ret->d.mj = j;
  83. ret->mk = k;
  84. }
  85. return ret;
  86. }
  87. int Derived_GetK(Derived *pThis)
  88. {
  89. struct ClassDerived *obj = ( struct ClassDerived*)pThis;
  90. return obj->mk;
  91. }
  92. // 定义子类虚函数表中指针所指向的具体函数
  93. static int Derived_Virtual_Add(Derived *pThis, int value)
  94. {
  95. struct ClassDerived *obj = ( struct ClassDerived *)pThis;
  96. return obj->mk + value;
  97. }
  98. // 分析虚函数
  99. int Derived_Add(Derived* pThis, int value)
  100. {
  101. struct ClassDerived *obj = ( struct ClassDerived*)pThis;
  102. // 从虚函数表中找到真正的实现函数
  103. return obj->d.vptr->pAdd(pThis, value);
  104. }
  • main.c

 
 
  1. #include <stdio.h>
  2. #include "test.h"
  3. void run(Demo *p, int v)
  4. {
  5. int r = Demo_Add(p, v); //多态
  6. printf( "r = %d\n", r);
  7. }
  8. int main()
  9. { //这两个指针类型都是void的。
  10. Demo *pb = Demo_Create( 1, 2);
  11. Derived *pd = Derived_Create( 1, 22, 333);
  12. printf( "pb->add(3) = %d\n", Demo_Add(pb, 3));
  13. printf( "pd->add(3) = %d\n", Derived_Add(pd, 3));
  14. run(pb, 3);
  15. run(pd, 3);
  16. Demo_Free(pb);
  17. Demo_Free(pd);
  18. system( "pause");
  19. return 0;
  20. }
  • 运行结果:

5.小结

  • 继承的本质是父子间成员变量的叠加
  • C++中的多态是通过虚函数表实现
  • 虚函数表是由编译器自动生成与维护的
  • 虚函数的调用效率低于普通成员函数。(虚函数要经过几次寻址

6.补充说明

  • 面向对象程序最关键的地方在于必须能够表现三大特性:封装,继承,多态!

    <ul><li>封装指的是类中的敏感数据在外界是不能访问的;</li>
    	<li>继承指的是可以对已经存在的类进行代码复用,并使得类之间存在父子关系;</li>
    	<li>多态指的是相同的调用语句可以产生不同的调用结果。</li>
    </ul></li>
    <li>因此,如果希望用 C 语言完成面向对象的程序,那么肯定的,必须实现这三个特性;否则,最多只算得上基于对象的程序</li>
    

6.1封装

  • 上文中通过 void* 指针保证具体的结构体成员是不能在外界被访问的,以此模拟 C++ 中 private 和 protected。因此,在头文件中定义了如下的语句:

 
 
  1. typedef void Demo;
  2. typedef void Derived;
  • Demo 和 Derived 的本质依旧是 void, 所以,用 Demo* 指针和 Derived* 指针指向具体的对象时,无法访问对象中的成员变量,这样就达到了“外界无法访问类中私有成员”的封装效果!

6.2 继承

  • 继承的本质是父类成员与子类成员的叠加,所以在用 C 语言写面向对象程序的时候,可以直接考虑结构体成员的叠加即可。文中的实现直接将 struct ClassDemo d 作为 struct ClassDerived 的第一个成员,以此表现两个自定义数据类型间的继承关系。因为 struct Class Derived 变量的实际内存分布就是由struct Class Demo 的成员以及 struct Class Derived 中新定义的成员组成的,这样就直接实现了继承的本质,所以说 struct Class Derived 继承自 struct ClassDemo

6.3 多态

  • 多态在 C++ 中的实现本质是通过虚函数表完成的,而虚函数表是编译器自主产生和维护的数据结构。因此,接下来要解决的问题就是如何在 C 语言中自定义虚函数表?文中认为通过结构体变量模拟 C++中的虚函数表是比较理想的一种选择,所以有了下面的代码:

 
 
  1. struct VTable  
  2. {
  3.     void (*pAdd)( void*,  int);  
  4. }; 
  • 必须要注意的是,若误将 pAdd 指针的类型定义成了int (*)(Derived*, int) ,这从 C 语言的角度算不上错误, 所以编译运行都没有问题。但是,从面向对象的角度,这里可以说是一种语义上的错误!因为 pAdd 必须可以指向父类中定义的Add函数版本,也可以指向子类中定义的 Add 函数版本,所以说用 Derived* 作为第一个参数表示实际对象并不合适,应该直接使用 void* 。有了类型后就可以定义实际的虚函数表了,在 C 语言中用具有文件作用域的全局变量表示实际的虚函数表是最合适的,因此有了下面的代码:

 
 
  1. // 父类对象使用的虚函数表    
  2. static  struct VTable g_Demo_vtbl =  
  3. {  
  4. Demo_Virtual_Add  
  5. };  
  6. // 子类对象使用的虚函数表  
  7. static  struct VTable g_Derived_vtbl =  
  8. {  
  9. Derived_Virtual_Add  
  10. };
  • 每个对象中都拥有一个指向虚函数表的指针,而所有父类对象都指向g_Demo_vtbl,所以所有子类对象都指向 g_Derived_vtbl。当一切就绪后,实际调用虚函数的过程就是通过虚函数表中的对应指针来完成的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值