C語言也能面向?qū)ο缶幊蹋。?/h2>

引言

面向?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

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容