Menu
Coddy logo textTech

Itération polymorphique

Fait partie de la section Object Oriented Programming du Journey C de Coddy — leçon 42 sur 61.

Vous avez appris à créer des interfaces et à les implémenter avec différentes fonctions concrètes. Le véritable avantage apparaît lorsque vous pouvez traiter une collection d'objets différents de manière uniforme — en itérant à travers eux et en appelant le même pointeur de fonction sur chacun, quel que soit le comportement spécifique configuré.

Imaginez que vous ayez un tableau de structures Action, chacune ayant sa propre fonction execute assignée :

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"); }

Vous pouvez créer un tableau de ces actions, chacune reliée à une fonction différente, puis boucler sur celles-ci avec une simple boucle for :

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;
}

La boucle ne sait pas et ne se soucie pas de ce que chaque action fait en interne. Elle appelle simplement execute() sur chaque élément.

C'est le polymorphisme en action — le même code gère des objets ayant des comportements complètement différents. L'ajout d'un nouveau type d'action ne nécessite aucune modification de la logique d'itération ; il suffit d'ajouter un autre élément au tableau.

challenge icon

Défi

Facile

Construisons un système de gestion de Task qui démontre l'itération polymorphe — le traitement d'une collection d'objets qui partagent une interface commune mais exécutent des comportements différents.

Vous organiserez votre code sur trois fichiers :

  • task.h : Définissez votre interface de tâche ici. Créez un type de pointeur de fonction appelé TaskFunc qui ne prend aucun paramètre et ne renvoie rien. Définissez ensuite une structure Task contenant un name (un const char*) et un pointeur de fonction run de type TaskFunc.
  • task.c : Implémentez trois fonctions de tâche différentes qui représentent différents types de travail :
    • backup_task — affiche Backing up data...
    • cleanup_task — affiche Cleaning up files...
    • report_task — affiche Generating report...
  • main.c : Rassemblez tout ici. Créez un tableau de structures Task, chacune reliée à une fonction de tâche différente. Ensuite, parcourez le tableau et exécutez chaque tâche de manière polymorphe — pour chaque tâche, affichez son nom suivi de l'appel de sa fonction run.

Votre programme recevra une seule entrée : le nombre de tâches à exécuter (1, 2 ou 3).

Créez un tableau contenant exactement trois tâches dans cet ordre : une tâche de sauvegarde nommée Backup, une tâche de nettoyage nommée Cleanup, et une tâche de rapport nommée Report. En fonction du nombre saisi, parcourez seulement ce nombre de tâches depuis le début du tableau.

Pour chaque tâche de votre itération, affichez le nom de la tâche suivi d'un deux-points et d'un espace, puis appelez sa fonction run.

Exemple de sortie lorsque l'entrée est 2 :

Backup: Backing up data...
Cleanup: Cleaning up files...

Exemple de sortie lorsque l'entrée est 3 :

Backup: Backing up data...
Cleanup: Cleaning up files...
Report: Generating report...

L'idée clé est que votre boucle n'a pas besoin de savoir ce que fait chaque tâche — elle affiche simplement le nom et appelle run() sur chaque élément. Le même code d'itération gère tous les types de tâches de manière uniforme. N'oubliez pas d'utiliser des gardes d'inclusion dans votre fichier d'en-tête.

Aide-mémoire

Vous pouvez itérer à travers des collections d'objets qui partagent une interface commune, en appelant le même pointeur de fonction sur chaque élément, quelle que soit son implémentation spécifique. C'est le polymorphisme — traiter différents objets de manière uniforme.

Définissez un type de pointeur de fonction et une structure qui l'utilise :

typedef void (*ExecuteFunc)(void);

typedef struct {
    const char* name;
    ExecuteFunc execute;
} Action;

Créez des fonctions concrètes qui correspondent à la signature :

void jump(void) { printf("Jumping!\n"); }
void run(void)  { printf("Running!\n"); }
void rest(void) { printf("Resting...\n"); }

Construisez un tableau de structures, chacune reliée à une fonction différente, puis parcourez-les :

Action actions[3] = {
    { "Jump", jump },
    { "Run", run },
    { "Rest", rest }
};

for (int i = 0; i < 3; i++) {
    printf("%s: ", actions[i].name);
    actions[i].execute();
}

La boucle n'a pas besoin de savoir ce que chaque action fait en interne — elle appelle simplement le pointeur de fonction sur chaque élément. L'ajout de nouveaux types ne nécessite aucune modification de la logique d'itération.

Essayez vous-même

#include <stdio.h>
#include "task.h"

// Déclarer les fonctions de tâche implémentées dans task.c
void backup_task(void);
void cleanup_task(void);
void report_task(void);

int main() {
    int count;
    scanf("%d", &count);
    
    // TODO: Créer un tableau de 3 structures Task dans cet ordre :
    // 1. Tâche de sauvegarde nommée "Backup" utilisant backup_task
    // 2. Tâche de nettoyage nommée "Cleanup" utilisant cleanup_task
    // 3. Tâche de rapport nommée "Report" utilisant report_task
    
    // TODO: Itérer à travers 'count' tâches du tableau
    // Pour chaque tâche, afficher : "<name>: " puis appeler sa fonction run
    
    return 0;
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Object Oriented Programming