Я должен записать AVL-дерево с универсальным типом в C. Лучшим способом я знаю, должен использовать [пусто*] и записать некоторые функции для создания, копирования, присвоения и разрушения. Скажите мне некоторый лучший путь.
Я дам вам пример того, как вы можете добиться функциональности универсальных шаблонов в 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 *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'.
Что сказал Алекс. В c, void *
- это то, что есть.
Предполагая, что вы должны работать на C, однако ... Зачем вам нужно предоставлять библиотеке функции создания / копирования / присвоения / уничтожения? Какие функции этой библиотеки требуют, чтобы код AVL-дерева использовал эти операции?
Основные операции в дереве поиска - это вставка, удаление и поиск, правильно? Вам нужно будет предоставить функцию сравнения для всех этих операций, но вы должны позволить клиентам этой библиотеки обрабатывать все остальные операции. В этом случае, наверное, лучше просто.
Чтобы получить истинные производительные генерики в 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.