Итератор-коллбек

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

Итератор-коллбек представляет альтернативу стандартному итератору, который описывался в прошлой теме, он также позволяет перебирать элементы структуры данных с последовательным доступом и также позволяет скрыть от внешнего кода детали реализации прохода по элементам набора и логику возвращения данных.

Рассмотрим суть итератора-коллбека. В 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>
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850