在電腦中,要取得變數的值,我們只需要知道變數的位址與變數的資料型態即可。如果我要取出某個結構屬性的值,我們只要知道該結構所在位址,以及該屬性與起始位址的位移,再加上該屬性的資料型態即可。如下圖所示。
有了這個觀念後,要在 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);
那些函數都有一個特性,第一個參數是傳入要操作的物件。畢竟,你不傳進去,那些函數不知道要對誰操作。
沒有留言:
張貼留言