Обобщенная обертка
Часть раздела Объектно-ориентированное программирование путешествия по C на Coddy. Урок 50 из 61.
Один void* может хранить любые данные, но у него есть критический недостаток: вы теряете информацию о том, какой тип он содержит. Чтобы создавать действительно полезные обобщённые контейнеры, нам нужно связать указатель на данные с информацией о типе. Здесь и применяется шаблон обобщённой обёртки.
Идея проста: создайте структуру, которая объединяет void* с перечислением, определяющим сохранённый тип. Благодаря этому оболочка всегда знает, как интерпретировать своё содержимое.
typedef enum {
TYPE_INT,
TYPE_STRING
} DataType;
typedef struct {
void* data;
DataType type;
} Wrapper;При хранении данных вы выделяете память для фактического значения и записываете его тип. При извлечении вы проверяете тип перед приведением:
Wrapper wrap_int(int value) {
int* p = malloc(sizeof(int));
*p = value;
return (Wrapper){ .data = p, .type = TYPE_INT };
}
void print_wrapper(Wrapper* w) {
if (w->type == TYPE_INT) {
printf("%d\n", *(int*)w->data);
} else if (w->type == TYPE_STRING) {
printf("%s\n", (char*)w->data);
}
}Этот шаблон является основой для создания типобезопасных обобщённых контейнеров в C. Перечисление выполняет роль тега типа времени выполнения, позволяя вашему коду безопасно принимать решения о том, как обрабатывать хранящиеся данные.
Задание
ЛегкоДавайте создадим типобезопасную обобщённую систему обёрток, которая может хранить либо целое число, либо строку, отслеживая, какой тип она содержит, чтобы впоследствии мы могли безопасно извлекать и выводить данные.
Вы распределите код по трём файлам:
wrapper.h: Определите перечислениеDataTypeсо значениямиTYPE_INTиTYPE_STRING. Затем определите структуруWrapper, которая объединяет указатель данныхvoid*с полем типаDataType. Объявите прототипы функций для создания обёрток и вывода их содержимого.wrapper.c: Реализуйте функциональность обёртки:wrap_int: принимает целое число, выделяет для него память, сохраняет значение и возвращаетWrapperс соответствующим тегом типаwrap_string: принимает строку (char*), выделяет память и копирует строку, а затем возвращаетWrapperс тегом строкового типаprint_wrapper: проверяет тег типа и соответствующим образом выводит данные (целое число как есть, строку как есть)free_wrapper: освобождает динамически выделенные данные внутри обёртки
main.c: Объедините всё вместе. Считайте индикатор типа (iдля целого числа илиsдля строки), а затем значение. Создайте соответствующую обёртку, выведите её содержимое и освободите память.
Ваша программа получит два входных значения:
- Индикатор типа:
iдля целого числа илиsдля строки - Значение, которое нужно обернуть
Пример вывода, если входными значениями являются i и 42:
42Пример вывода, если входными значениями являются s и Hello:
HelloПример вывода, если входными значениями являются i и -100:
-100Пример вывода, если входными значениями являются s и Generic Programming:
Generic ProgrammingНе забудьте использовать защиту от повторного включения в заголовочном файле. Для копирования строк выделите достаточно места для строки и нулевого терминатора с помощью strlen и strcpy.
Попробуйте сами
#include <stdio.h>
#include <string.h>
#include "wrapper.h"
int main() {
char type;
scanf("%c", &type);
getchar(); // поглотить символ новой строки
Wrapper w;
if (type == 'i') {
int value;
scanf("%d", &value);
// TODO: Создать обёртку для целого числа с помощью wrap_int
} else if (type == 's') {
char str[256];
fgets(str, sizeof(str), stdin);
// Удалить завершающий символ новой строки, если он присутствует
str[strcspn(str, "\n")] = '\0';
// TODO: Создать обёртку для строки с помощью wrap_string
}
// TODO: Вывести содержимое обёртки с помощью print_wrapper
// TODO: Освободить память обёртки с помощью free_wrapper
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы модульного программирования
Заголовочные файлыСтражи включенияИсходные файлыСтатические функцииПовторение: Модульный калькулятор4Инкапсуляция
Концепция непрозрачных указателейОпределение непрозрачных структурГеттеры и сеттерыВалидация в сеттерахИтоги: Секретный ящик7Указатели на функции
Объявление указателей на функцииВызов указателей на функцииTypedef для указателей на функцииПередача функций в качестве аргументовИтоги: Диспетчеризация калькулятора10Обобщенные контейнеры
Повторение: указатели voidОбобщенная оберткаОбобщенный обмен (Swap)Обобщенное сравнениеПовторение: обобщенный массив2Объекты и методы
Структуры как объектыУказатель 'Self'Константная корректностьУказатель против значенияВспомогательные методыИтоги: Point Manager5Проект: Простой банковский счет
Настройка проектаРеализация счета3Жизненный цикл объекта
Паттерн «Конструктор»Паттерн «Деструктор»Инициализация в стекеГлубокое копированиеПовторение: String Wrapper6Наследование через композицию
Встраивание структурПравило первого элементаДоступ к элементам родителяUpcastingПовторение: Иерархия фигур9Проект: Рисование фигур
Обзор проектаРеализация кругаРеализация прямоугольникаПолиморфное использованиеКонтейнер фигурПотренируйтесь самостоятельно: Онлайн-компилятор C