2013年6月2日 星期日

如何在 C 語言中模擬繼承與多型

    在 C 語言中,並沒有繼承與多形。然而,我們可以利用一些方法來模擬繼承與多型。在開始講之前,我們先來看看電腦如何取得變數的值。



    在電腦中,要取得變數的值,我們只需要知道變數的位址與變數的資料型態即可。如果我要取出某個結構屬性的值,我們只要知道該結構所在位址,以及該屬性與起始位址的位移,再加上該屬性的資料型態即可。如下圖所示。



    有了這個觀念後,要在 C 語言模擬繼承,基本上,只要讓那兩個型態有相同的屬性位移即可。舉個簡單的例子,如果我們要建立一個 Father 結構,讓 child 和  child2 繼承至 Father。首先,我們會定義一個巨集,裡面放要被繼承的屬性或方法。然後讓巨集出現在 child1 和 child2  開頭的地方。如下面程式碼。


#include <stdio>
typedef void (*myfunc)();
#define Father_HEADER \
  myfunc init;

struct father{
    Father_HEADER
};
struct child1{
    Father_HEADER
    myfunc custom1;
};
struct child2{
    Father_HEADER
    myfunc custom2;
};

    向上面的程式碼,當我們要在 father 新增欄位,讓繼承他的結構也一併新增,只要把該欄位新增在 Father_HEADER 巨集即可。而不必到處去加欄位。

    我在建立一個 function 叫做  call_init。它會呼叫該物件相對應的 init  物件。

void call_init(struct father *obj){
    obj->init();
}

    接下來,只要我們只要初始化各個結構所對應的 init 函數後,你會發現當我們傳進不同的東西到 call_init ,他會執行該物件所對應的函數。這不就很像是多型。


int main(){
    struct father f_obj = {father_init};
    struct child1 c1_obj = {child1_init, 0};
    struct child2 c2_obj = {child2_init, 0};

    call_init(&f_obj);
    call_init((struct father*) &c1_obj);
    call_init((struct father*) &c2_obj);
    return 0;
}

    在實務上,存放資料的屬性並不會和存放函數的屬性放在一起,因為對於相同的物件,他們所對應的動作也一樣。因此,他們會把那些動作全部封裝到一個結構裡。以 CPython 的實作而言,他們把該物件能做的動作都放到 ob_type 裡,若執行該物件的函數時,則使用類似下面的方法


#define Py_TYPE(ob)             (((PyObject*)(ob))->ob_type)

PyObject *v;
Py_TYPE(v)->tp_free((PyObject *)v);

     那些函數都有一個特性,第一個參數是傳入要操作的物件。畢竟,你不傳進去,那些函數不知道要對誰操作。

沒有留言:

張貼留言