Menu
Coddy logo textTech

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.

challenge icon

Aufgabe

Einfach

Lassen 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 namens TaskFunc, der keine Parameter entgegennimmt und nichts zurückgibt. Definieren Sie dann ein Task-Struct, das einen name (ein const char*) und einen run-Funktionszeiger vom Typ TaskFunc enthält.
  • task.c: Implementieren Sie drei verschiedene Task-Funktionen, die unterschiedliche Arten von Arbeit repräsentieren:
    • backup_task — gibt Backing up data... aus
    • cleanup_task — gibt Cleaning up files... aus
    • report_task — gibt Generating report... aus
  • main.c: Führen Sie hier alles zusammen. Erstellen Sie ein Array von Task-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 seiner run-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;
}
quiz iconTeste dich selbst

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