Макрос для создания обобщенного списка

Последнее обновление: 04.12.2024

Макросы предоставляют способ для создания обобщенных списков, которые могут хранить данные различных типов. В зависимости от наших потребностей и задач мы можем определить различные формы списка. Но в данном случае рассмотрим пример по проще. Сначала возьмем только определение списка:

#define DEFINE_LIST(T) \
typedef struct { \
    T* data; \
    size_t count; \
    size_t size; \
} list_##T;

Данный макрос принимает параметр T, через который будет передаваться имя типа. Макрос определяет структуру, которая представляет простейший список и которая имеет три поля. Первое поле - data представляет собственно данные списка. Причем в качестве типа этого поля используется значение параметра T. Поле count указывает на количество элементов, а поле size - на вместительность - для скольких элементов выделена память в списке.

Название структуры зависит от названия типа и образуется с помощью простой конкатенации: list_##T

Допустим, у нас есть такой вызов макроса

DEFINE_LIST(int)

Он будет разворачиваться в следующий код:

typedef struct {
    int* data;
    size_t count;
    size_t size;
} list_int;

Таким образом, с помощью этого макроса мы можем создать отдельные типы списков для отдельных типов данных без необходимости определять для каждого типа списка отдельную структуру.

Теперь пойдем дальше и добавим для списка ряд самых базовых операций:

#define DEFINE_LIST(T) \
typedef struct { \
    T* data; \
    size_t count; \
    size_t size; \
} list_##T; \
\
void list_##T##_new(list_##T* v) { \
    v->data = NULL; \
    v->count= v->size = 0; \
} \
\
void list_##T##_push(list_##T* v, T elem) { \
    if (v->count == v->size) { \
        v->size = v->size ? v->size << 1 : 1; \
        v->data = (T*)realloc(v->data, v->size * sizeof(T)); \
    } \
    v->data[v->count++] = elem; \
} \
\
T list_##T##_pop(list_##T* v) { \
    return v->data[--v->count]; \
} \
\
void list_##T##_free(list_##T* v) { \
    free(v->data); \
    list_##T##_new(v); \
}

Здесь добавлен ряд функций. Прежде всего функция для инициализации списка:

void list_##T##_new(list_##T* v) { \
    v->data = NULL; \
    v->count= v->size = 0; \
}

Название этой функции опять же зависит от типа данных, поэтому у нас не получится никаких конфликтов между реализациями этой функции для разных типов. В качестве параметра функция принимает указатель на список и инициализирует его поля.

Функция list_##T##_push() добавляет в список один элемент:

void list_##T##_push(list_##T* v, T elem) { \
    if (v->count == v->size) { \
        v->size = v->size ? v->size << 1 : 1; \
        v->data = (T*)realloc(v->data, v->size * sizeof(T)); \
    } \
    v->data[v->count++] = elem; \
}

Если места в списке нет, то с помощью библиотечной функции realloc() выделяем дополнительную память под удвоенное количество элементов и добавляем элемент в массив data с увеличеним счетчика count.

Функция list_##T##_pop, наоборот, извлекает последний элемент, уменьшая счетчик элементов:

T list_##T##_pop(list_##T* v) { \
    return v->data[--v->count]; \
}

И функция очищает ранее выделенную память и сбрасывает значение полей структуры:

void list_##T##_free(list_##T* v) { \
    free(v->data); \
    list_##T##_new(v); \
}

Пример применения:

#include <stdio.h>
#include <stdlib.h>

#define DEFINE_LIST(T) \
typedef struct { \
    T* data; \
    size_t count; \
    size_t size; \
} list_##T; \
\
void list_##T##_new(list_##T* v) { \
    v->data = NULL; \
    v->count= v->size = 0; \
} \
\
void list_##T##_push(list_##T* v, T elem) { \
    if (v->count == v->size) { \
        v->size = v->size ? v->size << 1 : 1; \
        v->data = (T*)realloc(v->data, v->size * sizeof(T)); \
    } \
    v->data[v->count++] = elem; \
} \
\
T list_##T##_pop(list_##T* v) { \
    return v->data[--v->count]; \
} \
\
void list_##T##_free(list_##T* v) { \
    free(v->data); \
    list_##T##_new(v); \
}

DEFINE_LIST(int) // определяем список


int main(){
    list_int numbers; // определяем переменную списка
    list_int_new(&numbers);     // инициализируем его поля
    list_int_push(&numbers, 22);    // добавляем в список новый элемент
    int n = list_int_pop(&numbers);  // удаляем из списка последний элемент
    printf("n: %d\n", n);

    // аналогично можно добавить несколько элементов
    list_int_push(&numbers, 11);
    list_int_push(&numbers, 12);
    list_int_push(&numbers, 13);
    // перебирем элементы
    for(size_t i=0; i<numbers.count; ++i){
        printf("numbers[%lu]: %d\n", i,  numbers.data[i]);
    }

    list_int_free(&numbers);  // очищаем память под список
    return 0;
}

Поскольку мы определили в макросе создание структуры с именем "list_##T", то при использовании типа int будет создаваться структура "list_int". Соответственно мы можем этот тип для определения переменных или параметров. И в данном случае мы определяем переменную numbers, которая представляет тип list_int, и выполняем различные операции с ней.

Аналогично можно использовать этот макрос для создания списка под другие типы данных:

// определение макроса то же самое

typedef struct{
    char* name;
    int age;
} person;

DEFINE_LIST(int)
DEFINE_LIST(person)

int main(){
    list_person people; // определяем переменную списка
    list_person_new(&people); 

    // добавляем данные
    list_person_push(&people, (person) {"Tom", 40});  
    list_person_push(&people, (person) {"Bob", 46});  
    list_person_push(&people, (person) {"Sam", 29});    

    for(size_t i=0; i<people.count; ++i){
        printf("%s: %d\n", people.data[i].name, people.data[i].age);
    }

    list_person_free(&people);  // очищаем память под список
    return 0;
}

Другой аналогичный пример создания списков через указатели:

#include <stdio.h>
#include <stdlib.h>


#define DEFINE_LIST(T) \
typedef struct { \
    T* data; \
    size_t count; \
    size_t size; \
} T##List; \
\
T##List* T##List_new() { \
    T##List* list = malloc(sizeof(*list)); \
    list->data = 0; \
    list->count= list->size = 0; \
    return list; \
} \
\
void T##List_append(T##List* list, T elem) { \
    if (list->count == list->size) { \
        list->size = list->size ? list->size << 1 : 1; \
        list->data = (T*)realloc(list->data, list->size * sizeof(T)); \
    } \
    list->data[list->count++] = elem; \
} \
void T##List_delete(T##List* list) { \
    if(list->data) { \
        free(list->data); \
        list->data = 0; \
    } \
    list->count= list->size = 0; \
    free(list); \
}


typedef struct{
    char* name;
    int age;
} Person;

DEFINE_LIST(Person)

int main(){
    PersonList* people = PersonList_new(); 

    PersonList_append(people, (Person) {"Tom", 40});  
    PersonList_append(people, (Person) {"Bob", 46});  
    PersonList_append(people, (Person) {"Sam", 29});    

    for(size_t i=0; i<people->count; ++i){
        printf("%s: %d\n", people->data[i].name, people->data[i].age);
    }

    PersonList_delete(people);  // очищаем память под список
    return 0;
}

Здесь вообщем-то аналогичный пример, только функция "T##List_new" возвращает указатель на структуру списка, через который мы затем взаимодействуем с его элементами.

Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850