Указатели на функции в структурах
Часть раздела 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.
Задание
ЛегкоДавайте создадим систему Notifier, которая демонстрирует, как структуры могут содержать собственное поведение с помощью указателей на функции.
Вы создадите два файла для организации вашего кода:
notifier.h: Определите тип указателя на функцию с именемNotifyFunc, который принимает параметрconst char*и ничего не возвращает. Затем определите структуруNotifier, которая содержитname(типаconst char*) и указатель на функциюnotifyтипаNotifyFunc.main.c: Включите ваш заголовочный файл и реализуйте две функции уведомления:alert_notify— выводит сообщение в формате:[ALERT] messageinfo_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;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Основы модульного программирования
Заголовочные файлыСтражи включенияИсходные файлыСтатические функцииПовторение: Модульный калькулятор4Инкапсуляция
Концепция непрозрачных указателейОпределение непрозрачных структурГеттеры и сеттерыВалидация в сеттерахИтоги: Секретный ящик2Объекты и методы
Структуры как объектыУказатель 'Self'Константная корректностьУказатель против значенияВспомогательные методыИтоги: Point Manager5Проект: Простой банковский счет
Настройка проектаРеализация счета8Полиморфизм
Указатели на функции в структурахСимуляция методовКонцепция интерфейсовРеализация интерфейсовПолиморфная итерацияПовторение: Greeter3Жизненный цикл объекта
Паттерн «Конструктор»Паттерн «Деструктор»Инициализация в стекеГлубокое копированиеПовторение: String Wrapper6Наследование через композицию
Встраивание структурПравило первого элементаДоступ к элементам родителяUpcastingПовторение: Иерархия фигур9Проект: Рисование фигур
Обзор проектаРеализация круга