C語言也有封裝,繼承和多態(tài)你知道嗎
我們知道封裝、繼承、多態(tài)是面向?qū)ο蟮娜筇匦?,我們也知道C語言是面向過程的語言,那么可不可以在面向過程的語言中用面向?qū)ο蟮乃枷刖幊棠亍,F(xiàn)在我們就一起看看用C語言如何實現(xiàn)封裝、繼承、多態(tài)。
封裝
所謂封裝就是把實現(xiàn)的細節(jié)隱藏起來,外部只能通過相關(guān)的函數(shù)對一個類進行操作,一呢是方便代碼的復(fù)用,二也可以有效的保證代碼的安全性。那么我們看看Redis源碼中對于雙向鏈表的一個設(shè)計和實現(xiàn),是不是就是傳說中的封裝呢?
typedef struct listNode { struct listNode* prev; struct listNode* next; void* value; } listNode; list* listCreate() { struct list* list; list = malloc(sizeof(struct list)); if (list == NULL) return NULL; list->head = list->tail = NULL; list->len = 0; return list; }
繼承
繼承也是為了代碼的重用設(shè)計的,比如多個子類都有一些共同的屬性和方法,那么就可以將這些共同點抽象成一個父類,讓子類去繼承他,子類也就擁有了父類的特性,更好的實現(xiàn)了代碼的重用性。但是繼承也有很多缺點,比如:
1.java中不能多繼承
2.如果繼承了一個類,那么就繼承了這個類的所有public的屬性和方法,即使你不想繼承
3.如果有一天父類的邏輯做了修改,那么子類的邏輯也被迫做了修改
基于這些原因呢,很多時候是不建議使用繼承的,而是優(yōu)先用組合的方式來達到代碼的復(fù)用。在Go語言中也沒有extends關(guān)鍵字,也是通過組合實現(xiàn)代碼的復(fù)用。那么在C語言中,雖然沒有繼承,但是我們可以組合啊,實現(xiàn)的效果是大同小異的。例如:
struct Shape { int area; Shape* crateShape(); }; struct Rectangle { struct Shape shape; int width; int height; }; struct Square { struct Shape shape; int side; };
多態(tài)
函數(shù)的指針
這里要回顧一下C語言基礎(chǔ)語法了,先來看看C語言中關(guān)于函數(shù)的指針。如果我們在C語言中定義了一個函數(shù),那么在編譯的時候會把函數(shù)的源代碼編譯成可執(zhí)行的的指令,然后分配一塊內(nèi)存去存放。這段空間的的地址就是函數(shù)的入口地址,每次調(diào)用的時候會從該地址入口開始執(zhí)行,函數(shù)名就代表了這個地址,因此函數(shù)名就是函數(shù)的指針。
那么我們就可以定義一個用來指向函數(shù)的指針變量,用來存放某一函數(shù)的入口地址。例如:int (* add) (int, int)
。這行代碼中第一個int
表示的是返回值類型;(* add)
表示add
是一個指針變量,括號不能省略;后面的(int, int)
表示參數(shù)類型是兩個int類型,括號不能省略,括號表示指針變量不是指向其他類型的,而是函數(shù)類型的。之前對函數(shù)的調(diào)用都是通過函數(shù)名,那么現(xiàn)在有了指針變量,我們也可以通過指針變量來對函數(shù)進行調(diào)用。
int main() { // 通過函數(shù)名調(diào)用 max(9, 2); // 通過函數(shù)的指針變量調(diào)用 int (*p)(int, int); // 將函數(shù)max的入口地址賦值給指針變量p p = max(); (*p)(a, b) } int add(int x, int y) { return x + y; }
通過函數(shù)的指針實現(xiàn)多態(tài)
我們看下面代碼,ShapeVtbl
這個結(jié)構(gòu)體中定義了一個計算面積的函數(shù),沒有實現(xiàn),我們叫做虛函數(shù)表。Shape
這個結(jié)構(gòu)體中定義了兩個屬性,一個vtbl
,一個area
。
struct Shape { struct ShapeVtbl *vtbl; int area; }; struct ShapeVtbl { float (*area)(struct Shape* self); }; struct Rectangle { struct Shape shape; int width; int height; }; struct Square { struct Shape shape; int side; };
這里我們分別定義了計算矩形面積的方法rectangle_area
和計算正方形面積的方法square_area
。也初始化了兩個ShapeVtbl
,讓他們的函數(shù)指針分別指向不同的函數(shù)入口。那么在實際運行的時候代碼就會根據(jù)我們的選擇調(diào)用不同的函數(shù),呈現(xiàn)出多態(tài)的效果。
// 計算矩形面積的方法 float rectangle_area(struct Shape *self) { struct Rectangle *r = (struct Rectangle *)self; self->area = r->width * r->height; return self->area; } // 計算正方形面積的方法 float square_area(struct Shape *self) { struct Square *r = (struct Square *)self; self->area = r->side * r->side; return self->area; } // 初始化了兩個ShapeVtbl,讓area函數(shù)分別指向了rectangle_area、square_area struct ShapeVtbl vtbl1 = { rectangle_area, }; struct ShapeVtbl vtbl2 = { square_area, }; // 計算面積的方法,這里的area函數(shù)的邏輯是在運行時期動態(tài)綁定的,也就是有self中函數(shù)指針指向的實際函數(shù)決定的 float shape_area(struct Shape *self) { struct ShapeVtbl *v = self->vtbl; return v->area(self); } struct Square* createSquare() { struct Square *s = malloc(sizeof(struct Square)); s->side = 5; s->shape.vtbl = &vtbl2; } int main() { struct Square* s = createSquare(); printf("area => %f\n", shape_area((struct Shape *)s)); }
更多可以參考圖例:
這樣的設(shè)計在redis源碼中有很多應(yīng)用,比如redis中的字典,dict
結(jié)構(gòu)體定義了字典的基本屬性以及屬于dict
的一些特定函數(shù),代碼在dict.h中。
/* * 字典類型特定函數(shù),定義了計算哈希值、復(fù)制鍵、比較鍵等函數(shù) */ typedef struct dictType { unsigned int (*hashFunction)(const void *key); void *(*keyDup)(void *privdata, const void *key); void *(*valDup)(void *privdata, const void *obj); int (*keyCompare)(void *privdata, const void *key1, const void *key2); void (*keyDestructor)(void *privdata, void *key); void (*valDestructor)(void *privdata, void *obj); } dictType; /* * 字典 */ typedef struct dict { // 類型特定函數(shù) dictType *type; void *privdata; dictht ht[2]; int rehashidx; int iterators; } dict;
而在redis中,dict
的應(yīng)用比較多,鍵的類型也可能有sds
、redisObject
等多種類型,他們的鍵比較函數(shù),hash函數(shù)等都是不同的,因此有了下面的代碼,分別定義了適應(yīng)于各種鍵的對比、hash等函數(shù),并封裝在了不同的dictType
,代碼在redis.c中。那么在實際應(yīng)用中,只需要為不同的類型選擇不同的dictType
即可。
dictType clusterNodesBlackListDictType = { dictSdsCaseHash, /* hash function */ NULL, /* key dup */ NULL, /* val dup */ dictSdsKeyCaseCompare, /* key compare */ dictSdsDestructor, /* key destructor */ NULL /* val destructor */ }; /* Migrate cache dict type. */ dictType migrateCacheDictType = { dictSdsHash, /* hash function */ NULL, /* key dup */ NULL, /* val dup */ dictSdsKeyCompare, /* key compare */ dictSdsDestructor, /* key destructor */ NULL /* val destructor */ }; /* Replication cached script dict (server.repl_scriptcache_dict). * Keys are sds SHA1 strings, while values are not used at all in the current * implementation. */ dictType replScriptCacheDictType = { dictSdsCaseHash, /* hash function */ NULL, /* key dup */ NULL, /* val dup */ dictSdsKeyCaseCompare, /* key compare */ dictSdsDestructor, /* key destructor */ NULL /* val destructor */ }; int dictSdsKeyCompare(void *privdata, const void *key1, const void *key2) { int l1,l2; DICT_NOTUSED(privdata); l1 = sdslen((sds)key1); l2 = sdslen((sds)key2); if (l1 != l2) return 0; return memcmp(key1, key2, l1) == 0; }
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
C++ xxx_cast實現(xiàn)轉(zhuǎn)換代碼實例解析
這篇文章主要介紹了C++xxx_cast轉(zhuǎn)換代碼實例解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-07-07QT使用QComBox和QLineEdit實現(xiàn)模糊查詢功能
模糊查詢是指根據(jù)用戶輸入的文本,在下拉框的選項中進行模糊匹配,并動態(tài)地顯示匹配的選項,本文將使用QComBox和QLineEdit實現(xiàn)模糊查詢功能,需要的可以參考下2023-11-11VS報錯C6011的問題:取消對NULL指針的引用(解決方法)
這篇文章主要介紹了VS報錯C6011的問題:取消對NULL指針的引用(解決方法),C6011:取消對NULL指針的引用,發(fā)現(xiàn)是沒有進行空指針的判斷,解決方案跟隨小編一起看看吧2024-01-01C++中4種強制類型轉(zhuǎn)換的區(qū)別總結(jié)
C++風(fēng)格的類型轉(zhuǎn)換提供了4種類型轉(zhuǎn)換操作符來應(yīng)對不同場合的應(yīng)用。下面這篇文章主要給大家介紹了C++中4種強制類型轉(zhuǎn)換的區(qū)別,有需要的朋友們可以參考借鑒,下面來一起看看吧。2016-12-12