# 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)