Variables de Puntero a Función y Tablas de Despacho en C

Variables de Puntero a Función

Declaración de Punteros a Función

Los punteros a función son variables que almacenan direccciones de funciones, permitiendo invocar dichas funciones a través del puntero. Veamos un ejemplo básico:

#include <stdio.h>

void saludar() {
    printf("Hola mundo\n");
}

int main() {
    printf("Dirección de saludar: %p\n", saludar);
    printf("Dirección con &: %p\n", &saludar);
    return 0;
}

Como se observa, las funciones tienen dirección de memoria, y podemos almacenarla en punteros especializados:

void funcion_ejemplo() {
    printf("Ejecutando función\n");
}

int sumar(int a, int b) {
    return a + b;
}

// Declaración de punteros a función
void (*ptr_funcion)() = &funcion_ejemplo;
void (*ptr_funcion2)() = funcion_ejemplo;
int (*ptr_sumar)(int, int) = sumar;

Utilización de Punteros a Función

Una vez declarado el puntero, podemos utilizarlo para invocar la función referencaida:

#include <stdio.h>

int multiplicar(int x, int y) {
    return x * y;
}

int main() {
    int (*operacion)(int, int) = multiplicar;
    
    printf("Resultado: %d\n", (*operacion)(5, 3));
    printf("Resultado: %d\n", operacion(4, 7));
    
    return 0;
}

Redefinición de Tipos con typedef

La palabra clave typedef permite crear alias para tipos complejos, facilitando su uso:

typedef unsigned int uint;
typedef int* ptr_entero;

// Para punteros a función
typedef int(*ptr_operacion)(int, int);
typedef void(*ptr_procedimiento)();

Arreglos de Punteros a Función

Un arreglo de punteros a función permite almacenar múltiples direcciones de funciones del mismo tipo:

int (*operaciones[4])(int, int);

Tablas de Despacho

Las tablas de despacho utilizan arreglos de punteros a función para simplificar código cnodicional complejo. Consideremos una calculadora:

#include <stdio.h>

int suma(int a, int b) { return a + b; }
int resta(int a, int b) { return a - b; }
int producto(int a, int b) { return a * b; }
int division(int a, int b) { return a / b; }

int main() {
    int x, y, opcion = 1;
    int resultado;
    
    // Tabla de despacho
    int (*calculos[5])(int, int) = { NULL, suma, resta, producto, division };
    
    do {
        printf("Calculadora\n");
        printf("1. Suma     2. Resta\n");
        printf("3. Producto 4. División\n");
        printf("0. Salir\n");
        printf("Seleccione: ");
        scanf("%d", &opcion);
        
        if (opcion >= 1 && opcion <= 4) {
            printf("Ingrese dos números: ");
            scanf("%d %d", &x, &y);
            resultado = calculos[opcion](x, y);
            printf("Resultado: %d\n", resultado);
        } else if (opcion == 0) {
            printf("Finalizando...\n");
        } else {
            printf("Opción inválida\n");
        }
    } while (opcion != 0);
    
    return 0;
}

Funciones de Retrollamada

Concepto de Retrollamada

Una función de retrollamada se invoca indirectamente a través de un puntero pasado como argumento:

#include <stdio.h>

int suma(int a, int b) { return a + b; }
int resta(int a, int b) { return a - b; }

void ejecutar_operacion(int (*operacion)(int, int)) {
    int x, y, resultado;
    printf("Ingrese dos valores: ");
    scanf("%d %d", &x, &y);
    resultado = operacion(x, y);
    printf("Resultado: %d\n", resultado);
}

int main() {
    int opcion;
    
    do {
        printf("1. Suma  2. Resta  0. Salir\n");
        printf("Seleccione: ");
        scanf("%d", &opcion);
        
        switch(opcion) {
            case 1: ejecutar_operacion(suma); break;
            case 2: ejecutar_operacion(resta); break;
            case 0: printf("Saliendo\n"); break;
            default: printf("Opción no válida\n");
        }
    } while (opcion != 0);
    
    return 0;
}

Implementación de qsort Personalizado

La función qsort de la biblioteca estándar utiliza retrollamadas para ordenamiento genérico:

#include <stdio.h>
#include <string.h>

struct Persona {
    char nombre[30];
    int edad;
};

int comparar_edad(const void* a, const void* b) {
    return ((struct Persona*)a)->edad - ((struct Persona*)b)->edad;
}

int comparar_nombre(const void* a, const void* b) {
    return strcmp(((struct Persona*)a)->nombre, ((struct Persona*)b)->nombre);
}

void intercambiar(void* a, void* b, size_t tam) {
    unsigned char* ptr_a = (unsigned char*)a;
    unsigned char* ptr_b = (unsigned char*)b;
    unsigned char temp;
    
    for (size_t i = 0; i < tam; i++) {
        temp = ptr_a[i];
        ptr_a[i] = ptr_b[i];
        ptr_b[i] = temp;
    }
}

void ordenar_burbuja(void* base, size_t num, size_t tam, 
                    int (*comparar)(const void*, const void*)) {
    for (size_t i = 0; i < num - 1; i++) {
        for (size_t j = 0; j < num - i - 1; j++) {
            void* actual = (char*)base + j * tam;
            void* siguiente = (char*)base + (j + 1) * tam;
            
            if (comparar(actual, siguiente) > 0) {
                intercambiar(actual, siguiente, tam);
            }
        }
    }
}

int main() {
    struct Persona personas[] = {
        {"Carlos", 25},
        {"Ana", 30},
        {"Luis", 20}
    };
    
    size_t cantidad = sizeof(personas) / sizeof(personas[0]);
    
    ordenar_burbuja(personas, cantidad, sizeof(struct Persona), comparar_edad);
    
    for (size_t i = 0; i < cantidad; i++) {
        printf("%s: %d años\n", personas[i].nombre, personas[i].edad);
    }
    
    return 0;
}

Etiquetas: punteros a función typedef arreglos de punteros tablas de despacho funciones de retrollamada

Publicado el 9-11 11:15