Итератор-коллбек представляет альтернативу стандартному итератору, который описывался в прошлой теме, он также позволяет перебирать элементы структуры данных с последовательным доступом и также позволяет скрыть от внешнего кода детали реализации прохода по элементам набора и логику возвращения данных.
Рассмотрим суть итератора-коллбека. В API определяется указатель на функцию, которая в качестве параметра принимает перебираемый элемент из набора данных:
/* API итератора */ // указатель на функцию, которая получает перебираемый элемент typedef void (*CALLBACK)(void* element); // Функция перебора объектов, которая вызывает коллбек-callback void iterate(CALLBACK callback);
И также определяется функция перебора, которая принимает в качестве параметра вышеопределенный указатель на функцию, то есть коллбек. При необходимости обе функции могут принимать дополнительные параметры.
Клиент - внешний код определяет функцию-коллбек и передает ее в вызов функции перебора:
/* Код клиента */ // функция-коллбек, которая принимает перебираемый элемент void some_action(void* element) { // Операции над элементом } int main(void) { // вызываем функцию перебора и передаем в нее коллбек iterate(some_action); }
Таким образом, реализация итератора также скрывается от внешнего кода. Благодаря чему мы можем в будущем изменить структуру данных, изменить реализацию функции перебора, но код клиента не изменится - он также будет использовать функцию перебора и передавать в нее функцию-коллбек. Кроме того, от внешнего кода скрыто строение структуры данных, клиент получает только те данные, которые мы ему предоставим и избегаем нежелательнного доступа к данным. При этом для клиента упрощается работа - в отличие от использования стандартного итератора клиент не должен вызывать функции создания и удаления итератора.
Рассмотрим небольшой пример. Пусть в заголовочном файле account.h будет определен API итератора:
// указатель на функцию, которая получает перебираемый элемент typedef void (*CALLBACK)(char* username); // Функция перебора объектов, которая вызывает коллбек-callback void iterateUserNames(CALLBACK callback); // создание списка void createAccountList(void); // удаление списка void deleteAccountList(void);
Здесь определена указатель на функцию-коллбек, которая имеет тип void и в качестве параметра принимает строку - указатель char*. И также определена функция перебора interateUserNames, которая принимает функцию-коллбек.
Чтобы у нас был некоторый набор данных для теста также объявлена функци создания списка createAccountList, а для удаления списка - функция deleteAccountList.
В файле account.c определим реализацию перебора списка:
#include <stdio.h> #include <stdlib.h> #include "account.h" // структура данных struct ACCOUNT { char* username; char* password; struct ACCOUNT* next; }; // список объектов - указатель на первый элемент static struct ACCOUNT* accountList; // перебираем элементы списка void iterateUserNames(CALLBACK callback) { struct ACCOUNT* current = accountList; // текущий перебираемый элемент списка while(current) // пока элемент не будет равен NULL { callback(current->username); // вызываем коллбек current = current->next; // получаем следующий элемент списка } } // создаем список с начальными данными void createAccountList() { struct ACCOUNT* tom = malloc(sizeof(struct ACCOUNT)); tom->username ="Tomas"; tom->password = "qwerty"; accountList = tom; struct ACCOUNT* bob = malloc(sizeof(struct ACCOUNT)); bob->username ="Bob"; bob->password = "12345"; tom->next = bob; struct ACCOUNT* tim = malloc(sizeof(struct ACCOUNT)); tim->username ="Tim"; tim->password = "45678"; bob->next = tim; tim->next = 0; } // создаем список с начальными данными void deleteAccountList() { while(accountList) { free(accountList); accountList = accountList->next; } }
Здесь сами данные представлены структурой ACCOUNT, которая хранит имя и пароль пользователя. Кроме того, с поля свойства next она указывает на следующий элемент списка. Также определен указатель этой структуры accountList, который представляет первый элемент и по цепочке весь список.
Вспомогательные функции createAccountList и deleteAccountList создают и удаляют список объектов ACCOUNT. А основная работа итератора происходит в функции iterateUserNames, которая в качестве параметра принимает функцию-коллбек. Допустим, пароль пользователя представляет чувствительные данные, к которым внешний код не должен иметь доступ. Поэтому при переборе будет отдавать клиенту только имя пользователя. Для перебора просто проходит по цепочке по всем элементам списка, начиная с первого элемента accountList, пока не дойдем до NULL, что будет символизировать конец списка:
void iterateUserNames(CALLBACK callback) { struct ACCOUNT* current = accountList; // текущий перебираемый элемент списка while(current) // пока элемент не будет равен NULL { callback(current->username); // вызываем коллбек current = current->next; // получаем следующий элемент списка } }
В цикле получаем поле username из каждой структуры ACCOUNT и передаем его в функцию-коллбек. Что в этой функции будет происходить, мы не знаем.
Для тестирования итератора определим следующий файл app.c:
#include <stdio.h> #include "account.h" void printUserName(char* username) { printf("%s\n", username); } void printNameT(char* username) { if(username[0]=='T') // если слово начинается на букву T printf("%s\n", username); } int main(void) { createAccountList(); // создаем данные iterateUserNames(printUserName); // выводим все имена пользователей printf("\n"); // для разделения вывода iterateUserNames(printNameT); // выводим только те имена, которые начинаются на Т deleteAccountList(); // удаляем данные }
Для тестирования здесь определены две функции-коллбека, которые по возвращаемому типу и параметрам соответствуют определению указателя CALLBACK из API. Так, функция printUserName принимает строку и выводит ее на консоль. Другая функция - printNameT также выводит строку на консоль, но только если строка начинается на букву T.
Затем вызываем функцию перебора interateUserNames и передаем в нее определенный коллбек:
iterateUserNames(printUserName); iterateUserNames(printNameT);
Пример компиляции и работы программы:
c:\C>gcc -Wall -pedantic app.c account.c -o app & app Tomas Bob Tim Tomas Tim c:\C>