Что лучший способ состоит в том, чтобы написать подобный шаблону класса общий код в C?

Я должен записать AVL-дерево с универсальным типом в C. Лучшим способом я знаю, должен использовать [пусто*] и записать некоторые функции для создания, копирования, присвоения и разрушения. Скажите мне некоторый лучший путь.

5
задан sharptooth 6 April 2010 в 08:44
поделиться

3 ответа

Я дам вам пример того, как вы можете добиться функциональности универсальных шаблонов в C. Пример находится в связанном списке, но я уверен, что вы сможете его адаптировать на вашем AVL-дереве, если необходимо.

Прежде всего вам нужно определить структуру для элемента списка. Возможная (наиболее простая реализация):

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

};
typedef struct list_element_s list_element;

Где «данные» будут действовать как «контейнер», в котором вы собираетесь хранить вашу информацию, а «следующий» - это ссылка на элемент, связанный напрямую. (ПРИМЕЧАНИЕ: ваш элемент двоичного дерева должен включать ссылку на правые / левые дочерние элементы).

После того, как вы создадите структуру элементов, вам нужно будет создать структуру списка. Хорошей практикой является наличие некоторых членов, которые указывают на функции: деструктор (необходим для освобождения памяти, удерживаемой «данными») и компаратор (чтобы иметь возможность сравнивать два элемента вашего списка).

Реализация структуры списка может выглядеть так:

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 (s).
  • list_insert_next: вставит новый элемент рядом с 'element'. Если 'element' равен NULL, вставка будет сделана в начало списка.
  • list_remove_next: удалит и вернет (void *) 'данные', которые хранятся в 'element-> next'. Если 'element' имеет значение NULL, он выполнит «удаление списка-> заголовка».

А теперь реализация функций:

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-> destructor'.

4
ответ дан 14 December 2019 в 04:34
поделиться

Что сказал Алекс. В c, void * - это то, что есть.

Предполагая, что вы должны работать на C, однако ... Зачем вам нужно предоставлять библиотеке функции создания / копирования / присвоения / уничтожения? Какие функции этой библиотеки требуют, чтобы код AVL-дерева использовал эти операции?

Основные операции в дереве поиска - это вставка, удаление и поиск, правильно? Вам нужно будет предоставить функцию сравнения для всех этих операций, но вы должны позволить клиентам этой библиотеки обрабатывать все остальные операции. В этом случае, наверное, лучше просто.

3
ответ дан 14 December 2019 в 04:34
поделиться

Чтобы получить истинные производительные генерики в 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.

1
ответ дан 14 December 2019 в 04:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: