Макросы предоставляют способ для создания обобщенных списков, которые могут хранить данные различных типов. В зависимости от наших потребностей и задач мы можем определить различные формы списка. Но в данном случае рассмотрим пример по проще. Сначала возьмем только определение списка:
#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" возвращает указатель на структуру списка, через который мы затем взаимодействуем с его элементами.