2.2.1 Списки

glib предоставляет универсальные одно- и двусвязные списки, соответственно GSList и GList. Они реализованы как списки, содержащие gpointer; вы можете использовать их, чтобы хранить целые числа с помощью макросов "GINT_TO_POINTER" и "GPOINTER_TO_INT". GSList и GList имеют идентичный API за исключением того, что есть функция "g_list_previous()", но нет "g_slist_previous()". В этом разделе будет обсуждаться GSList, но все это применимо и к двусвязным спискам.

В реализации glib пустой список -- это просто указатель равный NULL. Всегда можно передавать NULL функциям, так как это допустимый список, имеющий нулевую длину. Код, создающий список и добавляющий к нему один элемент, должен выглядеть примерно так:

GSList *list = NULL; gchar *element = g_strdup("a string"); list = g_slist_append(list, element);

Списки в glib реализованы под заметным влиянием Lisp'а; пустой список равен специальному значению "nil" по этой причине. "g_slist_prepend()" работает почти как cons -- это постоянная по времени операция, которая добавляет новую ячейку в начало списка.

Обратите внимание, вы должны заменить список, переданный в функции, изменяющие список, на их возвращаемое значение на случай, если голова списка изменится. glib сам справится с проблемами памяти, освобождая или выделяя связи списка по необходимости.

Например, следующий код удалит элемент, добавленный в примере выше, и, таким образом, очистит список:

list = g_slist_remove(list, element);

list не равен NULL. Вы также, естественно, сами должны освободить element. Для очистки всего списка воспользуйтесь "g_slist_free()", который удалит все связи за раз. "g_slist_free()" не имеет возвращаемого значения, потому что оно всегда будет NULL, и вы можете просто присвоить это значение своему списку, если захотите. Очевидно, функция "g_slist_free()" освобождает только ячейки списка; она ничего не знает про то, что делать с содержимым списка.

Для доступа к элементу списка, ссылайтесь на структуру GSList напрямую: gchar *my_data = list->data;

Для хождения по списку, можно писать примерно следующий код:

GSList *tmp = list; while (tmp != NULL) { printf("List data: %p\n", tmp->data); tmp = g_slist_next(tmp); }

Список функций 2..8 показывает базовые функции для изменения содержимого GSList. Вы должны присваивать своей переменной-списку возвращаемое значение всех этих функций на случай, если голова списка изменится. Учтите, что glib не хранит указатель на хвост списка, поэтому добавление в начало -- это постоянная по времени операция, тогда как время выполнения добавления в конец, вставки и удаления пропорционально длине списка.

В частности, это означает, что создание списка с использованием "g_slist_append()" -- ужасная идея; используйте "g_slist_prepend()" и затем вызывайте "g_slist_reverse()", если вам важно расположение элементов. Если вы собираетесь часто добавлять элементы к концу списка, вы можете хранить указатель на последний элемент. Следующий код может быть использован для организации эффективного добавления в конец списка:

void efficient_append(GSList **list, GSList **list_end, gpointer data) { g_return_if_fail(list != NULL); g_return_if_fail(list_end != NULL); if (*list == NULL) { g_assert(*list_end == NULL); *list = g_slist_append(*list, data); *list_end = *list; } else *list_end = g_slist_append(*list_end, data)->next; }

Чтобы воспользоваться этой функцией, храните список и его хвост где-нибудь, и передавайте их адреса в "efficient_append()":

GSList *list = NULL; GSList *list_end = NULL; efficient_append(&list, &list_end, g_strdup("Foo")); efficient_append(&list, &list_end, g_strdup("Bar")); efficient_append(&list, &list_end, g_strdup("Baz"));

Конечно, вы должны осторожно пользоваться списковыми функциями, которые могут изменить хвост списка без изменения "list_end".

Список функций 2..8: Изменения содержимого связного списка
"#include "<glib.h>
GSList *g_slist_append(GSList *list, gpointer data) GSList *g_slist_prepend(GSList *list, gpointer data) GSList *g_slist_insert(GSList *list, gpointer data, gint position) GSList *g_slist_remove(GSList *list, gpointer data)

Для доступа к элементам списка в списке функций 2..9 приведены соответствующие функции. Ни одна из них не меняет структуру списка. "g_slist_foreach()" применяет GFunc к каждому элементу списка. GFunc определена следующим образом: typedef void (*GFunc) (gpointer data, gpointer user_data); При использовании из "g_slist_foreach()", ваша GFunc будет вызвана для каждого "list->data" в списке list, с передачей ваших данных "user_data". "g_slist_foreach()" сравнима с функцией "map" из Scheme.

Например, вы имеете список строк, и, возможно, хотите создать параллельный список из трансформированных некоторым образом строк из первого списка. Вот код, использующий "efficient_append()" из предыдущего примера: typedef struct _AppendContext AppendContext; struct _AppendContext { GSList *list; GSList *list_end; const gchar *append; }; static void append_foreach(gpointer data, gpointer user_data) { AppendContext *ac = (AppendContext *) user_data; gchar *oldstring = (gchar *) data; efficient_append(&ac_list, &ac->list_end, g_strconcat(oldstring, ac->append, NULL)); } GSList *copy_with_append(GSList *list_of_strings, const gchar *append) { AppendContext ac; ac.list = NULL; ac.list_end = NULL; ac.append = append; g_slist_foreach(list_of_strings, append_foreach, &ac); return ac.list; }

glib и Gtk+ широко используют идиому указатель на функцию и данные пользователя. Если у вас есть опыт функционального программирования, это наподобие использования 4#4-выражений для создания клаузы. (Клауза сочетает функцию со средой -- набором пар имя-значение. В этом случае средой являются данные пользователя, которые вы передаете в "append_foreach()", а клаузой -- комбинация из указателя на функцию и данных пользователя.)

Список функций 2..9: Доступ к данным в связном списке
"#include "<glib.h>
GSList *g_slist_find(GSList *list, gpointer data) GSList *g_slist_nth(GSList *list, guint n) gpointer g_slist_nth_data(GSList *list, guint n) GSList *g_slist_last(GSList *list) gint g_slist_index(GSList *list, gpointer data) void g_slist_foreach(GSList *list, GFunc func, gpointer user_data)

Существует также несколько удобных процедур для манипулирования списком, приведенные в списке функций 2..10. За исключением "g_slist_copy()", все эти функции действуют на списки на месте. Это означает, что вы должны присвоить возвращенное значение и забыть о переданном указателе, как вы делаете когда добавляете или удаляете элементы в/из списка. "g_slist_copy()" возвращает уже выделенный в памяти список, поэтому вы можете продолжать пользоваться обоими списками, и в конце концов должны удалить оба списка.

Список функций 2..10: Манипулирование связным списком
"#include "<glib.h>
guint g_slist_length(GSList *list) GSList *g_slist_concat(GSList *list1, GSList *list2) GSList *g_slist_reverse(GSList *list) GSList *g_slist_copy(GSList *list)

И, наконец, существуют некоторые возможности для сортировки списков, показанные в списке функций 2..11. Чтобы воспользоваться ими, вы должны написать GCompareFunc, которая похожа на функцию сравнения в стандартном qsort(). С использованием типов glib она становится такой: typedef gint (*GCompareFunc) (gconstpointer a, gconstpointer b);

Если "a < b", функция должна возвратить отрицательное значение; если "a > b" -- положительное; если "a == b" -- должна возвратить 0.

Как только у вас появилась функция сравнения, вы можете вставлять элемент в уже отсортированный список, или сортировать список целиком. Списки сортируются в порядке возрастания. Вы даже можете переработать вашу GCompareFunc, чтобы находить элементы списка, используя "g_slist_find_custom()". (Предупреждение: GCompareFunc используется несогласованно в glib; иногда glib ожидает предикат равенства вместо функции в "qsort()"-стиле. Однако, использование согласованно внутри спискового API.)

Будьте осторожны с сортированными списками; неправильное их использование может стать очень неэффективным. Например, "g_slist_insert_sorted()" -- это O(n)-операция, но если вы ее используете в цикле для вставки большого количества элементов, цикл замедляется экспоненциально. Лучше добавить все ваши элементы в начало списка, а затем вызвать "g_slist_sort()".

Список функций 2..11: Сортированные списки
"#include "<glib.h>
GSList *g_slist_insert_sorted(GSList *list, gpointer data, GCompareFunc func) GSList *g_slist_sort(GSList *list, GCompareFunc func) GSList *g_slist_find_custom(GSList *list, gpointer data, GCompareFunc func)


Linux Land
2000-09-15