引言
面向?qū)ο笫且粋€老生常談的話題,其基本思想為封裝、繼承、多態(tài)。
最近在學習 Linux 系統(tǒng)源碼時,發(fā)現(xiàn)雖然系統(tǒng)是使用面向過程的 C 語言編寫,但是還是可以體現(xiàn)出面向?qū)ο蟮乃枷?,如文件系統(tǒng)中的文件操作 file_operation 結(jié)構(gòu),每種 inode 都有其對應的操作結(jié)構(gòu),這正是面向?qū)ο笾卸鄳B(tài)的體現(xiàn)。
所以,經(jīng)過小小的研究,我使用 C 語言成功實現(xiàn)了類似面向?qū)ο蠡木幊獭?/p>
這也充分說明,面向?qū)ο蟮木幊趟枷牒头妒?,可以不受語言的拘泥。正所謂金庸筆下的獨孤求敗,「四十歲後,不滯於物,草木竹石均可為劍。自此精修,漸進於無劍勝有劍之境。」
不多廢話,我們一起來看是如何實現(xiàn)的吧。
封裝
封裝也叫作信息隱藏或者數(shù)據(jù)訪問保護。類通過暴露有限的訪問接口,授權外部僅能通過類提供的方式來訪問內(nèi)部信息或者數(shù)據(jù)。
封裝特性存在的意義,一方面是保護數(shù)據(jù)不被隨意修改,提高代碼的可維護性;另一方面是僅暴露有限的必要接口,提高類的易用性。
我們嘗試封裝一個簡單的Point“類”,并且定義相關的 getter/setter 方法。當然 C語言沒有辦法在聲明中隱藏其數(shù)據(jù)結(jié)構(gòu),并且由于沒有訪問控制功能,沒有辦法做到完美的封裝。
//Point.h
typedef struct{
int x;
int y;
}Point;
int Point_getX(Point const p);
int Point_getY(Point const p);
void Point_init(Point *p, int x, int y);
然后我們在 .c 文件中實現(xiàn)前面定義的方法。
//Point.c
#include "Point.h"
int Point_getX(Point const p) {
return p.x;
}
int Point_getY(Point const p) {
return p.y;
}
void Point_init(Point *p, int x, int y) {
p->x = x;
p->y = y;
}
大功告成!
我們來體驗一下剛才封裝的 Point “類”。
創(chuàng)建一個 Point “對象”,使用 setter 設置它的“屬性”,然后通過 getter 將值取出打印出來。
//main.c
#include <stdio.h>
#include "Point.h"
int main(int argc, const char * argv[]) {
Point p;
Point_init(&p, 10, 20);
printf("point x:%d,y:%d\n", Point_getX(p), Point_getY(p));
return 0;
}
控制臺輸出
point x:10,y:20
繼承
繼承是用來表示類之間的 is-a 關系,用于解決代碼復用的問題。
那 C 語言如何實現(xiàn)繼承呢?其實在 C 語言里面實現(xiàn)繼承也非常簡單,只要把基類放到繼承類的數(shù)據(jù)成員中就行了。
我們來實現(xiàn)一個 Square “類”,添加一個邊長的“屬性”。
//Square.h
#include "Point.h"
typedef struct {
Point super;
int sideLen;
}Square;
int Square_getSideLen(Square const s);
void Square_init(Square *s, int x, int y, int sideLen);
//Square.c
#include "Square.h"
int Square_getSideLen(Square const s) {
return s.sideLen;
}
void Square_init(Square *s, int x, int y, int sideLen) {
//Point_init((Point*)s, x, y);
Point_init(&(s->super), x, y);
s->sideLen = sideLen;
}
實現(xiàn)完畢,趕快來體驗一下。
//main.c
int main(int argc, const char * argv[]) {
Square s;
Square_init(&s, 10, 20, 10);
printf("square x:%d,y:%d,sideLen:%d\n", s.super.x, s.super.y, s.sideLen);
return 0;
}
控制臺輸出
square x:10,y:20,sideLen:10
可能會有人對上面的實現(xiàn)有疑問,為什么 //Point_init((Point*)s, x, y); 這句也可以生效呢?那就要從結(jié)構(gòu)體的內(nèi)存布局說起了。
//Point內(nèi)存布局
┏━━━━━━┳━━━━━━┓
┃int x ┃int y ┃
┗━━━━━━┷━━━━━━┛
//Square內(nèi)存布局
┏━━━━━━┳━━━━━━┳━━━━━━━━━━━┓
┃int x ┃int y ┃int sideLen┃
┗━━━━━━┷━━━━━━┷━━━━━━━━━━━┛
Square 的首個成員是 Point,然后是邊長,則其內(nèi)存布局如上。
因為有這樣的內(nèi)存布局,所以你可以很安全的傳一個指向 Square 對象的指針到一個期望傳入 Point 對象的指針的函數(shù)中,就是一個函數(shù)的參數(shù)是 Point *,你可以傳入 Square *,并且這是非常安全的。這樣的話,基類的所有屬性和方法都可以被繼承類繼承!
多態(tài)
多態(tài)是指子類可以替換父類,在實際的代碼運行過程中,調(diào)用子類的方法實現(xiàn)。多態(tài)可以提高代碼的擴展性和復用性,是很多設計模式、設計原則、編程技巧的代碼實現(xiàn)基礎。
我們在 Point 類中,添加方法列表 Operations,然后在初始化時綁定 print 方法。同樣的,要實現(xiàn)多態(tài)的效果,我們需要在 Square 初始化時,替換原來的方法。
//Point.h
struct Operations;
typedef struct{
int x;
int y;
struct Operations *op;
}Point;
struct Operations{
void (*print)(Point*);
};
int Point_getX(Point const p);
int Point_getY(Point const p);
void Point_init(Point *p, int x, int y);
//Point.c
#include "Point.h"
#include <stdio.h>
int Point_getX(Point const p) {
return p.x;
}
int Point_getY(Point const p) {
return p.y;
}
void Point_print(Point *p) {
printf("point x:%d,y:%d\n", Point_getX(*p), Point_getY(*p));
}
void Point_init(Point *p, int x, int y) {
p->x = x;
p->y = y;
static struct Operations op = {Point_print};
p->op = &op;
}
//Square
#include "Square.h"
#include <stdio.h>
int Square_getSideLen(Square const s) {
return s.sideLen;
}
void Square_print(Square *s) {
printf("square x:%d,y:%d,sideLen:%d\n", Point_getX((*s).super), Point_getY((*s).super), Square_getSideLen(*s));
}
void Square_init(Square *s, int x, int y, int sideLen) {
// Point_init((Point*)s, x, y);
Point_init(&(s->super), x, y);
s->sideLen = sideLen;
struct Operations op = {(void (*)(Point*))Square_print};
s->super.op = &op;
}
大功告成!試一下是否真的可以有多態(tài)的效果。
int main(int argc, const char * argv[]) {
Point p;
Point_init(&p, 10, 20);
p.op->print(&p);
Square s;
Square_init(&s, 10, 20, 10);
Point* _s = (Point *)&s;
s.super.op->print(_s);
return 0;
}
控制臺輸出
point x:10,y:20
square x:10,y:20,sideLen:10