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
進行的操作。
add
向set
中添加一個element
find
查找一個element
是否在set
中
drop
將一個element
從set
中剔除
contains
將find
的結果轉變爲真假值
set
類型定義成void
類型是爲了能夠像set
傳遞任何類型的數據+
內存管理函數方法
在new.h
中定義了內存管理的方法
new
和delete
是用來代替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
中我們使用動態內存管理來實現
在Bag
中Set
和Object
表現形式如下,是兩個結構體
count -- 用於記錄Set中元素的個數,也可以說count是記錄有多少次element add到Set中了
struct Set { unsigned count; };
struct Object { unsigned count; struct Set * in; };
因爲使用動態內存呈現sets
和objects
我們需要初始化描述下Set
和Object
,new
可以定位到需要爲新的對象申請多少內存。
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
中的count
和element
中的count
進行遞增,用於統計set
中add 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
是否已經add
進 set
了,一旦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;
}
contains
和Set.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
函數,用於提供set
中add
的element
個數
雖然更加快捷的方法是直接調用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.c
和Bag,c
可以發現,抽象數據類型其實實現的就是將數據進行抽象之後,表現出同樣的接口。
在抽象數據中屏蔽所有的數據類型實現細節,暴露給用戶的是一個個應用調用的接口。