• 第51课 C++对象模型分析(下)


    继承对象模型:

    继承对象模型初探:

    结构体与类等价的示例:

    可以看到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
    ", 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
    ", Demo_Add(pb, 3));
    17     printf("pd->add(3) = %d
    ", 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。当一切就绪后,实际
    调用虚函数的过程就是通过虚函数表中的对应指针来完成的。

  • 相关阅读:
    scala之伴生对象的继承
    scala之伴生对象说明
    “Failed to install the following Android SDK packages as some licences have not been accepted” 错误
    PATH 环境变量重复问题解决
    Ubuntu 18.04 配置java环境
    JDBC的基本使用2
    DCL的基本语法(授权)
    ZJNU 1374
    ZJNU 2184
    ZJNU 1334
  • 原文地址:https://www.cnblogs.com/wanmeishenghuo/p/9581270.html
Copyright © 2020-2023  润新知