面向對象C語言編程--抽象數據類型-AbstractDataTypes

AbstractDataTypes

C語言的靈活

C語言很靈活,不但有基礎數據類型,char、int、double等,還允許程序員自定義類型,如:

定義一個鏈表使用的數據類型,其中有Node節點和自己需要使用的其他數據信息。

typedef struct node {
    struct node * next;
    ... information ...
} node;

一個小例子-set

set會有這些操作:

#ifndef	SET_H
#define	SET_H

extern const void * Set;

void * add (void * set, const void * element);
void * find (const void * set, const void * element);
void * drop (void * set, const void * element);
int contains (const void * set, const void * element);
unsigned count (const void * set);

#endif

這些方法中set就是一個抽象數據類型,而這些方法描述的是我們能對set進行的操作。

addset中添加一個element

find查找一個element是否在set

drop將一個elementset中剔除

containsfind的結果轉變爲真假值

set類型定義成void類型是爲了能夠像set傳遞任何類型的數據+

內存管理函數方法

new.h中定義了內存管理的方法

newdelete是用來代替ANSI-C中的calloc還有free方法的

#ifndef	NEW_H
#define	NEW_H

void * new (const void * type, ...);
void delete (void * item);

#endif

對象

想讓set更有意思,我們需要另外一個抽象數據類型Object

#ifndef	OBJECT_H
#define	OBJECT_H

extern const void * Object;		/* new(Object); */

int differ (const void * a, const void * b);

#endif

differ用於對比Objects是否相等。

一個小應用

這也是這裏說明的重點,不同的實現方式但是表現形式是一樣的,這就達到了數據抽象的效果,同一個應用程序框架,底層更換之後,程序還是能正常運行。

#include <stdio.h>

#include "new.h"
#include "Object.h"
#include "Set.h"



int main ()
{	
    /* 新建一個Set對象 */
    void * s = new(Set);  //*s = 10
    /* 將一個新建的Object添加到s中 */
	void * a = add(s, new(Object)); //*a = 1
    /* 將一個新建的Object添加到s中 */  
	void * b = add(s, new(Object));   // *b = 1
    /* 新建一個  Object 對象*/
	void * c = new(Object); //  *c = 10

     /* s中是佛包含 a 是否包含b */
	if (contains(s, a) && contains(s, b))
		puts("ok");
	/* s中是否含有c */
	if (contains(s, c))
		puts("contains?");
	/*  a, add(s, a)) 相同*/
	if (differ(a, add(s, a)))
		puts("differ?");
	
	if (contains(s, drop(s, a)))
		puts("drop?");

	delete(drop(s, b));
	delete(drop(s, c));

	return 0;
}

#include <assert.h>
#include <stdio.h>

#include "new.h"
#include "Set.h"
#include "Object.h"

const void * Set;
const void * Object;

#if ! defined MANY || MANY < 1
#define	MANY	10
#endif

/* 定義一個數組用於模擬堆 */
static int heap [MANY];

/* 創建一個對象,這裏使用在堆裏賦值MANY表示 */
void * new (const void * type, ...)
{	int * p;							/* & heap[1..] */

	for (p = heap + 1; p < heap + MANY; ++ p)
		if (! * p)
			break;
	assert(p < heap + MANY);
	* p = MANY;
	return p;
}
/* 刪除一個對象,將對應數組中的值進行清空表示 */
void delete (void * _item)
{	int * item = _item;

	if (item)
	{	assert(item > heap && item < heap + MANY);
		* item = 0;
	}
}
/* 向_set中添加一個  _element元素 */
void * add (void * _set, const void * _element)
{	int * set = _set;
	const int * element = _element;

	assert(set > heap && set < heap + MANY);
	assert(* set == MANY);
	assert(element > heap && element < heap + MANY);

	if (* element == MANY)
		* (int *) element = set - heap;
	else
		assert(* element == set - heap);

	return (void *) element;
}
/* 查找_set中是否有_element */
void * find (const void * _set, const void * _element)
{	const int * set = _set;
	const int * element = _element;

	assert(set > heap && set < heap + MANY);
	assert(* set == MANY);
	assert(element > heap && element < heap + MANY);
	assert(* element);

	return * element == set - heap ? (void *) element : 0;
}
/* 將_set是否包含_element轉換爲是否爲真 */
int contains (const void * _set, const void * _element)
{
	return find(_set, _element) != 0;
}
/* 從_set中刪除 _element */
void * drop (void * _set, const void * _element)
{	int * element = find(_set, _element);

	if (element)
		* element = MANY;
	return element;
}
/* 對比a與b是否相同 */
int differ (const void * a, const void * b)
{
	return a != b;
}

到這裏整個set的例子就結束了,接下來看下Bags的例子,通過這兩個例子的對比你就能明白如何通過設計良好的數據類型也就是對數據進行抽象,來達到代碼複用,同一個應用程序,調用同樣的接口來達到不同的目的。


另外一個例子—Bag

Bag例子使用的主函數和set一樣但是實現不一樣,達到的效果就是對數據進行抽象之後,使用同一個框架運行不同的底層程序。

在不改變方法的情況下,我們可以改變方法的實現,在Bag中我們使用動態內存管理來實現

BagSetObject表現形式如下,是兩個結構體

count -- 用於記錄Set中元素的個數,也可以說count是記錄有多少次element add到Set中了
struct Set { unsigned count; };
struct Object { unsigned count; struct Set * in; };

因爲使用動態內存呈現setsobjects我們需要初始化描述下SetObjectnew可以定位到需要爲新的對象申請多少內存。

static const size_t _Set = sizeof(struct Set);
static const size_t _Object = sizeof(struct Object);

const void * Set = & _Set;
const void * Object = & _Object;

主函數中申請內存使用的方式是void * s = new(Set); * (const size_t *) type就相當於* (const size_t *) Set大小剛好等於Set

new函數很好理解就是使用calloc申請固定大小的內存,並將內存的指針賦返回給要New的對象

void * new (const void * type, ...)
{	const size_t size = * (const size_t *) type;
	void * p = calloc(1, size);

	assert(p);
	return p;
}
The  calloc()  function  allocates  memory for an array of nmemb elements of size bytes each and returns a pointer to the allocated memory.  The memory is set to zero.  If nmemb or size is 0, then calloc() returns  either NULL, or a unique pointer value that can later be successfully passed to free().

delete方法只是對free的一個簡單封裝而已,直接將需要釋放內存的指針傳遞給free就可以了,當然這裏只是一個玩具代碼,真正使用的代碼需要對釋放的對象進行合法性檢測。

void delete (void * item)
{
	free(item);
}

add方法將element元素add到對應的set對象中,add之後對set中的countelement中的count進行遞增,用於統計setadd element個數。

void * add (void * _set, const void * _element)
{	struct Set * set = _set;
	struct Object * element = (void *) _element;

	assert(set);
	assert(element);

	if (! element -> in)
		element -> in = set;
	else
		assert(element -> in == set);
	++ element -> count, ++ set -> count;

	return element;
}

find 函數作用就是檢查對應的element是否已經addset了,一旦add之後element -> in == _set將會成立🉑

void * find (const void * _set, const void * _element)
{	const struct Object * element = _element;

	assert(_set);
	assert(element);

	return element -> in == _set ? (void *) element : 0;
}

containsSet.c中實現的一樣,contains函數依然時對find函數的一個封裝,find函數返回非0的時候也就是在set中已經添加了對應的元素element

int contains (const void * _set, const void * _element)
{
	return find(_set, _element) != 0;
}

drop函數

void * drop (void * _set, const void * _element)
{	struct Set * set = _set;
	struct Object * element = find(set, _element);

	if (element)
	{	if (-- element -> count == 0)
			element -> in = 0;
		-- set -> count;
	}
	return element;
}

Count函數,用於提供setaddelement個數

雖然更加快捷的方法是直接調用set->Count但是最好不要這樣做,這樣會破壞數據的封裝性。

unsigned count (const void * _set)
{	const struct Set * set = _set;

	assert(set);
	return set -> count;
}

differ函數

int differ (const void * a, const void * b)
{
	return a != b;
}

主函數

#include <stdio.h>

#include "new.h"
#include "Object.h"
#include "Set.h"

int main ()
{	void * s = new(Set);
	void * a = add(s, new(Object));
	void * b = add(s, new(Object));
	void * c = new(Object);

	if (contains(s, a) && contains(s, b))
		puts("ok");

	if (contains(s, c))
		puts("contains?");

	if (differ(a, add(s, a)))
		puts("differ?");

	if (contains(s, drop(s, a)))
		puts("drop?");

	delete(drop(s, b));
	delete(drop(s, c));

	return 0;
}


執行結果

andrew@DESKTOP-GDC67HT:/mnt/u/linux-sys/ooc/test/c.01$ ./sets 
ok
andrew@DESKTOP-GDC67HT:/mnt/u/linux-sys/ooc/test/c.01$ ./bags
ok
drop?

執行結果可以看出bags比sets多一個drops打印,這是因爲a被add了兩次,但是sets中無論添加多少次都只是被添加一次,所以drop之後就不在set裏面了,但是bags能夠添加多次,a添加了兩次到那時drop一次,所以a還是在s中。

總結

對比Set.cBag,c可以發現,抽象數據類型其實實現的就是將數據進行抽象之後,表現出同樣的接口。

在抽象數據中屏蔽所有的數據類型實現細節,暴露給用戶的是一個個應用調用的接口。

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