继承对象模型:

继承对象模型初探:

结构体与类等价的示例:

 

可以看到mi、mj、mk被改变了,这三个值在外界本来是访问不到的,但是现在通过一个指针却改变了。

这说明本例中结构体的内存分布和继承类对象的内存分布是一样的。

父类的成员排在前面,继承类新添加的成员排在后面。

 

多态对象模型:

 

 

 

 

多态本质分析小实验:

运行结果如下:

可以看到定义了虚函数后,类的大小增加了4个字节,这说明确实多了一个虚函数表指针。

 

 测试虚函数表指针的位置:

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 class Demo
 7 {
 8 protected:
 9     int mi;
10     int mj;
11 public:
12     virtual void print()
13     {
14         cout << "mi = " << mi << ", "
15              << "mj = " << mj << endl;
16     }
17 };
18 
19 class Derived : public Demo
20 {
21     int mk;
22 public:
23     Derived(int i, int j, int k)
24     {
25         mi = i;
26         mj = j;
27         mk = k;
28     }
29     
30     void print()
31     {
32         cout << "mi = " << mi << ", "
33              << "mj = " << mj << ", "
34              << "mk = " << mk << endl;
35     }
36 };
37 
38 struct Test
39 {
40     void* p;
41     int mi;
42     int mj;
43     int mk;
44 };
45 
46 int main()
47 {
48     cout << "sizeof(Demo) = " << sizeof(Demo) << endl;         
49     cout << "sizeof(Derived) = " << sizeof(Derived) << endl;  
50     
51     Derived d(1, 2, 3);
52     Test* p = reinterpret_cast<Test*>(&d);
53     
54     cout << "Before changing ..." << endl;
55     
56     d.print();
57     
58     p->mi = 10;
59     p->mj = 20;
60     p->mk = 30;
61     
62     cout << "After changing ..." << endl;
63     
64     d.print();
65     
66     return 0;
67 }

 运行结果:

 

打印结果正确,这说明虚函数表的指针确实在开头的位置。要不然,我们重解释内存后,在结构体中修改的mi、mj、mk和类对象中的mi、mj、mk是不对应的。

 

用C写面向对象,并实现多态:

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

 

  1 #include "51-2.h"
  2 #include "malloc.h"
  3 
  4 static int Demo_Virtual_Add(Demo* pThis, int value);
  5 static int Derived_Virtual_Add(Demo* pThis, int value);
  6 
  7 struct VTable     // 2. 定义虚函数表数据结构
  8 {
  9     int (*pAdd)(void*, int);   // 3. 虚函数表里面存储什么???
 10 };
 11 
 12 struct ClassDemo
 13 {
 14     struct VTable* vptr;     // 1. 定义虚函数表指针  ==》 虚函数表指针类型???
 15     int mi;
 16     int mj;
 17 };
 18 
 19 struct ClassDerived
 20 {
 21     struct ClassDemo d;
 22     int mk;
 23 };
 24 
 25 static struct VTable g_Demo_vtbl = 
 26 {
 27     Demo_Virtual_Add
 28 };
 29 
 30 static struct VTable g_Derived_vtbl = 
 31 {
 32     Derived_Virtual_Add
 33 };
 34 
 35 Demo* Demo_Create(int i, int j)
 36 {
 37     struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo)); 
 38 
 39     if( ret != NULL )
 40     {
 41         ret->vptr = &g_Demo_vtbl;   // 4. 关联对象和虚函数表
 42         ret->mi = i;
 43         ret->mj = j;
 44     }
 45     
 46     return ret;
 47 }
 48 
 49 int Demo_GetI(Demo* pThis)
 50 {
 51      struct ClassDemo* obj = (struct ClassDemo*)pThis;    
 52 
 53      return obj->mi;
 54 }
 55 
 56 int Demo_GetJ(Demo* pThis)
 57 {
 58     struct ClassDemo* obj = (struct ClassDemo*)pThis;
 59 
 60     return obj->mj;
 61 }
 62 
 63 // 6. 定义虚函数表中指针所指向的具体函数
 64 static int Demo_Virtual_Add(Demo* pThis, int value)
 65 {
 66     struct ClassDemo* obj = (struct ClassDemo*)pThis;
 67     
 68     return obj->mi + obj->mj + value;
 69 }
 70 
 71 
 72 // 5. 分析具体的虚函数!!!!
 73 int Demo_Add(Demo* pThis, int value)
 74 {
 75 
 76     struct ClassDemo* obj = (struct ClassDemo*)pThis;
 77 
 78     return obj->vptr->pAdd(pThis, value);
 79 }
 80 
 81 void Demo_Free(Demo* pThis)
 82 {
 83     free(pThis);
 84 }
 85 
 86 Derived* Derived_Create(int i, int j, int k)
 87 {
 88     struct ClassDerived* ret = (struct ClassDerived*)malloc(sizeof(struct ClassDerived));
 89     
 90     if( ret != NULL )
 91     {
 92         ret->d.vptr = &g_Derived_vtbl;
 93         ret->d.mi = i;
 94         ret->d.mj = j;
 95         ret->mk = k;
 96     }
 97     
 98     return ret;
 99 }
100 
101 int Derived_GetK(Derived* pThis)
102 {
103     struct ClassDerived* obj = (struct ClassDerived*)pThis;
104     
105     return obj->mk;
106 }
107 
108 static int Derived_Virtual_Add(Demo* pThis, int value)
109 {
110     struct ClassDerived* obj = (struct ClassDerived*)pThis; 
111 
112     return obj->mk + value;
113 }
114 
115 int Derived_Add(Derived* pThis, int value)
116 {   
117     struct ClassDerived* obj = (struct ClassDerived*)pThis;
118     
119     return obj->d.vptr->pAdd(pThis, value);
120 }

 

 1 #include "stdio.h"
 2 #include "51-2.h"
 3 
 4 void run(Demo* p, int v)
 5 {
 6     int r = Demo_Add(p, v);
 7     
 8     printf("r = %d\n", r);
 9 }
10 
11 int main()
12 {
13     Demo* pb = Demo_Create(1, 2);
14     Derived* pd = Derived_Create(1, 22, 333);
15     
16     printf("pb->add(3) = %d\n", Demo_Add(pb, 3));
17     printf("pd->add(3) = %d\n", Derived_Add(pd, 3));
18     
19     run(pb, 3);
20     run(pd, 3);
21     
22     Demo_Free(pb);
23     Demo_Free(pd);
24     
25     return 0;
26 }

我们定义了虚函数表的结构体类型,并且定义了一个全局的虚函数表类型的变量。

我们在ClassDemo结构体中塞入一个“虚函数表”指针vtbl。

我们模拟编译器的行为,在生成对象时,将虚函数的地址写入虚函数表变量中,并将虚函数表变量的地址挂接到ClassDemo结构体中的虚函数表指针上。

运行结果如下:

 

小结:

 

 

补充说明:

  面向对象程序最关键的地方在于必须能够表现三大特性:封装,继承,多态!
封装指的是类中的敏感数据在外界是不能访问的;继承指的是可以对已经存在的类
进行代码复用,并使得类之间存在父子关系;多态指的是相同的调用语句可以产生
不同的调用结果。因此,如果希望用 C 语言完成面向对象的程序,那么肯定的,

必须实现这三个特性;否则,最多只算得上基于对象的程序(程序中能够看到对象
的影子,但是不完全具备面向对象的 3 大特性)。

 

课程中通过 void* 指针保证具体的结构体成员是不能在外界被访问的,以
此模拟 C++ 中 private 和 protected。因此,在头文件中定义了如下的语句:
  typedef void Demo;
  typedef void Derived;
Demo 和 Derived 的本质依旧是 void, 所以,用 Demo* 指针和 Derived* 指针

指向具体的对象时,无法访问对象中的成员变量,这样就达到了“外界无法访问类
中私有成员”的封装效果!

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

struct ClassDemo 的成员以及 struct ClassDerived 中新定义的成员组成的,这

样就直接实现了继承的本质,所以说 struct ClassDerived 继承自 struct
ClassDemo。

 

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

struct VTable
{
  int (*pAdd)(void*, int);
};

  必须要注意的是,课程中由于复制粘贴的缘故,误将 pAdd 指针的类型定义成了
int (*)(Derived*, int) , 这从 C 语言的角度算不上错误,因为 Derived* 的本质就
是 void* , 所以编译运行都没有问题。但是,从面向对象的角度,这里可以说是
一种语义上的错误!因为 pAdd 必须可以指向父类中定义的 Add 函数版本,也可
以指向子类中定义的 Add 函数版本,所以说用 Derived* 作为第一个参数表示实
际对象并不合适,应该直接使用 void* 。

 

  有了类型后就可以定义实际的虚函数表了,在 C 语言中用具有文件作用域
的全局变量表示实际的虚函数表是最合适的,因此有了下面的代码:

// 父类对象使用的虚函数表

static struct VTable g_Demo_vtbl =
{
  Demo_Virtual_Add
};
// 子类对象使用的虚函数表
static struct VTable g_Derived_vtbl =
{
  Derived_Virtual_Add
};

  每个对象中都拥有一个指向虚函数表的指针,而所有父类对象都指向
g_Demo_vtbl,所以所有子类对象都指向 g_Derived_vtbl。当一切就绪后,实际
调用虚函数的过程就是通过虚函数表中的对应指针来完成的。

 

posted on 2018-09-03 21:52  周伯通789  阅读(208)  评论(0编辑  收藏  举报