Polymorphic Iteration
Part of the Object Oriented Programming section of Coddy's C journey — lesson 42 of 61.
You've learned to create interfaces and implement them with different concrete functions. The true payoff comes when you can treat a collection of different objects uniformly — iterating through them and calling the same function pointer on each, regardless of what specific behavior is wired in.
Imagine you have an array of Action structs, each with its own execute function assigned:
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"); }You can create an array of these actions, each wired to a different function, then loop through them with a simple for loop:
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;
}The loop doesn't know or care what each action does internally. It simply calls execute() on each element.
This is polymorphism in action — the same code handles objects with completely different behaviors. Adding a new action type requires no changes to the iteration logic; you just add another element to the array.
Challenge
EasyLet's build a Task management system that demonstrates polymorphic iteration — processing a collection of objects that share a common interface but execute different behaviors.
You'll organize your code across three files:
task.h: Define your task interface here. Create a function pointer type calledTaskFuncthat takes no parameters and returns nothing. Then define aTaskstruct containing aname(aconst char*) and arunfunction pointer of typeTaskFunc.task.c: Implement three different task functions that represent different types of work:backup_task— printsBacking up data...cleanup_task— printsCleaning up files...report_task— printsGenerating report...
main.c: Bring everything together here. Create an array ofTaskstructs, each wired to a different task function. Then iterate through the array and execute each task polymorphically — for each task, print its name followed by calling itsrunfunction.
Your program will receive a single input: the number of tasks to run (1, 2, or 3).
Create an array containing exactly three tasks in this order: a backup task named Backup, a cleanup task named Cleanup, and a report task named Report. Based on the input count, iterate through only that many tasks from the beginning of the array.
For each task in your iteration, print the task's name followed by a colon and space, then call its run function.
Example output when the input is 2:
Backup: Backing up data...
Cleanup: Cleaning up files...Example output when the input is 3:
Backup: Backing up data...
Cleanup: Cleaning up files...
Report: Generating report...The key insight is that your loop doesn't need to know what each task does — it simply prints the name and calls run() on each element. The same iteration code handles all task types uniformly. Remember to use include guards in your header file.
Cheat sheet
You can iterate through collections of objects that share a common interface, calling the same function pointer on each element regardless of its specific implementation. This is polymorphism — treating different objects uniformly.
Define a function pointer type and a struct that uses it:
typedef void (*ExecuteFunc)(void);
typedef struct {
const char* name;
ExecuteFunc execute;
} Action;Create concrete functions that match the signature:
void jump(void) { printf("Jumping!\n"); }
void run(void) { printf("Running!\n"); }
void rest(void) { printf("Resting...\n"); }Build an array of structs, each wired to a different function, then iterate through them:
Action actions[3] = {
{ "Jump", jump },
{ "Run", run },
{ "Rest", rest }
};
for (int i = 0; i < 3; i++) {
printf("%s: ", actions[i].name);
actions[i].execute();
}The loop doesn't need to know what each action does internally — it simply calls the function pointer on each element. Adding new types requires no changes to the iteration logic.
Try it yourself
#include <stdio.h>
#include "task.h"
// Declare the task functions implemented in task.c
void backup_task(void);
void cleanup_task(void);
void report_task(void);
int main() {
int count;
scanf("%d", &count);
// TODO: Create an array of 3 Task structs in this order:
// 1. Backup task named "Backup" using backup_task
// 2. Cleanup task named "Cleanup" using cleanup_task
// 3. Report task named "Report" using report_task
// TODO: Iterate through 'count' tasks from the array
// For each task, print: "<name>: " then call its run function
return 0;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Modular Programming Basics
Header FilesInclude GuardsSource FilesStatic FunctionsRecap: Modular Calculator4Encapsulation
Opaque Pointers ConceptDefining Opaque StructsGetters and SettersValidation in SettersRecap: Secret Box2Objects and Methods
Structs as ObjectsThe 'Self' PointerConst CorrectnessPointer vs ValueHelper MethodsRecap: Point Manager5Project: Simple Bank Account
Project SetupImplementation of Account8Polymorphism
Function Pointers in StructsSimulating MethodsThe Interface ConceptImplementing InterfacesPolymorphic IterationRecap: Greeter3Object Lifecycle
Constructor PatternDestructor PatternStack InitializationDeep CopyRecap: String Wrapper6Inheritance via Composition
Struct EmbeddingThe First Member RuleAccessing Parent MembersUpcastingRecap: Shape Hierarchy