Generischer Wrapper
Teil des Abschnitts Object Oriented Programming der C-Journey von Coddy — Lektion 50 von 61.
Ein void* allein kann beliebige Daten speichern, hat aber einen entscheidenden Nachteil: Man verliert die Information darüber, welchen Typ es enthält. Um wirklich nützliche generische Container zu erstellen, müssen wir den Datenzeiger mit Typinformationen verknüpfen. Hier kommt das Generic Wrapper-Muster ins Spiel.
Die Idee ist einfach: Erstellen Sie ein Struct, das ein void* mit einem Enum bündelt, das den gespeicherten Typ identifiziert. Auf diese Weise weiß der Wrapper immer, wie er seinen Inhalt interpretieren soll.
typedef enum {
TYPE_INT,
TYPE_STRING
} DataType;
typedef struct {
void* data;
DataType type;
} Wrapper;Beim Speichern von Daten reservieren Sie Speicherplatz für den eigentlichen Wert und zeichnen dessen Typ auf. Beim Abrufen überprüfen Sie den Typ vor der Typumwandlung:
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);
}
}Dieses Muster ist die Grundlage für den Aufbau typsicherer generischer Container in C. Das enum fungiert als Laufzeit-Typ-Tag, sodass Ihr Code sichere Entscheidungen darüber treffen kann, wie die gespeicherten Daten zu handhaben sind.
Aufgabe
EinfachLassen Sie uns ein typsicheres generisches Wrapper-System erstellen, das entweder eine Ganzzahl oder eine Zeichenfolge speichern kann und dabei den Typ verfolgt, damit wir die Daten später sicher abrufen und ausgeben können.
Sie werden Ihren Code in drei Dateien organisieren:
wrapper.h: Definieren Sie einDataType-Enum mit den WertenTYPE_INTundTYPE_STRING. Definieren Sie dann eineWrapper-Struktur, die einenvoid*-Datenzeiger mit einemDataType-Feld bündelt. Deklarieren Sie die Funktionsprototypen zum Erstellen von Wrappern und zum Ausgeben ihres Inhalts.wrapper.c: Implementieren Sie die Wrapper-Funktionalität:wrap_int— nimmt eine Ganzzahl entgegen, reserviert Speicher dafür, speichert den Wert und gibt einen Wrapper mit dem entsprechenden Typ-Tag zurückwrap_string— nimmt eine Zeichenfolge (char*) entgegen, reserviert Speicher, kopiert die Zeichenfolge und gibt einen Wrapper mit dem Typ-Tag für Zeichenfolgen zurückprint_wrapper— prüft das Typ-Tag und gibt die Daten entsprechend aus (Ganzzahl wie sie ist, Zeichenfolge wie sie ist)free_wrapper— gibt den dynamisch reservierten Speicher innerhalb des Wrappers frei
main.c: Führen Sie alles zusammen. Lesen Sie einen Typ-Indikator (ifür Ganzzahl,sfür Zeichenfolge), gefolgt vom Wert. Erstellen Sie den entsprechenden Wrapper, geben Sie seinen Inhalt aus und geben Sie den Speicher frei.
Ihr Programm erhält zwei Eingaben:
- Einen Typ-Indikator:
ifür Ganzzahl odersfür Zeichenfolge - Den zu wrappenden Wert
Beispielausgabe, wenn die Eingaben i und 42 sind:
42Beispielausgabe, wenn die Eingaben s und Hello sind:
HelloBeispielausgabe, wenn die Eingaben i und -100 sind:
-100Beispielausgabe, wenn die Eingaben s und Generic Programming sind:
Generic ProgrammingDenken Sie daran, Include-Guards in Ihrer Header-Datei zu verwenden. Verwenden Sie für das Kopieren von Zeichenfolgen strlen und strcpy, um genügend Platz für die Zeichenfolge plus den Null-Terminator zu reservieren.
Spickzettel
Das Generic Wrapper-Muster kombiniert einen void*-Pointer mit Typinformationen, um typsichere generische Container zu erstellen. Dies ermöglicht es Ihnen, jeden Datentyp zu speichern und gleichzeitig das Wissen darüber zu behalten, welcher Typ gespeichert ist.
Definieren Sie ein Enum, um die gespeicherten Typen zu identifizieren:
typedef enum {
TYPE_INT,
TYPE_STRING
} DataType;Erstellen Sie ein Struct, das den Daten-Pointer mit dem Typ-Tag bündelt:
typedef struct {
void* data;
DataType type;
} Wrapper;Wenn Sie Daten speichern, reservieren Sie Speicherplatz und protokollieren Sie den Typ:
Wrapper wrap_int(int value) {
int* p = malloc(sizeof(int));
*p = value;
return (Wrapper){ .data = p, .type = TYPE_INT };
}Überprüfen Sie beim Abrufen von Daten das Typ-Tag vor dem Casting:
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);
}
}Das Enum fungiert als Laufzeit-Typ-Tag und ermöglicht eine sichere Typprüfung sowie eine angemessene Handhabung der gespeicherten Daten.
Probier es selbst
#include <stdio.h>
#include <string.h>
#include "wrapper.h"
int main() {
char type;
scanf("%c", &type);
getchar(); // Zeilenumbruch konsumieren
Wrapper w;
if (type == 'i') {
int value;
scanf("%d", &value);
// TODO: Erstelle einen Integer-Wrapper mit wrap_int
} else if (type == 's') {
char str[256];
fgets(str, sizeof(str), stdin);
// Entferne den abschließenden Zeilenumbruch, falls vorhanden
str[strcspn(str, "\n")] = '\0';
// TODO: Erstelle einen String-Wrapper mit wrap_string
}
// TODO: Gib den Inhalt des Wrappers mit print_wrapper aus
// TODO: Gib den Speicher des Wrappers mit free_wrapper frei
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Object Oriented Programming
1Grundlagen der modularen Programmierung
Header-DateienInclude GuardsQuelldateienStatische FunktionenWiederholung: Modularer Taschenrechner4Kapselung
Konzept der Opaque PointersOpaque Structs definierenGetter und SetterValidierung in SetternRückblick: Die geheime Box7Funktionszeiger
Funktionszeiger deklarierenFunktionszeiger aufrufenTypedef für FunktionszeigerFunktionen als Argumente übergebenZusammenfassung: Calculator Dispatch10Generische Container
Wiederholung: Void-PointerGenerischer WrapperGenerisches SwapGenerisches VergleichenWiederholung: Generisches Array2Objekte und Methoden
Structs als ObjekteDer 'Self'-PointerConst-CorrectnessPointer vs. WertHilfsmethodenZusammenfassung: Point Manager5Projekt: Einfaches Bankkonto
Projekt-SetupImplementierung des Kontos3Objekt-Lebenszyklus
Konstruktor-MusterDestruktor-MusterStack-InitialisierungTiefe KopieRückblick: String-Wrapper6Vererbung durch Komposition
Struct-EinbettungDie First-Member-RegelZugriff auf Parent-MemberUpcastingRückblick: Formenhierarchie9Projekt: Formen-Zeichner
ProjektübersichtKreis-Implementierung