C语言泛型设计的原则
------------------------------------------------------------------------------------------------------------------------
使用C语言编写通用数据结构相对比较困难.其一, C语言本身不支持泛型编程的特性;其二, C语言不支持元语言.
C语言只能使用void *来编写泛型数据类型.编写泛型程序的最大难点在于如何统一:如何统一的处理对象本身和指针以及如何管理内存.
为了编写genericlist.所写的程序遵循下列原则(可能我表达的不够准确,将就看看):
1.只要是数据结构(如list)中的东西,一律在堆中分配. 这样做目的是为了统一管理内存(类似java).
2.一个东西一旦加入数据结构中,采用Deep Copy在堆中分配它的副本,数据结构中的是它的副本.这样做虽然损失了一部分效率,这样做满足了原则1,而且不用考虑这个东西的来源(栈,堆…).
3.一个东西一旦从数据结构中取出,这个东西就和数据结构脱离关系,用户需要自行管理这个东西.
Generic List的模型
------------------------------------------------------------------------------------------------------------------------
Generic List的实现(核心代码)
------------------------------------------------------------------------------------------------------------------------
完整代码详见我的github(https://github.com/gnudennis/ds_c)(generic-list.h, generic-list.c, generic-list-test.c)
0. API
/* Create a singly list with dummy head node */
List list_create(int elemsize, PfCbFree freefn);
/* Make the given list empty */
void list_make_empty(List list);
/* Dispose the list */
void list_dispose(List list);
/* Retrun true if List list is emtpy */
int list_is_empty(List list);
/* Return true if pos is last position in List list */
int list_is_last(List list, Position pos);
/* Return the postion of x in List list; NULL if not found */
Position list_find(List list, ElementAddr x, PfCbCmp cmp);
/* Return the prevoius position of x; the next field is NULL when
* x is not in List list
*/
Position list_find_previous(List list, ElementAddr x, PfCbCmp cmp);
/* Insert an element with given position pos in the List list */
void list_insert(List list, Position pos, ElementAddr x);
/* Delete the first node in List list when x is found */
int list_delete(List list, ElementAddr x, PfCbCmp cmp, ElementAddr elemaddr);
/* Get List list header position */
Position list_get_header(List list);
/* Supporting iteration mechanism Following */
Position list_get_first(List list);
Position list_next(Position pos);
int list_is_end(Position pos);
ElementAddr list_retrieve(Position pos);
1. Generic List定义
typedef void *ElementAddr;
struct Node;
typedef struct Node *PtrToNode;
struct Node
{
ElementAddr elemaddr;
PtrToNode next;
};
typedef PtrToNode Position;
typedef int (*PfCbCmp)(ElementAddr, ElementAddr);
typedef void (*PfCbFree)(ElementAddr);
typedef struct ListRecord {
PtrToNode head;
int elemsize;
PfCbFree freefn;
} *List;
可以看出:每个Node节点中放的是元素的指针(ElemAddr).而元素(Element)本身是放置在堆中. freefn的目的是为了销毁当元素本身也是指针的情形,当销毁栈时需要调用该毁掉函数.
2. list_create(Dummy Head Node, NULL Tail)
/* Create a singly list with dummy head node */
List
list_create(int elemsize, PfCbFree freefn)
{
List list;
if ( (list = malloc(sizeof(struct ListRecord))) == NULL ) {
fprintf(stderr, "Out of memory!\n");
exit(1);
}
if ( (list->head = malloc(sizeof(struct Node))) == NULL ) {
fprintf(stderr, "Out of memory!\n");
exit(1);
}
list->head->next = NULL;
list->freefn = freefn;
list->elemsize = elemsize;
}
3. list_dispose
/* Make the given list empty */
void
list_make_empty(List list)
{
PtrToNode first;
if ( list == NULL ) {
fprintf(stderr, "Must use list_create first\n");
} else {
while ( !list_is_empty(list) ) {
first = list->head->next;
list->head->next = first->next;
if ( list->freefn )
list->freefn(first->elemaddr);
free(first->elemaddr);
free(first);
}
}
}
/* Dispose the list */
void
list_dispose(List list)
{
list_make_empty(list);
free(list->head);
free(list);
}
4. list_insert
/* Insert an element with given position pos in the List list */
void
list_insert(List list, Position pos, ElementAddr x)
{
PtrToNode node;
ElementAddr elemaddr;
if ( (elemaddr = malloc(list->elemsize)) == NULL ) {
fprintf(stderr, "Out of memory");
exit(1);
}
memcpy(elemaddr, x, list->elemsize);
/* Deep Copy*/
if ( (node = malloc(sizeof(struct Node))) == NULL ) {
fprintf(stderr, "Out of memory!\n");
exit(1);
}
node->elemaddr = elemaddr;
node->next = pos->next;
pos->next = node;
}
根据原则2,采用深拷贝在堆中分配待添加元素.
5.list_delete
/* Delete the first node in List list when x is found; */
int
list_delete(List list, ElementAddr x, PfCbCmp cmp, ElementAddr elemaddr)
{
Position pos, tmp;
int is_found = 0;
pos = list_find_previous(list, x, cmp);
if ( !list_is_last(list, pos) ) {
tmp = pos->next;
pos->next = tmp->next;
memcpy(elemaddr, tmp->elemaddr, sizeof(list->elemsize));
free(tmp->elemaddr);
free(tmp);
is_found = 1;
}
return is_found;
}
根据原则3, 一旦从list中取出, 用户需要自行管理这个东西.
6.Iteration Implementation
Position
list_get_first(List list)
{
return list->head->next;
}
Position
list_next(Position pos)
{
return pos->next;
}
int
list_is_end(Position pos)
{
return pos == NULL;
}
ElementAddr
list_retrieve(Position pos)
{
return pos->elemaddr;
}
抽象出Position这个概念是非常好的, Position可以是一个Index,也可以是指针,这取决具体的实现.
Generic List的使用
------------------------------------------------------------------------------------------------------------------------
即是"如何统一的处理对象本身和指针"的问题.先看一例.
#include
#include
#include
#include "generic-list.h"
typedef void (*PfCbPrintElement)(const ElementAddr elemaddr);
void
print_list(List list, PfCbPrintElement printfn)
{
Position pos;
if ( list_is_empty(list) ) {
fprintf(stdout, "Empty list\n");
return ;
}
for ( pos = list_get_first(list);
!list_is_end(pos);
pos = list_next(pos)
) {
printfn(list_retrieve(pos));
}
printf("\n");
}
int
int_equal(ElementAddr left, ElementAddr right)
{
if ( *(int *)left == *(int *)right )
return 1;
return 0;
}
void
print_int(ElementAddr elemaddr)
{
printf("%d ", *(int *)elemaddr);
}
void
stringfree(ElementAddr elemaddr)
{
free(*(void **)elemaddr);
}
int
str_equal(ElementAddr left, ElementAddr right)
{
if ( strcmp((char *)(*(void **)left), (char *)(*(void **)right)) == 0 )
return 1;
return 0;
}
void
print_str(ElementAddr elemaddr)
{
printf("%s ", (char *)(*(void **)elemaddr));
}
int main(int argc, char **argv)
{
List int_list, str_list;
Position pos;
int i;
char *names[] = {
"C", "C++", "Jave", "C#", "Python",
"PHP", "Basic", "Objective C", "Matlab", "Golang"
};
/* test int list */
printf("Test int list\n");
int_list = list_create(sizeof(int), NULL);
pos = list_get_header(int_list);
for ( i = 0; i < 10; ++i ) {
list_insert(int_list, pos, &i);
print_list(int_list, print_int);
pos = list_next(pos);
}
for ( i = 0; i < 10; i += 2 ) {
int delval;
int is_found;
is_found = list_delete(int_list, &i, int_equal, &delval);
if ( is_found )
printf("Delete: %d\n", delval );
}
}
for ( i = 0; i < 10; ++i)
if ( (i % 2 == 0) == (list_find(int_list, &i, int_equal) != NULL) )
printf("Find fails\n");
printf("Finished deletions\n");
print_list(int_list, print_int);
list_dispose(int_list);
/* test string list */
printf("Test string list\n");
str_list = list_create(sizeof(char *), stringfree);
pos = list_get_header(str_list);
for ( i = 0; i < sizeof(names) / sizeof(names[0]); ++i) {
char *copy = strdup(names[i]);
list_insert(str_list, pos, ©);
print_list(str_list, print_str);
pos = list_next(pos);
}
for ( i = 0; i < sizeof(names) / sizeof(names[0]); i += 2) {
char *delstr;
int is_found;
is_found = list_delete(str_list, &names[i], str_equal, &delstr);
if ( is_found ) {
printf("Delete: %s\n", delstr);
free(delstr);
}
}
for ( i = 0; i < 10; ++i ) {
if ( (i % 2 == 0) == (list_find(str_list, &names[i], str_equal) != NULL))
printf("Find fails\n");
}
printf("Finished deletions\n");
print_list(str_list, print_str);
list_dispose(str_list);
return 0;
}
分析:
这个例子就是一个代表,int_list表示处理的是对象,而string_list表示处理的是指针.看下面的示例,就可以理解处理对象本身和处理指针的不同.
(1).处理对象本身情形
对于int_list.
int_list= list_create(sizeof(int), NULL);
int_list不需要freefn.直接赋为NULL.
for ( i = 0; i< 10; ++i ) {
list_insert(int_list, pos, &i);
print_list(int_list, print_int);
pos = list_next(pos);
}
list_insert采用深拷贝,首先先在堆中生成元素,然后在生成Node, Node的elemaddr指向这些堆中的元素.
for ( i = 0; i< 10; i += 2 ) {
int delval;
int is_found;
is_found = list_delete(int_list,&i, int_equal, &delval);
if ( is_found )
printf("Delete: %d\n", delval );
}
}
(2).对于处理指针情形
对于str_list.
str_list= list_create(sizeof(char *), stringfree);
str_list需要指定freefn,因为我们知道Node中的elemaddr其实是void **形式.需要stringfree销毁诸如"C", "C++"等字符串.
void
stringfree(ElementAddrelemaddr)
{
free(*(void **)elemaddr);
}
for ( i = 0; i< sizeof(names) / sizeof(names[0]); ++i) {
char *copy = strdup(names[i]);
list_insert(str_list, pos, ©);
print_list(str_list, print_str);
pos = list_next(pos);
}
需要保证:用户需要保证诸如字符串"C","C++"等在堆中分配. Node中保存的char**.
for ( i = 0; i< sizeof(names) / sizeof(names[0]); i += 2) {
char *delstr;
int is_found;
is_found = list_delete(str_list, &names[i], str_equal,&delstr);
if ( is_found ) {
printf("Delete: %s\n", delstr);
free(delstr);
}
}