面向对象的C语言编程-DynamicLinkageGenericFunctions--C语言中的偷梁换柱
文章目錄
- `DynamicLinkageGenericFunctions`
- Constructors and Destructors
- 方法、信息、類和對象
- `new`
- `delete`
- `clone`
- `differ`
- `sizeOf`
- `main`
- 總結
- `TIPS`
DynamicLinkageGenericFunctions
代碼倉庫
[代碼倉庫]:https://github.com/zzu-andrew/linux-sys/blob/6c19498a436902120eec7e08c18e4a74d04dafa2 b+j6/ooc/test/c.02/
簡介:如果你在公司維護的模塊底層又區分很多小模塊,在用戶選擇不同的模塊時其他的模塊根本不需要運行,那么這個方法很適合你,通過將方法都做成靜態方法,可以有不同的對象,但是這些對象都通過函數指針調用處理自己數據的函數。
用處:
1. 面向對象,編程風格簡潔
2. 可以完美解決程序中到處都是的if-lse
Constructors and Destructors
構造與析構函數
加入我們像下面這樣使用函數,為每個類型都定義自己的析構函數,那么就有一個問題,需要為所有的數據安裝析構函數和編寫new函數,如下:
struct type {size_t size; /* size of an object */void (* dtor) (void *); /* destructor */ }; struct String {char * text; /* dynamic string */const void * destroy; /* locate destructor */ }; struct Set {... information ...const void * destroy; /* locate destructor */ };new數據初始化只是new函數的部分工作,需要將dtor函數指向新創建對象中的析構函數,并且new中傳入不同的參數時需要實現不同的功能。
?
new(Set); /* make a set */ new(String, "text"); /* make a string */需要進行初始化時,new將會根據不同的對象調用不同的構造函數對對象進行初始化,這里需要將對象中的函數指針ctor執行對應的構造函數,指向的構造函數如下,用于初始化除了對象之外的對象成員。
/* 構造函數 */ void * new (const void * _class, ...) { const struct Class * class = _class;void * p = calloc(1, class -> size);assert(p);/* 強制轉換為Class,這樣就能實現同樣的函數只賦值初始化一次,但是所有定義的對象都能夠調用 */* (const struct Class **) p = class;if (class -> ctor){ va_list ap;va_start(ap, _class);p = class -> ctor(p, & ap);va_end(ap);}return p; } /* 析構函數 */ static void * String_ctor (void * _self, va_list * app) { struct String * self = _self;const char * text = va_arg(* app, const char *);self -> text = malloc(strlen(text) + 1);assert(self -> text);strcpy(self -> text, text);return self; }從上述構造函數的實現可以看出,這個構造函數是沒有負責對象的構建的,只是對對象中的成員進行了構造(初始化),因為對象的構造和析構是new和delete函數負責的。
并且delete函數也是只負責釋放釋放new函數申請的內存,所有構造函數申請的內存全部由析構函數負責進行釋放。
/* delete函數只負責釋放new函數申請的內存 */ void delete (void * self) { const struct Class ** cp = self;if (self && * cp && (* cp) -> dtor)self = (* cp) -> dtor(self);free(self); } /* 所有構造函數中申請的內存由,析構函數這里負責釋放 */ static void * String_dtor (void * _self) { struct String * self = _self;free(self -> text), self -> text = 0;return self; }對于一個String,有一部分內存是通過構造函數進行申請的,但是String卻是new函數申請的內存,String本身需要使用delete函數進行釋放。
方法、信息、類和對象
delete函數中傳入的對象指針,必須在傳入前初始化好,將析構函數指針指向對應對象的析構函數
struct Class {size_t size;void * (* ctor) (void * self, va_list * app);void * (* dtor) (void * self);void * (* clone) (const void * self);int (* differ) (const void * self, const void * b); };struct String {const void * class; /* must be first */char * text; };每個對象的開頭都有一個指向自類型的指針const void * class,通過這個指針我們能獲取得到new對象時需要申請的內存大小 .size,構造函數.ctor,析構函數.dtor,.clone函數對象復制函數,.differ函數對象對比函數。
仔細看來我們的每個對象開頭都有一個指向自己類型的指針const void * class;,通過calss的類型中的提供的信息我們能知道如下信息:
size_t size; // 提供對象的大小,這里也就是String的大小 void * (* ctor) (void * self, va_list * app); // 構造函數 void * (* dtor) (void * self); // 析構函數 void * (* clone) (const void * self); // 對象克隆函數 int (* differ) (const void * self, const void * b); //對象對比函數new
new函數中申請了對象的內存,并通過構造函數對對象中的內容進行了構造,其實這里也就是一個text字符串指針,申請一塊內存,并將new中傳入的字符串復制到申請的內存中去。
需要注意的是二級指針的使用方法,通過new中申請的是struct String那么p是指向String類型的一個對象,但是Stirng類型中class才是指向靜態方法的結構體的指針,想要調用方法,就需要取出p指針中的class指針。實現方法就是* (const struct Class **) p = class;,要求就是,class必須是object中的首個指針。
拆分:
* (const struct Class **) p在按照結合方式從左至右 *先與p結合,然后是(const struct Class **) 上述等價于:p->class ==> (*p).class* (const struct Class **) p ==> (const struct Class *)p->class void * new (const void * _class, ...) { const struct Class * class = _class;void * p = calloc(1, class -> size);assert(p);/* 強制轉換為Class,這樣就能實現同樣的函數只賦值初始化一次,但是所有定義的對象都能夠調用 */* (const struct Class **) p = class;if (class -> ctor){ va_list ap;//ctor 指向的函數 String_ctorva_start(ap, _class);p = class -> ctor(p, & ap);va_end(ap);}return p; } static void * String_ctor (void * _self, va_list * app) { struct String * self = _self;const char * text = va_arg(* app, const char *);self -> text = malloc(strlen(text) + 1);assert(self -> text);strcpy(self -> text, text);return self; }delete
delete函數是對new申請的內存進行釋放的函數,在確保傳入的值不是NULL并且構造函數存在的情況下先調用析構函數對self中構造函數申請的內存進行釋放,在調用free釋放new函數申請的內存。
void delete (void * self) { const struct Class ** cp = self;if (self && * cp && (* cp) -> dtor)self = (* cp) -> dtor(self);free(self); }析構函數
static void * String_dtor (void * _self) { struct String * self = _self;free(self -> text), self -> text = 0;return self; }clone
對象的克隆
void * clone (const void * self) { const struct Class * const * cp = self;assert(self && * cp && (* cp) -> clone);return (* cp) -> clone(self); }對象的克隆,就是調用new方法新生成一個和傳入對象一樣的對象
static void * String_clone (const void * _self) { const struct String * self = _self;return new(String, self -> text); }differ
差異對比函數,實現了對對象本身以及對象字符串是否相同的對比,如果對象相等那就返回0,如果是不同的對象,對象描述text一樣也放回0,表示相同;
int differ (const void * self, const void * b) { const struct Class * const * cp = self;assert(self && * cp && (* cp) -> differ);return (* cp) -> differ(self, b); } static int String_differ (const void * _self, const void * _b) { const struct String * self = _self;const struct String * b = _b;if (self == b)return 0;if (! b || b -> class != String)return 1;return strcmp(self -> text, b -> text); }sizeOf
sizeOf函數比較簡單,內部只是返回對象中size字段中存儲的數值
size_t sizeOf (const void * self) { const struct Class * const * cp = self;assert(self && * cp);return (* cp) -> size; }main
#include <stdio.h> #include "String.h" #include "new.h"int main () { void * a = new(String, "a"), * aa = clone(a);void * b = new(String, "b");printf("sizeOf(a) == %zu\n", sizeOf(a));if (differ(a, b))puts("ok");if (differ(a, aa))puts("differ?");if (a == aa)puts("clone?");delete(a), delete(aa), delete(b);return 0; }總結
這小節想說的其實就是動態的指針,同樣的應用程序將函數指針指向不同的函數,就能實現不同的效果.
在主函數中創建了a、aa、b對象,三個對象都通過calss指針指向了共同的Class結構體, Class結構中有指向靜態函數的指針,在編譯的時候通過編譯不同的c文件實現對函數指針裝載不同的函數。
實現的效果也就是通過動態指針指向不同的函數,從而實現同樣的主函數,實際執行的時候,執行不同的函數,達到不同的效果,實現對數據的封裝。
TIPS
#include <stdio.h> #include <stdlib.h> #include <assert.h> #include <string.h> #include <stdarg.h>void test(void);struct String {const void * class; /* must be first */char * text; };struct Class {size_t size;void (* test) (void); }; void test(void) {printf("test for point.\n"); }static const struct Class _String = {sizeof(struct String),test };const void * String = & _String;/*** */ int main(int argc, char const *argv[]) {size_t stringSize = sizeof(struct String);const struct Class * class = String;void * p = calloc(1, class -> size);assert(p);* (const struct Class **) p = class;struct String {const void * class; /* must be first */char * text;};struct String * p = (struct String *)calloc(1, sizeof(struct String));return 0; }我們有如下定義:
struct String {const void * class; /* must be first */char * text; }; struct Class {size_t size;void (* test) (void); };首先我們申請一個內存void * p = calloc(1, sizeof(struct String));,這里很好理解,這個時候我們想讓p變成一個指向struct String類型的一塊內存,只需要申請的時候加上強制轉換就可以了struct String * p = (struct String *)calloc(1, sizeof(struct String));,這個時候p就是一個指向struct String類型元素的一個結構體指針。
接下來看下這個使用方法*(const struct Class **) p = class;很多人看到第一眼的時候,第一眼就本能的將后面兩個取址符與前面一個解引用相互抵消最終得出下面這樣的結論,實際效果相當于:
(const struct Class *) p = class;,你要是真的這樣想就大錯特錯了。
這里的意思其實是如下圖:
在經過上述操作之后,p指針指向了String結構體,而* (const struct Class **) p = class;的作用就是講String中的void *類型的指針class指向String結構體,注意是結構體不是類型,解如圖中那樣,String結構體其實是一個struct Class類型的結構體指針。
static const struct Class _String = {sizeof(struct String),test };const void * String = & _String; 所以下面這個絕對不能使用取址和解引用相互抵消的方式進行代碼走讀,也不要新百度問答上那些講的這不這樣使用不能達到能將String結構體中的void *指針指向新申請內存的目的const struct Class ** -- 告訴編譯器這個指針是個二維指針,第一個指針是指向String結構體,結構體中又有兩個指針,第二個指針說明的是取結構體指針中的指針,具體取得哪個指針就由前面給出的類型來決定了,這里取得是一個`struct Class`結構體指針。因為這個時候取得是雙重指針,有因為p本身又是指針,要想和class這個指針對應上,需要對指針進行一次解引用,這也就形成了下面這種方式,在C語言中實現取結構體指針中的指針的方式 * (const struct Class **) p = class;總結
以上是生活随笔為你收集整理的面向对象的C语言编程-DynamicLinkageGenericFunctions--C语言中的偷梁换柱的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2016中国国际大数据大会邀请函
- 下一篇: 多中心临床大数据平台建设及深度应用