Polymorphe Iteration
Teil des Abschnitts Object Oriented Programming der C-Journey von Coddy — Lektion 42 von 61.
Sie haben gelernt, Interfaces zu erstellen und sie mit verschiedenen konkreten Funktionen zu implementieren. Der wahre Vorteil zeigt sich, wenn Sie eine Sammlung verschiedener Objekte einheitlich behandeln können — indem Sie diese iterieren und denselben Funktionszeiger für jedes aufrufen, unabhängig davon, welches spezifische Verhalten hinterlegt ist.
Stellen Sie sich vor, Sie haben ein Array von Action Structs, von denen jedes seine eigene execute Funktion zugewiesen hat:
typedef void (*ExecuteFunc)(void);
typedef struct {
const char* name;
ExecuteFunc execute;
} Action;
void jump(void) { printf("Jumping!\n"); }
void run(void) { printf("Running!\n"); }
void rest(void) { printf("Resting...\n"); }Sie können ein Array dieser Aktionen erstellen, die jeweils mit einer anderen Funktion verknüpft sind, und diese dann mit einer einfachen for-Schleife durchlaufen:
int main() {
Action actions[3] = {
{ "Jump", jump },
{ "Run", run },
{ "Rest", rest }
};
for (int i = 0; i < 3; i++) {
printf("%s: ", actions[i].name);
actions[i].execute();
}
return 0;
}Die Schleife weiß nicht und kümmert sich nicht darum, was jede Aktion intern tut. Sie ruft einfach execute() für jedes Element auf.
Das ist Polymorphismus in Aktion — derselbe Code verarbeitet Objekte mit völlig unterschiedlichen Verhaltensweisen. Das Hinzufügen eines neuen Aktionstyps erfordert keine Änderungen an der Iterationslogik; Sie fügen einfach ein weiteres Element zum Array hinzu.
Aufgabe
EinfachLassen Sie uns ein Task-Managementsystem bauen, das polymorphe Iteration demonstriert — das Verarbeiten einer Sammlung von Objekten, die eine gemeinsame Schnittstelle teilen, aber unterschiedliche Verhaltensweisen ausführen.
Sie werden Ihren Code über drei Dateien organisieren:
task.h: Definieren Sie hier Ihre Task-Schnittstelle. Erstellen Sie einen Funktionszeigertyp namensTaskFunc, der keine Parameter entgegennimmt und nichts zurückgibt. Definieren Sie dann einTask-Struct, das einenname(einconst char*) und einenrun-Funktionszeiger vom TypTaskFuncenthält.task.c: Implementieren Sie drei verschiedene Task-Funktionen, die unterschiedliche Arten von Arbeit repräsentieren:backup_task— gibtBacking up data...auscleanup_task— gibtCleaning up files...ausreport_task— gibtGenerating report...aus
main.c: Führen Sie hier alles zusammen. Erstellen Sie ein Array vonTask-Structs, von denen jedes mit einer anderen Task-Funktion verknüpft ist. Iterieren Sie dann durch das Array und führen Sie jeden Task polymorph aus — geben Sie für jeden Task seinen Namen aus, gefolgt vom Aufruf seinerrun-Funktion.
Ihr Programm erhält eine einzige Eingabe: die Anzahl der Tasks, die ausgeführt werden sollen (1, 2 oder 3).
Erstellen Sie ein Array, das genau drei Tasks in dieser Reihenfolge enthält: einen Backup-Task namens Backup, einen Cleanup-Task namens Cleanup und einen Report-Task namens Report. Iterieren Sie basierend auf der eingegebenen Anzahl nur durch so viele Tasks vom Anfang des Arrays an.
Geben Sie für jeden Task in Ihrer Iteration den Namen des Tasks aus, gefolgt von einem Doppelpunkt und einem Leerzeichen, und rufen Sie dann seine run-Funktion auf.
Beispielausgabe, wenn die Eingabe 2 ist:
Backup: Backing up data...
Cleanup: Cleaning up files...Beispielausgabe, wenn die Eingabe 3 ist:
Backup: Backing up data...
Cleanup: Cleaning up files...
Report: Generating report...Die entscheidende Erkenntnis ist, dass Ihre Schleife nicht wissen muss, was jeder Task tut — sie gibt einfach den Namen aus und ruft run() für jedes Element auf. Derselbe Iterationscode verarbeitet alle Task-Typen einheitlich. Denken Sie daran, Include-Guards in Ihrer Header-Datei zu verwenden.
Spickzettel
Sie können Sammlungen von Objekten durchlaufen, die eine gemeinsame Schnittstelle teilen, und dabei denselben Funktionszeiger auf jedes Element aufrufen, unabhängig von dessen spezifischer Implementierung. Dies ist Polymorphie – die einheitliche Behandlung verschiedener Objekte.
Definieren Sie einen Funktionszeigertyp und ein Struct, das diesen verwendet:
typedef void (*ExecuteFunc)(void);
typedef struct {
const char* name;
ExecuteFunc execute;
} Action;Erstellen Sie konkrete Funktionen, die der Signatur entsprechen:
void jump(void) { printf("Jumping!\n"); }
void run(void) { printf("Running!\n"); }
void rest(void) { printf("Resting...\n"); }Erstellen Sie ein Array von Structs, von denen jedes mit einer anderen Funktion verknüpft ist, und iterieren Sie dann durch diese:
Action actions[3] = {
{ "Jump", jump },
{ "Run", run },
{ "Rest", rest }
};
for (int i = 0; i < 3; i++) {
printf("%s: ", actions[i].name);
actions[i].execute();
}Die Schleife muss nicht wissen, was jede Aktion intern tut – sie ruft einfach den Funktionszeiger für jedes Element auf. Das Hinzufügen neuer Typen erfordert keine Änderungen an der Iterationslogik.
Probier es selbst
#include <stdio.h>
#include "task.h"
// Deklariere die in task.c implementierten Task-Funktionen
void backup_task(void);
void cleanup_task(void);
void report_task(void);
int main() {
int count;
scanf("%d", &count);
// TODO: Erstelle ein Array aus 3 Task-Structs in dieser Reihenfolge:
// 1. Backup-Task namens "Backup" unter Verwendung von backup_task
// 2. Cleanup-Task namens "Cleanup" unter Verwendung von cleanup_task
// 3. Report-Task namens "Report" unter Verwendung von report_task
// TODO: Iteriere durch 'count' Tasks aus dem Array
// Gib für jeden Task "<name>: " aus und rufe dann seine run-Funktion auf
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 Kontos8Polymorphismus
Funktionszeiger in StructsMethoden simulierenDas Interface-KonzeptInterfaces implementierenPolymorphe IterationWiederholung: Greeter3Objekt-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