Simulando métodos
Parte de la sección Object Oriented Programming del Journey de C de Coddy — lección 39 de 61.
Has visto cómo colocar un puntero a función dentro de un struct. El verdadero poder surge cuando asignas diferentes funciones a diferentes instancias del mismo tipo de struct — así es como simulamos métodos con comportamiento variable.
Considera una estructura que representa una acción con lógica personalizable:
typedef void (*ActionFunc)(void);
typedef struct {
const char* name;
ActionFunc execute;
} Action;Al crear instancias, conectas diferentes funciones a cada una:
void say_hello(void) {
printf("Hello!\n");
}
void say_goodbye(void) {
printf("Goodbye!\n");
}
int main() {
Action greet = { "Greet", say_hello };
Action farewell = { "Farewell", say_goodbye };
greet.execute(); // Salida: Hello!
farewell.execute(); // Salida: Goodbye!
return 0;
}Tanto greet como farewell son del mismo tipo (Action), pero llamar a execute en cada uno produce resultados completamente diferentes. La estructura no solo contiene datos — contiene un comportamiento que varía por instancia.
Este patrón refleja cómo funcionan los objetos en otros lenguajes: cada objeto puede tener su propia implementación de un método, aunque compartan la misma interfaz.
Desafío
FácilVamos a construir un sistema Speaker que demuestre cómo diferentes instancias del mismo tipo de struct pueden exhibir comportamientos completamente diferentes basándose en qué función se les asigne.
Crearás dos archivos para organizar tu código:
speaker.h: Define un tipo de puntero a función llamadoSpeakFuncque no reciba parámetros y no devuelva nada. Luego, define un structSpeakerque contenga unname(unconst char*) y un puntero a funciónspeakde tipoSpeakFunc.main.c: Incluye tu cabecera e implementa tres funciones de habla diferentes:bark— imprimeWoof!meow— imprimeMeow!chirp— imprimeChirp!
Speaker, cada una vinculada a una función diferente basada en la entrada, y llama a sus métodosspeakpara ver el comportamiento polimórfico en acción.
Tu programa recibirá dos entradas: el primer tipo de altavoz (1 para bark, 2 para meow, 3 para chirp) y el segundo tipo de altavoz (también 1, 2 o 3).
Crea dos instancias de Speaker; puedes nombrarlas como quieras (como "Dog", "Cat", "Bird", o simplemente "Speaker1", "Speaker2"). Vincula cada una a la función apropiada según su código de entrada, luego llama a speak en cada instancia en orden.
Ejemplo de salida cuando el primer tipo es 1 y el segundo tipo es 2:
Woof!
Meow!Ejemplo de salida cuando el primer tipo es 3 y el segundo tipo es 3:
Chirp!
Chirp!La idea clave aquí es que ambos altavoces son del mismo tipo Speaker, pero producen salidas diferentes porque cada uno tiene una función diferente asignada a su miembro speak. Recuerda usar guardas de inclusión en tu archivo de cabecera.
Hoja de referencia
Diferentes instancias del mismo tipo de estructura pueden contener diferentes punteros a funciones, lo que permite un comportamiento polimórfico.
Define un tipo de puntero a función y una estructura que lo utilice:
typedef void (*ActionFunc)(void);
typedef struct {
const char* name;
ActionFunc execute;
} Action;Crea instancias con diferentes funciones asignadas:
void say_hello(void) {
printf("Hello!\n");
}
void say_goodbye(void) {
printf("Goodbye!\n");
}
int main() {
Action greet = { "Greet", say_hello };
Action farewell = { "Farewell", say_goodbye };
greet.execute(); // Salida: Hello!
farewell.execute(); // Salida: Goodbye!
return 0;
}Ambas instancias comparten el mismo tipo pero muestran un comportamiento diferente cuando se llaman a sus punteros a funciones.
Pruébalo tú mismo
#include <stdio.h>
#include "speaker.h"
// TODO: Implementar la función bark que imprima "Woof!"
// TODO: Implementar la función meow que imprima "Meow!"
// TODO: Implementar la función chirp que imprima "Chirp!"
int main() {
int type1, type2;
scanf("%d", &type1);
scanf("%d", &type2);
// TODO: Crear dos instancias de Speaker
// TODO: Vincular la función speak del primer altavoz basándose en type1
// (1 = bark, 2 = meow, 3 = chirp)
// TODO: Vincular la función speak del segundo altavoz basándose en type2
// (1 = bark, 2 = meow, 3 = chirp)
// TODO: Llamar a speak en cada altavoz en orden
return 0;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Object Oriented Programming
1Fundamentos de programación modular
Archivos de cabeceraGuardas de inclusiónArchivos fuenteFunciones estáticasRepaso: Calculadora modular4Encapsulamiento
Concepto de punteros opacosDefinición de structs opacosGetters y SettersValidación en settersResumen: Caja secreta7Punteros a funciones
Declaración de punteros a funcionesLlamada a punteros a funcionesTypedef para punteros a funcionesPasar funciones como argumentosRepaso: Dispatch de calculadora10Contenedores genéricos
Repaso de punteros voidWrapper genéricoSwap genéricoComparación genéricaRepaso: Array genérico2Objetos y métodos
Structs como objetosEl puntero 'Self'Const CorrectnessPuntero vs. ValorMétodos auxiliaresResumen: Point Manager5Proyecto: Cuenta bancaria simple
Configuración del proyectoImplementación de la cuenta8Polimorfismo
Punteros a funciones en estructurasSimulando métodosEl concepto de interfazImplementando interfacesIteración polimórficaResumen: Greeter3Ciclo de vida de los objetos
Patrón ConstructorPatrón DestructorInicialización en StackCopia profundaResumen: String Wrapper6Herencia mediante composición
Incrustación de StructsLa regla del primer miembroAcceso a miembros padreUpcastingResumen: Jerarquía de formas9Proyecto: Dibujador de figuras
Descripción general del proyectoImplementación del círculo