Menu
CoddyTech

Обобщенная обертка

Часть раздела Объектно-ориентированное программирование путешествия по 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. Перечисление выполняет роль тега типа времени выполнения, позволяя вашему коду безопасно принимать решения о том, как обрабатывать хранящиеся данные.

challenge icon

Задание

Легко

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

Вы распределите код по трём файлам:

  • 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 для строки), а затем значение. Создайте соответствующую обёртку, выведите её содержимое и освободите память.

Ваша программа получит два входных значения:

  1. Индикатор типа: i для целого числа или s для строки
  2. Значение, которое нужно обернуть

Пример вывода, если входными значениями являются 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;
}
quiz iconПроверьте себя

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

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор C