Generyczny wrapper
Część sekcji Programowanie obiektowe ścieżki C w Coddy. Lekcja 50 z 61.
Sam wskaźnik void* może przechowywać dowolne dane, ale ma istotną wadę: tracisz informację o tym, jaki typ przechowuje. Aby tworzyć naprawdę użyteczne kontenery generyczne, musimy połączyć wskaźnik do danych z informacją o typie. Właśnie tutaj pojawia się wzorzec Generic Wrapper.
Pomysł jest prosty: utwórz strukturę, która łączy void* z enumem identyfikującym przechowywany typ. Dzięki temu wrapper zawsze wie, jak interpretować swoją zawartość.
typedef enum {
TYPE_INT,
TYPE_STRING
} DataType;
typedef struct {
void* data;
DataType type;
} Wrapper;Podczas przechowywania danych przydzielasz pamięć na rzeczywistą wartość i zapisujesz jej typ. Podczas pobierania sprawdzasz typ przed rzutowaniem:
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);
}
}Ten wzorzec stanowi podstawę tworzenia bezpiecznych typowo, generycznych kontenerów w C. Wyliczenie pełni funkcję znacznika typu w czasie wykonywania, pozwalając kodowi podejmować bezpieczne decyzje dotyczące obsługi przechowywanych danych.
Wyzwanie
ŁatwyZbudujmy bezpieczny typowo, generyczny system opakowujący, który może przechowywać liczbę całkowitą albo ciąg znaków, śledząc, jaki typ przechowuje, abyśmy mogli później bezpiecznie pobrać i wypisać dane.
Podzielisz swój kod na trzy pliki:
wrapper.h: Zdefiniuj typ wyliczeniowyDataTypez wartościamiTYPE_INTiTYPE_STRING. Następnie zdefiniuj strukturęWrapper, która łączy wskaźnik danychvoid*z polem typuDataType. Zadeklaruj prototypy funkcji tworzących opakowania i wypisujących ich zawartość.wrapper.c: Zaimplementuj funkcjonalność opakowania:wrap_int: przyjmuje liczbę całkowitą, przydziela dla niej pamięć, zapisuje wartość i zwraca obiekt Wrapper z odpowiednią etykietą typuwrap_string: przyjmuje ciąg znaków (char*), przydziela pamięć i kopiuje ciąg, a następnie zwraca obiekt Wrapper z etykietą typu ciągu znakówprint_wrapper: sprawdza etykietę typu i odpowiednio wypisuje dane (liczbę całkowitą bez zmian, ciąg znaków bez zmian)free_wrapper: zwalnia dynamicznie przydzielone dane znajdujące się w opakowaniu
main.c: Połącz wszystko w całość. Odczytaj wskaźnik typu (idla liczby całkowitej,sdla ciągu znaków), a następnie wartość. Utwórz odpowiednie opakowanie, wypisz jego zawartość i zwolnij pamięć.
Program otrzyma dwa dane wejściowe:
- Wskaźnik typu:
idla liczby całkowitej lubsdla ciągu znaków - Wartość do opakowania
Przykładowe wyjście, gdy danymi wejściowymi są i i 42:
42Przykładowe wyjście, gdy danymi wejściowymi są s i Hello:
HelloPrzykładowe wyjście, gdy danymi wejściowymi są i i -100:
-100Przykładowe wyjście, gdy danymi wejściowymi są s i Generic Programming:
Generic ProgrammingPamiętaj, aby użyć strażników dołączania w pliku nagłówkowym. Przy kopiowaniu ciągu znaków przydziel wystarczającą ilość miejsca na ciąg wraz z terminatorem null, używając strlen i strcpy.
Spróbuj swoich sił
#include <stdio.h>
#include <string.h>
#include "wrapper.h"
int main() {
char type;
scanf("%c", &type);
getchar(); // pobierz znak nowej linii
Wrapper w;
if (type == 'i') {
int value;
scanf("%d", &value);
// TODO: Utwórz obiekt opakowujący liczbę całkowitą za pomocą wrap_int
} else if (type == 's') {
char str[256];
fgets(str, sizeof(str), stdin);
// Usuń końcowy znak nowej linii, jeśli występuje
str[strcspn(str, "\n")] = '\0';
// TODO: Utwórz obiekt opakowujący ciąg znaków za pomocą wrap_string
}
// TODO: Wypisz zawartość obiektu opakowującego za pomocą print_wrapper
// TODO: Zwolnij pamięć obiektu opakowującego za pomocą free_wrapper
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania modularnego
Pliki nagłówkoweZabezpieczenia przed wielokrotnym dołączeniemPliki źródłoweFunkcje statycznePodsumowanie: kalkulator modularny4Enkapsulacja
Koncepcja niejawnych wskaźnikówDefiniowanie niejawnych strukturGettery i setteryWalidacja w setterachPowtórka: tajemnicze pudełko7Wskaźniki do funkcji
Deklarowanie wskaźników do funkcjiWywoływanie funkcji przez wskaźnikiTypedef dla wskaźników do funkcjiPrzekazywanie funkcji jako argumentówPodsumowanie: wybór działania kalkulatora10Kontenery generyczne
Powtórzenie: wskaźniki voidGeneryczny wrapperGeneryczna zamianaGeneryczne porównywaniePowtórzenie: tablica generyczna2Obiekty i metody
Struktury jako obiektyWskaźnik „self”Poprawność constWskaźnik czy wartośćMetody pomocniczePodsumowanie: menedżer punktów5Projekt: Proste konto bankowe
Konfiguracja projektuImplementacja konta8Polimorfizm
Wskaźniki do funkcji w strukturachSymulowanie metodKoncepcja interfejsuImplementowanie interfejsówIteracja polimorficznaPodsumowanie: Greeter11Wzorce projektowe w C
Wzorzec SingletonWzorzec fabrykiWzorzec iteratoraPodsumowanie: fabryka loggera3Cykl życia obiektu
Wzorzec konstruktoraWzorzec destruktoraInicjalizacja na stosieKopia głębokaPodsumowanie: klasa opakowująca ciąg znaków6Dziedziczenie przez kompozycję
Osadzanie strukturZasada pierwszego elementuDostęp do elementów klasy nadrzędnejKonwersja w górę hierarchiiPodsumowanie: hierarchia kształtów9Projekt: Rysownik kształtów
Przegląd projektuImplementacja kołaImplementacja prostokątaZastosowanie polimorfizmuKontener kształtówPoćwicz samodzielnie: Kompilator C online