代码之家  ›  专栏  ›  技术社区  ›  Ribtoks

用C编写类模板(如通用代码)的最佳方法是什么?

  •  5
  • Ribtoks  · 技术社区  · 15 年前

    我需要在C语言中编写带有泛型类型的AVL树。我知道的最好方法是使用[void*]并编写一些用于创建、复制、分配和销毁的函数。请告诉我更好的方法。

    3 回复  |  直到 15 年前
        1
  •  4
  •   Andrei Ciobanu    15 年前

    我将给您举一个例子,说明如何在C中实现泛型功能。这个例子在一个链接列表中,但我确信您可以在必要时在AVL树中对其进行调整。

    首先,您需要为list元素定义一个结构。可能的(最简单的实现):

    struct list_element_s {
        void *data;
        struct list_element_s *next;
    
    };
    typedef struct list_element_s list_element;
    

    其中“data”将充当“container”,用于保存信息,“next”是对直接链接元素的引用。(注意:二进制树元素应该包含对右/左子元素的引用)。

    创建元素结构后,需要创建列表结构。一个好的实践是让一些指向函数的成员:析构函数(需要释放“data”保存的内存)和比较器(能够比较两个列表元素)。

    列表结构实现可能如下所示:

    struct list_s {
        void (*destructor)(void *data);    
        int (*cmp)(const void *e1, const void *e2);
        unsigned int size;
        list_element *head;
        list_element *tail;
    
    };
    typedef struct list_s list;
    

    在设计数据结构之后,应该设计数据结构接口。假设我们的列表将具有以下最简单的界面:

    nmlist *list_alloc(void (*destructor)(void *data));
    int list_free(list *l);
    int list_insert_next(list *l, list_element *element, const void *data);
    void *list_remove_next(list *l, list_element *element);
    

    在哪里?

    • 列表分配:将为列表分配内存。
    • list-free:将释放为list分配的内存,以及list-element持有的所有“数据”。
    • list_insert_next:将在“element”旁边插入一个新元素。如果“element”为空,则将在列表的开头进行插入。
    • list_remove_next:将删除“element->next”持有的&return(void*)“data”。如果“element”为空,则将执行“list->head removal”。

    现在功能实现:

    list *list_alloc(void (*destructor)(void *data))
    {
        list *l = NULL;
        if ((l = calloc(1,sizeof(*l))) != NULL) {
            l->size = 0;
            l->destructor = destructor;
            l->head = NULL;
            l->tail = NULL;
        }
        return l;
    }
    
    int list_free(list *l)
    {
        void *data;
        if(l == NULL || l->destructor == NULL){
            return (-1);
        }    
        while(l->size>0){    
            if((data = list_remove_next(l, NULL)) != NULL){
                list->destructor(data);
            }
        }
        free(l);
        return (0);
    }
    
    int list_insert_next(list *l, list_element *element, const void *data)
    {
        list_element *new_e = NULL;
        new_e = calloc(1, sizeof(*new_e));
        if (l == NULL || new_e == NULL) {
            return (-1);
        }
        new_e->data = (void*) data;
        new_e->next = NULL;
        if (element == NULL) {
            if (l->size == 0) {
                l->tail = new_e;
            }
            new_e->next = l->head;
            l->head = new_e;
        } else {
            if (element->next == NULL) {
                l->tail = new_e;
            }
            new_e->next = element->next;
            element->next = new_e;
        }
        l->size++;
        return (0);
    }
    
    void *list_remove_next(list *l, list_element *element)
    {
        void *data = NULL;
        list_element *old_e = NULL;
        if (l == NULL || l->size == 0) {
            return NULL;
        }
        if (element == NULL) {
            data = l->head->data;
            old_e = l->head;
            l->head = l->head->next;
            if (l->size == 1) {
                l->tail = NULL;
            }
        } else {
            if (element->next == NULL) {    
                return NULL;    
            }    
            data = element->next->data;
            old_e = element->next;
            element->next = old_e->next;
            if (element->next == NULL) {
                l->tail = element;
            }
        }
        free(old_e);
        l->size--;
        return data;
    }
    

    现在,如何使用简单的通用链表实现。在下面的示例中,列表的作用类似于堆栈:

    #include <stdlib.h>
    #include <stdio.h>
    #include "nmlist.h"
    
    void simple_free(void *data){
        free(data);
    }
    
    int main(int argc, char *argv[]){
        list *l = NULL;
        int i, *j;
    
        l = list_alloc(simple_free);
        for(i = 0; i < 10; i++){
            j = calloc(1, sizeof(*j));
            if(j != NULL){
                *j = i;
                list_insert_next(l, NULL, (void*) j);
            }
        }
    
        for(i = 0; i < 10; i++){
            j = (int*) list_remove_next(l, NULL);
            if(j != NULL){
                printf("%d \n", *j);
            }
        }
    
        list_free(l);
    
        return (0);
    }
    

    请注意,您可以使用引用更复杂结构的指针,而不是“int*j”。如果这样做,请不要忘记相应地修改“list->析构函数”。

        2
  •  3
  •   Jesse Millikan    15 年前

    亚历克斯说的。在C中, void * 就是这样。

    假设你必须在C工作,尽管…为什么需要向库提供创建/复制/分配/销毁功能?这个库的哪些特性需要AVL树代码来使用这些操作?

    搜索树上的主要操作是插入、删除和查找,对吗?您需要为所有这些操作提供一个比较函数,但是您应该让这个库的客户机处理所有其他操作。在这种情况下,简单可能更好。

        3
  •  1
  •   user295691    15 年前

    要做到这一点,在C语言中执行泛型,需要使用预处理器进行黑客攻击。这种方法有许多与C++模板方法相同的缺点,即所有(大多数,无论如何)代码必须位于头文件中,调试和测试是一种痛苦。它的优点还包括:您可以获得优异的性能,让编译器进行各种内联以加快速度,通过减少间接寻址来最小化分配,以及一点类型安全性。

    定义如下(假设我们有一个哈希集)

    int my_int_set(int x);
    #define HASH_SET_CONTAINED_TYPE int
    #define HASH_SET_TYPE my_int_set
    #define HASH_SET_FUNC hash_int
    #include "hash_set.h"
    

    然后要使用它,只需使用上面创建的类型:

    my_int_set x;
    my_int_set_init(&x);
    my_int_set_add(&x, 7);
    if (my_int_set_check(&x, 7)) printf("It worked!\n");
    ...
    // or, if you prefer
    my_int_set *x = my_int_set_create();
    

    在内部,这是通过一系列令牌粘贴等实现的,并且(如上所述)是一个巨大的测试难题。

    比如:

    #ifndef HASH_SET_CONTAINED_TYPE
        #error Must define HASH_SET_CONTAINED_TYPE
    #endif
    ...  /// more parameter checking
    
    #define HASH_SET_ENTRY_TYPE HASH_SET_TYPE ## _entry
    typedef struct HASH_SET_ENTRY_TYPE ## _tag {
        HASH_SET_CONTAINED_TYPE key;
        bool present;
    } HASH_SET_ENTRY_TYPE;
    typedef struct HASH_SET_TYPE ## _tag {
        HASH_SET_TYPE ## _entry data[];
        size_t elements;
    } HASH_SET_TYPE;
    
    void HASH_SET_TYPE ## _add(HASH_SET_CONTAINED_TYPE value) {
        ...
    }
    ...
    #undef HASH_SET_CONTAINED_TYPE
    ...  // remaining uninitialization
    

    您甚至可以添加选项,例如定义哈希集值类型或定义哈希集调试。