# C 之物件導向 > 著重於想法,而非語法 ## 前言 有些人會把語言分門別類,物件導向的歸一邊,而非物件導向的歸一邊,像 java 是基於 class base,而 python, javascript 是基於 prototype,C++ 則是 Meta programming(因為 [`template`](https://en.wikipedia.org/wiki/Template_(C%2B%2B)#Generic_programming_features_in_other_languages))。並非沒有了語法支援便無法體現物件導向的精神,萬語皆有 OOP 。 ## 實作 在 C 裡實作一個**偽**物件導向的語法,讓 C 看起來具備物件導向的繼承功能(實際上就是 struct 裡塞 struct) #### header file : `psdooc.h` ```cpp #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,如下: ```cpp 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 這麼方便,如果物件方法要使用物件自己本身,還是得先將自己當成參數傳入。 如果不想每次都要傳入物件本身的話,可以使用巨集**包裝**起來,如下 ```cpp #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](https://github.com/sunpaq/monkc) (monkey C) #### example : `ooc.c` 使用 `psdooc.h` 模仿物件導向 ```graphviz digraph { node[shape=box] Object->Vehicle Vehicle->Car Vehicle->Truck } ``` ```cpp #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; } ``` ## 結語 > 物件導向是設計上一種演化的方向,有心或沒有心,指的是開發者有沒有持續地檢討設計,以及當時的需求下是否適合朝該方向演化,而不是一味地套用封裝、繼承的語法或術語。有心的話,就算不想著物件導向,演化的方向後來自然地朝向物件導向,也只是剛好而已! >> 摘自:[無拘的物件導向](https://www.ithome.com.tw/voice/124477) 經典的 [GObject](https://zh.wikipedia.org/zh-tw/GObject) 即是這種演化方向下的產物。在程式語言的演化中,不管是用哪些程式語言進行開發,不妨可以想想,究竟被限制的是語法還是自己的思考邏輯。 ## 參考 1. [你所不知道的 C 語言:物件導向程式設計篇](https://hackmd.io/@sysprog/c-oop) 2. [無拘的物件導向](https://www.ithome.com.tw/voice/124477)