面向對象的C語言編程-DynamicLinkageGenericFunctions--C語言中的偷樑換柱

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;
}

從上述構造函數的實現可以看出,這個構造函數是沒有負責對象的構建的,只是對對象中的成員進行了構造(初始化),因爲對象的構造和析構是newdelete函數負責的。

並且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_ctor
		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;
}

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;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章