Opaque Structs definieren
Teil des Abschnitts Object Oriented Programming der C-Journey von Coddy — Lektion 18 von 61.
Nachdem Sie nun das Konzept der opaken Pointer verstanden haben, schauen wir uns an, wie man das Muster vervollständigt. Der Header deklariert, dass ein Typ existiert, aber die Quelldatei ist der Ort, an dem Sie definieren, was sich tatsächlich darin befindet.
Hier ist der vollständige Aufbau. Zuerst der Header mit nur der Vorwärtsdeklaration:
// counter.h
#ifndef COUNTER_H
#define COUNTER_H
typedef struct Counter Counter; // Unvollständiger Typ
Counter *create_counter(int start);
void free_counter(Counter *c);
#endif
Anschließend enthält die Quelldatei die tatsächliche Struct-Definition:
// counter.c
#include "counter.h"
#include <stdlib.h>
struct Counter {
int value; // Von außen verborgen!
int step;
};
Counter *create_counter(int start) {
Counter *c = malloc(sizeof(Counter));
c->value = start;
c->step = 1;
return c;
}
void free_counter(Counter *c) {
free(c);
}
Der entscheidende Punkt: struct Counter { ... } erscheint nur in der .c-Datei. Jede Datei, die counter.h einbindet, weiß, dass Counter existiert, kann aber value oder step nicht sehen. Der Versuch, c->value in main.c zu schreiben, würde einen Compiler-Fehler verursachen – die Member sind wirklich unsichtbar.
Diese Trennung ist die Grundlage der Kapselung in C. Der nächste Schritt besteht darin, einen kontrollierten Zugriff über Getter- und Setter-Funktionen bereitzustellen.
Aufgabe
EinfachLassen Sie uns ein Timer-Modul erstellen, das das Opaque-Pointer-Muster demonstriert. Sie werden üben, die Struct-Definition innerhalb der Quelldatei zu verbergen, sodass ihre internen Mitglieder für jeglichen Code, der Ihr Modul verwendet, völlig unsichtbar sind.
Sie werden drei Dateien erstellen:
timer.h: Dies ist Ihre öffentliche Schnittstelle. Deklarieren Sie einen opakenTimer-Typ nur mit einer Vorwärtsdeklaration – kein Struct-Body hier! Deklarieren Sie außerdem zwei Funktionen:create_timer(nimmt eine Ganzzahl für Sekunden entgegen und gibt einen Timer-Pointer zurück) undfree_timer(nimmt einen Timer-Pointer entgegen und räumt auf). Verwenden Sie Include-Guards mit dem SymbolTIMER_H.timer.c: Hier lebt das Geheimnis. Definieren Sie die tatsächlichestruct Timermit zwei verborgenen Mitgliedern:int secondsundint running(verwenden Sie 1 für laufend, 0 für gestoppt). Implementieren Sie Ihren Konstruktor, um das Struct zu allozieren,secondsmit dem bereitgestellten Wert zu initialisieren undrunningauf 1 zu setzen. Implementieren Sie den Destruktor, um den Speicher freizugeben.main.c: Verwenden Sie Ihr Timer-Modul, um einen Timer zu erstellen und zu bestätigen, dass er erfolgreich erstellt wurde. Da die Struct-Mitglieder verborgen sind, können Sie nicht direkt auf sie zugreifen – das ist der ganze Punkt! Geben Sie einfach eine Nachricht aus, die die Erstellung bestätigt, geben Sie dann den Timer frei und geben Sie eine Bereinigungsnachricht aus.
Sie erhalten eine Eingabe: die Anzahl der Sekunden (eine Ganzzahl), mit der der Timer initialisiert werden soll.
Erstellen Sie in Ihrer Hauptdatei einen Timer mit dem angegebenen Sekundenwert. Geben Sie eine Erstellungsnachricht aus, geben Sie dann den Timer frei und geben Sie eine Bestätigung aus.
Geben Sie die Ausgabe in diesem Format aus:
Timer created
Timer freedDie entscheidende Erkenntnis hierbei ist, dass main.c mit Timer-Pointern arbeiten kann, aber keine Möglichkeit hat, direkt auf seconds oder running zuzugreifen. Der Versuch, t->seconds in main.c zu schreiben, würde zu einem Compilerfehler führen, da der Struct-Body nur in timer.c definiert ist. Dies ist echte Kapselung – die Implementierungsdetails sind für die Benutzer Ihres Moduls vollständig verborgen.
Spickzettel
Das Opaque-Pointer-Muster trennt die Deklaration eines Typs von seiner Definition und verbirgt so die Implementierungsdetails.
Die Header-Datei enthält nur eine Vorwärtsdeklaration:
// counter.h
#ifndef COUNTER_H
#define COUNTER_H
typedef struct Counter Counter; // Unvollständiger Typ
Counter *create_counter(int start);
void free_counter(Counter *c);
#endif
Die Quelldatei enthält die eigentliche Struct-Definition:
// counter.c
#include "counter.h"
#include <stdlib.h>
struct Counter {
int value; // Von außen verborgen
int step;
};
Counter *create_counter(int start) {
Counter *c = malloc(sizeof(Counter));
c->value = start;
c->step = 1;
return c;
}
void free_counter(Counter *c) {
free(c);
}
Die Struct-Definition erscheint nur in der .c-Datei. Dateien, die den Header einbinden, wissen, dass der Typ existiert, können aber nicht direkt auf seine Member zugreifen. Dies ermöglicht echte Kapselung in C.
Probier es selbst
#include <stdio.h>
#include "timer.h"
int main() {
int seconds;
scanf("%d", &seconds);
// TODO: Erstelle einen Timer mit dem bereitgestellten seconds-Wert
// TODO: Gib "Timer created" aus
// TODO: Gib den Timer frei
// TODO: Gib "Timer freed" aus
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 Box2Objekte 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