【C++】 51_C++對象模型分析 (下)

繼承對象模型

  • 在 C++ 編譯器的內部類能夠理解爲結構體
  • 子類是由父類成員疊加子類新成員獲得的
class Dervied : public Demo
{
    int mk;
};

clipboard.png

編程實驗: 繼承對象模型初探

#include <iostream>

using namespace std;

class Demo
{
protected:
    int mi;
    int mj;
};

class Derived : public Demo
{
private:
    int mk;
public:
    Derived(int i, int j, int k)
    {
        mi = i;
        mj = j;
        mk = k;
    }
    
    void print()
    {
        cout << "mi = " << mi << ", "
             << "mj = " << mj << ", "
             << "mk = " << mk << endl;
    }
};

struct Test
{
    int mi;
    int mj;
    int mk;
};

int main()
{
    cout << "sizeof(Demo) = " << sizeof(Demo) << endl;
    cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
    
    Derived d(1, 2, 3);
    Test* p = reinterpret_cast<Test*>(&d);    // 注意這裏!
    
    cout << "Befor changeing ..." << endl;
    
    d.print();
    
    p->mi = 10;                               // 注意這裏! 
    p->mj = 20;
    p->mk = 30;
    
    cout << "After changeing ..." << endl;
    
    d.print();
    
    return 0;
}
輸出:
sizeof(Demo) = 8
sizeof(Derived) = 12
Befor changeing ...
mi = 1, mj = 2, mk = 3
After changeing ...
mi = 10, mj = 20, mk = 30

結論:
子類是由父類成員疊加子類新成員獲得的

多態對象模型

  • C++ 多態的實現原理ios

    • 當類中聲明虛函數時,編譯器會在類中生成一個虛函數表
    • 虛函數表是一個存儲成員函數地址的數據結構
    • 虛函數表由編譯器自動生成與維護
    • virtual 成員函數被編譯器放入虛函數表中
    • 存在虛函數時,每一個對象都有一個指向虛函數表的指針

多態:面向對象理論中的概念,與程序設計語言無關
虛函數:C++ 中實現多態的惟一方式編程


class Demo
{
private:
    int mi;
    int mj;
public:
    virtual int add(int value)
    {
        return mi + mj + value;
    };
};

==>數據結構

clipboard.png

class Derived : public Demo
{
private:
    int mk;
public:
    virtual int add(int value)
    {
        return mk + value;
    }
};

==>函數

clipboard.png


void run(Demo* p, int v)
{
    p->add(v);
}

編譯器確認 add() 是否爲虛函數?spa

  • Yes -> 編譯器在對象 VPTR 所指向的虛函數表中查找 add() 的地址
  • No -> 編譯器能夠直接肯定被調成員函數的地址

clipboard.png

clipboard.png

調用效率: 虛函數 < 普通成員函數設計

編程實驗:多態對象模型初探

#include <iostream>

using namespace std;

class Demo
{
protected:
    int mi;
    int mj;
public:
    virtual void print()
    {
        cout << "mi = " << mi << ", "
             << "mj = " << mj << endl;
    }
};

class Derived : public Demo
{
private:
    int mk;
public:
    Derived(int i, int j, int k)
    {
        mi = i;
        mj = j;
        mk = k;
    }
    
    void print()
    {
        cout << "mi = " << mi << ", "
             << "mj = " << mj << ", "
             << "mk = " << mk << endl;
    }
};

struct Test
{
    void* p;                                                 // 注意這裏!
    int mi;
    int mj;
    int mk;
};

int main()
{
    cout << "sizeof(Demo) = " << sizeof(Demo) << endl;       // 注意這裏!
    cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
    
    Derived d(1, 2, 3);
    Test* p = reinterpret_cast<Test*>(&d);
    
    cout << "Befor changeing ..." << endl;
    
    d.print();
    
    p->mi = 10;
    p->mj = 20;
    p->mk = 30;
    
    cout << "After changeing ..." << endl;
    
    d.print();
    
    return 0;
}
輸出:
sizeof(Demo) = 12
sizeof(Derived) = 16
Befor changeing ...
mi = 1, mj = 2, mk = 3
After changeing ...
mi = 10, mj = 20, mk = 30

虛函數表指針位於對象最開始的 4 個字節處(32位平臺)指針

編程實驗: 多態本質分析

C 寫面向對象、繼承與多態
Demo.hcode

#ifndef _DEMO_H_
#define _DEMO_H_

typedef void Demo;
typedef void Derived;

Demo* Demo_Create(int i,int j);
int Demo_GetI(Demo* pThis);
int Demo_GetJ(Demo* pThis);
int Demo_Add(Demo* pThis, int value);
int Demo_Free(Demo* pThis);

Derived* Derived_Create(int i, int j, int k);
int Derived_GetK(Derived* pThis);
int Derived_Add(Derived* pThis, int value);

#endif

Demo.c對象

#include <malloc.h>
#include "Demo.h"

static int Demo_Virtual_Add(Demo* pThis, int value);
static int Derivate_Virtual_Add(Demo* pThis, int value);

struct VTable                  // 2. 定義虛函數表數據結構
{
    int (*pAdd)(void*, int);   // 3. 虛函數表裏面存儲的是什麼???
};

struct ClassDemo
{
    struct VTable* vptr;        // 1. 定義虛函數表指針  ==> 虛函數表指針指向哪裏???
    int mi;
    int mj;
};

struct ClassDerived
{
    struct ClassDemo d;
    int mk;
};

// 定義 Demo 類虛函數表,並存儲虛函數地址
struct VTable g_Demo_vptr = 
{
    Demo_Virtual_Add
};

// 定義 Derivate 類虛函數表,並存儲虛函數地址
struct VTable g_Derivate_vptr = 
{
    Derivate_Virtual_Add
};

Demo* Demo_Create(int i,int j)
{
    struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo));
    
    if( ret != NULL )
    {
        ret->vptr = &g_Demo_vptr;  // 4. 關聯對象和虛函數表
        ret->mi = i;
        ret->mj = j;
    }
    
    return ret;
}

int Demo_GetI(Demo* pThis)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    
    return obj->mi;
}

int Demo_GetJ(Demo* pThis)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    
    return obj->mj;
}

// 6. 定義虛函數表中指針所指向的具體函數
int Demo_Virtual_Add(Demo* pThis, int value)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    
    return (obj->mi + obj->mj + value);
}

// 5. 分析具體的虛函數
int Demo_Add(Demo* pThis, int value)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    
    return (obj->vptr->pAdd(pThis, value));
}

int Demo_Free(Demo* pThis)
{
    free(pThis);
}

Derived* Derived_Create(int i, int j, int k)
{
    struct ClassDerived* ret = (struct ClassDerived*)malloc(sizeof(struct ClassDerived));
    
    if( ret != NULL )
    {
        ret->d.vptr = &g_Derivate_vptr;
        ret->d.mi = i;
        ret->d.mj = j;
        ret->mk = k;
    }
    
    return ret;
}

int Derived_GetK(Derived* pThis)
{
    struct ClassDerived* obj = (struct ClassDerived*)pThis;

    return obj->mk; 
}

int Derivate_Virtual_Add(Demo* pThis, int value)
{
    struct ClassDerived* obj = (struct ClassDerived*)pThis;

    return obj->mk + value; 
}

int Derived_Add(Derived* pThis, int value)
{
    struct ClassDerived* obj = (struct ClassDerived*)pThis;

    return obj->d.vptr->pAdd(pThis, value); 
}

main.c繼承

#include <stdio.h>
#include "Demo.h"

void run(Demo* p, int v)
{
    int r = Demo_Add(p, v);
    
    printf("r = %d\n", r);
}

int main()
{
    Demo* pb = Demo_Create(1, 2);
    Derived* pd = Derived_Create(1, 22, 333);
    
    printf("pb->add(3) = %d\n", Demo_Add(pb, 3));
    printf("pd->add(3) = %d\n", Derived_Add(pd, 3));
    
    run(pb, 3);
    run(pd, 3);
    
    Demo_Free(pb);
    Demo_Free(pd);
    
    return 0;
}
輸出:
pb->add(3) = 6
pd->add(3) = 336
r = 6
r = 336

深度分析:

面向對象程序最關鍵的地方在於必須可以表現三大特性:封裝,繼承,多態!封裝指的是類中的敏感數據在外界是不能訪問的;封裝指的是類中的敏感數據在外界是不能訪問的;多態指的是相同的調用語句能夠產生不一樣的調用結果。 所以,若是但願用 C 語言完成面向對象的程序,那麼確定的,必須實現這三個特性;不然,最多隻算得上基於對象的程序(程序中可以看到對象的影子,可是不徹底具有面向對象的 3 大特性)。
++++++++++++++++
課程中經過 void* 指針保證具體的結構體成員是不能在外界被訪問的,以此模擬 C++ 中 private 和 protected。 所以,在頭文件中定義了以下的語句:

typedef void Demo;
typedef void Derived;

Demo 和 Derived 的本質依舊是 void, 因此,用 Demo 指針和 Derived 指針指向具體的對象時,沒法訪問對象中的成員變量,這樣就達到了「外界沒法訪問類中私有成員」 的封裝效果!
++++++++++++++++
繼承的本質是父類成員與子類成員的疊加,因此在用 C 語言寫面向對象程序的時候,能夠直接考慮結構體成員的疊加便可。課程中的實現直接將 structClassDemo d 做爲 struct ClassDerived 的第一個成員,以此表現兩個自定義數據類型間的繼承關係。 由於 struct ClassDerived 變量的實際內存分佈就是由struct ClassDemo 的成員以及 struct ClassDerived 中新定義的成員組成的,這樣就直接實現了繼承的本質,因此說 struct ClassDerived 繼承自 structClassDemo。
++++++++++++++++
下一步要實現的就是多態了!多態在 C++ 中的實現本質是經過虛函數表完成的,而虛函數表是編譯器自主產生和維護的數據結構。所以,接下來要解決的問題就是如何在 C 語言中自定義虛函數表?課程中認爲經過結構體變量模擬 C++中的虛函數表是比較理想的一種選擇,因此有了下面的代碼:

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

有了類型後就能夠定義實際的虛函數表了,在 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。當一切就緒後,實際調用虛函數的過程就是經過虛函數表中的對應指針來完成的。

小結

  • 繼承的本質就是父子間成員變量的疊加
  • C++ 中的多態是經過虛函數表實現的
  • 虛函數表是由編譯器自動生成與維護的
  • 虛函數的調用效率低於普通函數

以上內容參考狄泰軟件學院系列課程,請你們保護原創!

相關文章
相關標籤/搜索