想用C來實現OOP,關鍵在於結構體。struct和OOP中的class最大區別爲默認的繼承訪問權限:struct是public的,大家都能看到,class是private的,只有指定的對象看得到。
碼農翻身裏面有一篇文章講到過用c語言來實現OOP,今天參照着它擼了一下oop的三大概念:封裝、繼承、多態。
1 封裝:意思就是把信息隱藏起來。
舉個例子,先創建一個shape結構體,然後實現create和init等方法,之後將源代碼封裝成庫。這樣一來,外部程序只能看見頭文件的接口原型,而看不到其內部結構。
shape.h
//聲明結構體
struct Shape;
//api原型
struct Shape *Shape_create(int x,int y);
void Shape_init(struct Shape *self, int x,int y);
void Shape_move(struct Shape *self, int x,int y);
float Shape_area(struct Shape *self);
...
shape.c
struct Shape{
int x;
int y;
};
struct Shape *Shape_create(int x, int y){
struct Shape *s = malloc(sizeof(struct Shape));
s->x= x;
s->y= y;
return s;
}
void Shape_move(struct Shape *self, int x,int y)
{
self->x= x;
self->y= y;
}
float Shape_area(struct Shape *self){
return (*self->vptr->area)(self); //vptr成員後面會新增進去
}
main.c中調用api
int main(int argc, *argv[]){
...
struct Shape *s = Shape_create(0,0);
Shape_move(s,1,2);
...
}
雖然結構體成員和create、move等方法是分開寫的,看起來沒class那麼融合,可也算是實現了簡單的封裝。
2 繼承
先看下面代碼:
//Rectangle子類
struct Rectangle{
struct Shape base;
int length;
int width;
};
struct Rectangle *Rectangle_create(int x,int y,int l, int w){
struct Rectangle *r = malloc(sizeof(struct Rectangle)); //創建對象
Shape_init((struct Shape *)r,x,y); //繼承了Shape基類的成員函數:shape_init
r->length = l;
r->width = w;
return r;
}
...
int main(){
struct Rectangle *r = Rectangle_create(1,2,30,40);
Shape_move((struct Shape*)r,10,20);
...
}
上述代碼創建了另一個結構體Rectangle。因其包含了Shape,所以稱之爲子類。其在內存中是這樣的:
繼承關係如下:
有兩個地方需要注意:
- 在繼承Shape_init這個api時,傳進去的對象爲 r,此時需要類型轉換爲Shape基類。
- 在Rectangle子類中Shape基類的位置需放在最前。下面舉個例子說明一下:
struct rectangle{
int length;
int width;
struct shape base; //此處放在最後,會有問題
};
...
struct rectangle *r = malloc(sizeof(struct rectangle));
struct shape *tmp = (struct shape *)r; //強制類型轉換
tmp->x=110;
上述代碼中 將Shape類放在了最後。本來是將110賦值給tmp 的 x成員,實際卻是給了length成員,調試結果如下:
(r和tmp的地址一樣,只是裏頭的數據代表的類型不同)
3 多態
多態最難搞,關鍵在於理解函數指針的使用。先上代碼:
//新建虛函數表
struct shapeVtbl{
float (*area)(struct shape *self);
void (*draw)(struct shape *self);
}
//將虛函數表添加到Shape基類中
struct shape{
struct shapevtbl *vptr;
int x;
int y;
};
新結構體shapeVtbl 包含兩個函數指針,目的是爲了獲取對象的面積及畫圖,我們稱其爲虛函數表。之後不管創建的子類是Rectangle 還是其它的,只要是繼承自Shape基類,都會有個虛函數表。
下面是Rectangle 和Square對象。前者的函數指針area指向的是自家的 Rectangle_area();而Square對象的函數指針area指向的是Square_area()。
開頭部分的shape.h中有個api爲Shape_area(...),在子類繼承之後,調用它就可以直接指向自家的api地址。如下:
float Area;
...
struct Rectangle *r = Rectangle_create(1,2,30,40);
Area = Shape_area((struct Shape*)r);
注意:上圖中的虛函數表需要你手動將自家的函數入口地址填進去,比如 &Rectangle_area 和 &Rectangle_draw,而C++則可以自動完成。