在C中编写类模板通用代码的最佳方法是什么?

时间:2010-04-06 08:41:02

标签: c generics

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

3 个答案:

答案 0 :(得分:4)

我将举例说明如何在C中实现泛型功能。示例在链接列表中,但我相信如果需要,您可以在AVL树上进行调整。

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

struct list_element_s {
    void *data;
    struct list_element_s *next;

};
typedef struct list_element_s list_element;

“数据”将作为您将保留信息的“容器”,“下一个”是对直接链接元素的引用。 (注意:您的二叉树元素应包含对右/左子元素的引用)。

创建元素结构后,您需要创建列表结构。一个好的做法是让一些指向函数的成员:析构函数(需要释放由'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_alloc:将为您的列表分配内存。
  • list_free:释放为列表分配的内存,以及list_element所持有的所有“数据”。
  • list_insert_next:将在'element'旁边插入一个新元素。如果'element'为NULL,则将在列表的头部进行插入。
  • list_remove_next:将删除& return(void *)'data'由'element-> next'持有。如果'element'为NULL,它将执行“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-&gt;析构函数'。

答案 1 :(得分:3)

亚历克斯说。在c中,void *就是它。

假设你必须在C中工作,但是......为什么你需要为库提供创建/复制/赋值/销毁功能?该库的哪些功能要求AVL树代码使用这些操作?

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

答案 2 :(得分:1)

要在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

您甚至可以添加选项;比如#define HASH_SET_VALUE_TYPE或#define HASH_SET_DEBUG。