日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C語言也有封裝,繼承和多態你知道嗎_C 語言

作者:半夏(????????)?????? ? 更新時間: 2022-05-23 編程語言

我們知道封裝、繼承、多態是面向對象的三大特性,我們也知道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的應用比較多,鍵的類型也可能有sdsredisObject等多種類型,他們的鍵比較函數,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

欄目分類
最近更新