Menu
Coddy logo textTech

Указатели на функции в структурах

Часть раздела Object Oriented Programming путешествия по C на Coddy — урок 38 из 61.

До сих пор мы использовали указатели на функции как отдельные переменные или передавали их в качестве аргументов. Следующим шагом к полиморфизму является встраивание указателя на функцию непосредственно внутрь структуры — это наделяет структуру собственным поведением.

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

typedef void (*PrintFunc)(const char*);

typedef struct {
    const char* name;
    PrintFunc print;  // Указатель на функцию как член структуры
} Printer;

Структура Printer теперь имеет два члена: данные (name) и поведение (print). Чтобы использовать её, вы назначаете функцию указателю во время инициализации:

void loud_print(const char* msg) {
    printf("!!! %s !!!\n", msg);
}

int main() {
    Printer p;
    p.name = "Alert";
    p.print = loud_print;
    
    p.print("Hello");  // Вывод: !!! Hello !!!
    return 0;
}

Обратите внимание, как мы вызываем функцию через структуру: p.print("Hello"). Это очень похоже на вызов метода объекта в других языках. Структура содержит как свои данные, так и функцию, которая с ними работает.

Этот паттерн является основой полиморфизма в C — разным экземплярам могут быть назначены разные функции, что обеспечивает различное поведение для одного и того же типа struct.

challenge icon

Задание

Легко

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

Вы создадите два файла для организации вашего кода:

  • notifier.h: Определите тип указателя на функцию с именем NotifyFunc, который принимает параметр const char* и ничего не возвращает. Затем определите структуру Notifier, которая содержит name (типа const char*) и указатель на функцию notify типа NotifyFunc.
  • main.c: Включите ваш заголовочный файл и реализуйте две функции уведомления:
    • alert_notify — выводит сообщение в формате: [ALERT] message
    • info_notify — выводит сообщение в формате: [INFO] message
    Затем создайте экземпляр Notifier, привяжите соответствующую функцию на основе входных данных и вызовите уведомление через указатель на функцию в структуре.

Ваша программа получит два входных значения: тип уведомления (1 для alert, 2 для info) и сообщение для отображения.

В зависимости от типа, назначьте соответствующую функцию члену notify вашего уведомления, а затем вызовите её через структуру, чтобы отобразить сообщение.

Пример вывода, когда тип равен 1, а сообщение — "System starting":

[ALERT] System starting

Пример вывода, когда тип равен 2, а сообщение — "All systems normal":

[INFO] All systems normal

Не забудьте использовать include guards в заголовочном файле и вызвать функцию через член структуры: n.notify(message)

Шпаргалка

Указатели на функции могут быть встроены непосредственно в структуры, что дает каждому экземпляру свое собственное поведение. Это основа полиморфизма в C.

Определение структуры с указателем на функцию

Сначала определите тип указателя на функцию, а затем включите его в качестве члена структуры:

typedef void (*PrintFunc)(const char*);

typedef struct {
    const char* name;
    PrintFunc print;  // Function pointer as a member
} Printer;

Использование структуры

Назначьте функцию указателю во время инициализации, а затем вызовите ее через структуру:

void loud_print(const char* msg) {
    printf("!!! %s !!!\n", msg);
}

int main() {
    Printer p;
    p.name = "Alert";
    p.print = loud_print;
    
    p.print("Hello");  // Output: !!! Hello !!!
    return 0;
}

Синтаксис p.print("Hello") вызывает функцию через структуру, что похоже на вызов метода объекта в других языках.

Полиморфное поведение

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

Попробуйте сами

#include <stdio.h>
#include "notifier.h"

// TODO: Реализовать функцию alert_notify
// Должна выводить: [ALERT] message


// TODO: Реализовать функцию info_notify
// Должна выводить: [INFO] message


int main() {
    int type;
    char message[256];
    
    // Считать входные данные
    scanf("%d", &type);
    getchar(); // поглотить символ новой строки
    fgets(message, sizeof(message), stdin);
    
    // Удалить завершающий символ новой строки из message, если он есть
    int len = 0;
    while (message[len] != '\0') len++;
    if (len > 0 && message[len-1] == '\n') message[len-1] = '\0';
    
    // TODO: Создать экземпляр Notifier
    
    
    // TODO: В зависимости от type (1 для alert, 2 для info),
    // назначить соответствующую функцию члену notify структуры notifier
    
    
    // TODO: Вызвать уведомление через указатель на функцию в структуре
    // Использовать: n.notify(message)
    
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Object Oriented Programming