上次說到,用 C 來實作繼承及多形時,界面不統一,並且由於冗長的原故,反而變得難以閱讀及維護。這樣雖然可以用 OO 的概念來開發,但是如果代碼變成如此難以使用反而是違背了初衷呢。
上次最後的例子可以發現,程式在宣告記憶體時,第一個 attribute 和 class 的位置是相同的。應用這點,我們可以大幅簡化使用方式,並且讓代碼清晰可用。
首先是界面,
最後,
執行,
另外這邊的實作主要是參照了[4],ex19 詳細說明了使用方式,並用這個簡單的編寫一個小遊戲,非常精彩。
上次最後的例子可以發現,程式在宣告記憶體時,第一個 attribute 和 class 的位置是相同的。應用這點,我們可以大幅簡化使用方式,並且讓代碼清晰可用。
1. 定一通用的 class 界面
定義一個通用的界面,可以幫助我們大幅度減少代碼的重寫。在這邊主要是定義了 constructor 以及 destructor
// object.h
#pragma once
typedef struct Object Object;
struct Object {
int (*init)(void * self);
void (*free)(void * self);
};
void * Object_new(size_t size, Object Base);
#define _(N) base.N
#define NEW(T) Object_new(sizeof(T), T##Init)
#define DEL(N) ((Object*)N)->free(N)
這裡用到一些有趣的巨集(Macro),這裡一一解釋,
#progma once
這是為了避免重複編譯,也可以用#ifndef OBJECT_H
#define OBJECT_H
...
#endif
第二種方法在各編譯器都是通用的,適合移植。但是在編寫大型程式時可能碰上 Macro 名相同的麻煩。第一種就沒這個問題,而且現在大多編譯器也支持這種格式。
#define _(N) base.N
#define NEW(T) Object_new(sizeof(T), T##Init)
#define DEL(N) ((Object*)N)->free(N)
最後的 macro 主要是為了方便使用而設,待會可以見到。由 NEW 可以看見,我們之後的記憶體宣告,其實都是使用 Object_new, 但是大小由 sizeof() 決定。_(N) 就像一個新的操作符,如此我們便不需要重複的寫 base.N,也讓界面更清晰、簡單。2. 通用界面的實作
// object.c
#include <stdlib.h>
#include "object.h"
static void Object_free_impl(void * self){
Object * obj = self;
free(obj);
}
static int Object_init_impl(void * self){
// do nothing
return 1;
}
void * Object_new (size_t size, Object base){
if(!base.init) {base.init = Object_init_impl;}
if(!base.free) {base.free = Object_free_impl;}
Object * new_obj = calloc(1,size);
* new_obj = base;
if(!new_obj->init(new_obj)) {
new_obj->free(new_obj);
return NULL;
}
else{
return new_obj;
}
}
3. 使用方式
同樣的,我們以多邊形、長方形、三角形為例。首先是界面,
// polygon.h
#pragma once
#include "object.h"
// constructor
Object PolygonInit;
typedef struct Polygon Polygon;
struct Polygon {
// base class
Object base;
// variables
int width, height;
// methods
void (*set)(void * self,int w, int h);
int (*area)(void * self);
};
Object RectInit;
typedef struct Rect Rect;
struct Rect{
Polygon base;
};
Object TrigInit;
typedef struct Trig Trig;
struct Trig{
Polygon base;
};
要注意的是,這裡的 constructor 是一個 Object structure,而其中初始化的定義我們可以使用 static function 來隱藏。
// polygon.c
#include <stdlib.h>
#include "object.h"
#include "polygon.h"
static void Polygon_set_impl(void * self,int w,int h){
Polygon * polygon = self;
polygon->width = w;
polygon->height = h;
}
static int Polygon_area_impl(void * self){
return 0;
}
static int Polygon_init(void * self){
Polygon * polygon = self;
polygon->width = 0;
polygon->height = 0;
polygon->set = Polygon_set_impl;
polygon->area = Polygon_area_impl;
return 1;
}
Object PolygonInit = {
.init = Polygon_init
};
static int Rect_area_impl(void * self){
Rect * rect = self;
return rect->_(width) * rect->_(height);
}
static int Rect_init(void * self){
Rect * rect = self;
// initialize base class
Polygon_init(rect);
rect->_(area) = Rect_area_impl;
return 1;
}
Object RectInit = {
.init = Rect_init
};
static int Trig_area_impl(void * self){
Trig * trig = self;
return trig->_(width) * trig->_(height) / 2;
}
static int Trig_init(void * self){
Trig * trig = self;
// initialize base class
Polygon_init(trig);
trig->_(area) = Trig_area_impl;
return 1;
}
Object TrigInit = {
.init = Trig_init
};
Polygon_init() 與之前相同,我們初始化了各個變數以及方法,但是 Rect 及 Trig 只需要再次的呼叫 Polygon_init(), 並初始化自己的方法即可。最後,
// main.c
#include <stdio.h>
#include "polygon.h"
int main(void){
Rect * rect = NEW(Rect);
Trig * trig = NEW(Trig);
rect->_(set)(rect,4,5);
trig->_(set)(trig,4,5);
printf("Area of Rect: %d\n",rect->_(area)(rect));
printf("Area of Trig: %d\n",trig->_(area)(trig));
DEL(rect);
DEL(trig);
return 0;
}
可以見到,使用上非常簡潔方便,而且非常清晰。如此在設計及開發都變得更容易了。執行,
$ gcc -Wall -c object.c
$ gcc -Wall -c polygon.c
$ gcc -Wall polygon.o object.o main.c -o polygon.run
$ ./polygon.run
Area of Rect: 20
Area of Trig: 10
後記
這邊的介紹是想說明,OO 是一種編程的概念,而非一定需要什麼特定的語言才可以寫出來。但是呢,OO 的語言提供了更多的語法糖(syntax sugar),而且在許多問題都有良好的解決辦法,代碼也更加規範。但有時我們很難知道這些語法糖到底做了什麼。我還是喜歡那句話,「C 是給知道自己幹什麼的人用的語言」,用 C 實作非常的有意思,也讓我更加理解何謂 OO 。另外這邊的實作主要是參照了[4],ex19 詳細說明了使用方式,並用這個簡單的編寫一個小遊戲,非常精彩。
reference:
- 我所偏爱的 C 语言面向对象编程范式
- C中的继承和多态
- 你所不知道的 C 語言:物件導向程式設計篇
- Learn C the hard way ex. 19 A Simple Object System
沒有留言:
張貼留言
歡迎發表意見