網站首頁 編程語言 正文
我們知道封裝、繼承、多態是面向對象的三大特性,我們也知道C語言是面向過程的語言,那么可不可以在面向過程的語言中用面向對象的思想編程呢?,F在我們就一起看看用C語言如何實現封裝、繼承、多態。
封裝
所謂封裝就是把實現的細節隱藏起來,外部只能通過相關的函數對一個類進行操作,一呢是方便代碼的復用,二也可以有效的保證代碼的安全性。那么我們看看Redis源碼中對于雙向鏈表的一個設計和實現,是不是就是傳說中的封裝呢?
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; }
繼承
繼承也是為了代碼的重用設計的,比如多個子類都有一些共同的屬性和方法,那么就可以將這些共同點抽象成一個父類,讓子類去繼承他,子類也就擁有了父類的特性,更好的實現了代碼的重用性。但是繼承也有很多缺點,比如:
1.java中不能多繼承
2.如果繼承了一個類,那么就繼承了這個類的所有public的屬性和方法,即使你不想繼承
3.如果有一天父類的邏輯做了修改,那么子類的邏輯也被迫做了修改
基于這些原因呢,很多時候是不建議使用繼承的,而是優先用組合的方式來達到代碼的復用。在Go語言中也沒有extends關鍵字,也是通過組合實現代碼的復用。那么在C語言中,雖然沒有繼承,但是我們可以組合啊,實現的效果是大同小異的。例如:
struct Shape { int area; Shape* crateShape(); }; struct Rectangle { struct Shape shape; int width; int height; }; struct Square { struct Shape shape; int side; };
多態
函數的指針
這里要回顧一下C語言基礎語法了,先來看看C語言中關于函數的指針。如果我們在C語言中定義了一個函數,那么在編譯的時候會把函數的源代碼編譯成可執行的的指令,然后分配一塊內存去存放。這段空間的的地址就是函數的入口地址,每次調用的時候會從該地址入口開始執行,函數名就代表了這個地址,因此函數名就是函數的指針。
那么我們就可以定義一個用來指向函數的指針變量,用來存放某一函數的入口地址。例如:int (* add) (int, int)
。這行代碼中第一個int
表示的是返回值類型;(* add)
表示add
是一個指針變量,括號不能省略;后面的(int, int)
表示參數類型是兩個int類型,括號不能省略,括號表示指針變量不是指向其他類型的,而是函數類型的。之前對函數的調用都是通過函數名,那么現在有了指針變量,我們也可以通過指針變量來對函數進行調用。
int main() { // 通過函數名調用 max(9, 2); // 通過函數的指針變量調用 int (*p)(int, int); // 將函數max的入口地址賦值給指針變量p p = max(); (*p)(a, b) } int add(int x, int y) { return x + y; }
通過函數的指針實現多態
我們看下面代碼,ShapeVtbl
這個結構體中定義了一個計算面積的函數,沒有實現,我們叫做虛函數表。Shape
這個結構體中定義了兩個屬性,一個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
,讓他們的函數指針分別指向不同的函數入口。那么在實際運行的時候代碼就會根據我們的選擇調用不同的函數,呈現出多態的效果。
// 計算矩形面積的方法 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函數分別指向了rectangle_area、square_area struct ShapeVtbl vtbl1 = { rectangle_area, }; struct ShapeVtbl vtbl2 = { square_area, }; // 計算面積的方法,這里的area函數的邏輯是在運行時期動態綁定的,也就是有self中函數指針指向的實際函數決定的 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)); }
更多可以參考圖例:
這樣的設計在redis源碼中有很多應用,比如redis中的字典,dict
結構體定義了字典的基本屬性以及屬于dict
的一些特定函數,代碼在dict.h中。
/* * 字典類型特定函數,定義了計算哈希值、復制鍵、比較鍵等函數 */ 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 { // 類型特定函數 dictType *type; void *privdata; dictht ht[2]; int rehashidx; int iterators; } dict;
而在redis中,dict
的應用比較多,鍵的類型也可能有sds
、redisObject
等多種類型,他們的鍵比較函數,hash函數等都是不同的,因此有了下面的代碼,分別定義了適應于各種鍵的對比、hash等函數,并封裝在了不同的dictType
,代碼在redis.c中。那么在實際應用中,只需要為不同的類型選擇不同的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; }
總結
原文鏈接:https://blog.csdn.net/weixin_40149557/article/details/123625475
相關推薦
- 2022-09-29 Shell之function函數的定義及調用示例_linux shell
- 2022-03-23 QT實現定時關閉消息提示框_C 語言
- 2022-07-23 C++深入淺出探索數據結構的原理_C 語言
- 2022-04-09 一起來了解python的運算符_python
- 2022-04-26 C#新特性之可空引用類型_C#教程
- 2023-02-10 解決Jupyter?Notebook?“signal?only?works?in?main?thre
- 2022-05-06 C#利用反射實現多數據庫訪問_C#教程
- 2022-04-24 .NET?CORE?鑒權的實現示例_實用技巧
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支