前言
有些人會把語言分門別類,物件導向的歸一邊,而非物件導向的歸一邊,像 java 是基於 class base,而 python, javascript 是基於 prototype,C++ 則是 Meta programming(因為 Template)。 並非沒有了語法支援便無法體現物件導向的精神,萬語皆有 OOP 。
實作
在 C 裡實作偽物件導向的語法,讓 C 看起來具備物件導向的繼承功能(實際上就是 struct 裡塞 struct)
header file : psdooc.h
#define noargs void *_noargs #define nulargs 0 typedef struct __NullClass {} NullClass; #define class(class_name, cls_inherit,...) \ typedef struct __##class_name { \ cls_inherit super; \ __VA_ARGS__ \ } class_name; #define super(self) (self->super) // class method template for function declare and define #define method_tmpl(class_name, ret_type, method_name, ...) \ ret_type class_name##_##method_name(class_name* self, __VA_ARGS__) // class method call #define method_call(obj, class_name, method_name, ...) \ class_name##_##method_name(obj, __VA_ARGS__) |
分析一下 header file。
NullClass : 空的 struct,因為我寫的巨集 class 必須要有繼承的類別(我就菜)。
巨集 noargs 跟 nulargs 跟上述原因差不多,noargs 用於 method_tmpl,而 nulargs 用於 method_call
class : 類別
- class_name : 類別名稱
- cls_inherit : 要繼承的類別名稱
- ... : 變數成員(使用不定數量參數寫法)
super : 用於指向父類別,也可以直接 self->super ,看個人喜好。
method_tmpl : 聲明或定義類別方法
- class_name : 類別名稱
- ret_type : 回傳型態
- method_name : 方法名稱
實際上,巨集 method_tmpl 展開後就是一個 function
method_call : 呼叫由 method_tmpl 定義的類別方法
- obj : 要傳入的物件
- class_name : 要呼叫方法的目標類別名稱
- method_name : 方法名稱
- ... : 參數(使用不定數量參數寫法)。
老實說,用 method_tmpl 跟 method_call 去重新包裝 function 讓他看起來像 method 有點多此一舉,如過要看起來更像物件的話可以在 struct 裡面塞 function pointer,如下:
typedef struct Object Object; typedef int (*func_t)(Object *); struct Object { int member_a; int member_b; func_t add, sub;// function pointer } int add_impl(Object *self) { return self->member_a + self->member_b; } int sub_impl(Object *self) { return self->member_a - self->member_b; } void func_binding(Object *obj) { obj->add = add_impl; obj->sub = sun_impl; } Object *obj ObjectInit() { Object *obj = (Object*) malloc(sizeof(Object)); obj->member_a = 1; obj->member_b = 10; func_binding(obj); return obj; } // entry point int main() { Object *obj = ObjectInit(); int res = obj.add(obj); free(obj); } |
這樣的寫法必須得在程式的執行階段動態將 Object 物件內的 function pointer 指向對應參數的 function,由於 C 沒有支援物件導向的語法,因此沒有像 java 有 this 這麼方便,如果物件方法要使用物件自己本身,還是得先將自己當成參數傳入。
如果不想每次都要傳入物件本身的話,可以使用巨集包裝起來,如下
如果不想每次都要傳入物件本身的話,可以使用巨集包裝起來,如下
#define callm(obj, func_name, ...) \ obj->func_name(obj, __VA_ARGS__) callm(car, gassing, 100); // calling method gassing of car object |
使用 function pointer 的好處是可以指向同類型,不同實做的 function,大致程度上可以達成 function overload(c++ 也是用類似的手法達成)
> 有高手實做了 C 的物件導向,想當然是一大堆看不懂的巨集 : monkc (monkey C)
example : ooc.c
使用 psdooc.h 模仿物件導向
#include "psdooc.h" #include <stdio.h> // class Object class(Object, NullClass, int ref; ) method_tmpl(Object, // method of class Object Object *, ref, noargs) { return NULL; } method_tmpl(Object, // method of class Object Object *, unref, noargs) { return NULL; } // class Vehicle class(Vehicle, Object, ) method_tmpl(Vehicle, // method of class Vehicle void, start, noargs) { if (self) printf("%x derived from %x\n", self, super(self)); } // class Car class(Car, Vehicle, int useless_a; ) // class Truck class(Truck, Vehicle, int useless_a; int useless_b; ) // main int main() { Car car; Truck truck; method_call((Vehicle *) &car, Vehicle, start, nulargs); method_call((Vehicle *) &truck, Vehicle, start, nulargs); return 0; } |
結語
> 物件導向是設計上一種演化的方向,有心或沒有心,指的是開發者有沒有持續地檢討設計,以及當時的需求下是否適合朝該方向演化,而不是一味地套用封裝、繼承的語法或術語。有心的話,就算不想著物件導向,演化的方向後來自然地朝向物件導向,也只是剛好而已!
>> 摘自:無拘的物件導向
經典的 GObject 即是這種演化方向下的產物。在程式語言的演化中,不管是用哪些程式語言進行開發,不妨可以想想,究竟被限制的是語法還是自己的思考邏輯。