如何使用C语言写: 泛型链表

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, Nodeelemaddr指向这些堆中的元素.

 

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

}

}


你可能感兴趣的:(数据结构)