CAPÍTULO 10: Punteros a Funciones

Hasta ahora hemos visto punteros que apuntan a objetos de datos, pero C también permite declarar punteros para apuntar a funciones. Este tipo de punteros tienen gran variedad de usos y vamos a ver algunos de ellos.

Considera el siguiente problema: quieres escribir una función para ordenar virtualmente cualquier colección de datos que pueda guardarse en un array. Esto podría ser un array de cadenas, o de enteros, o de floats, o incluso de estructuras. El algoritmo de ordenación puede ser para todos el mismo. Por ejemplo, podría ser un algoritmo de ordenación de burbuja simple, o de tipo shell (más complicado) o el algoritmo de ordenación quick sort. Para nuestro propósito, aquí veremos una ordenación de burbuja simple.

Sedgewick [1] escribió en C la ordenación de burbuja utilizando una función que, pasándola un puntero a un array, lo ordena. Vamos a ver la función bubble() en el siguiente programa bubble_1.c:


/*-------------------- bubble_1.c --------------------*/

/* Programa bubble_1.c de PTRTUT10.HTM   6/13/97 */

#include <stdio.h>

int arr[10] = { 3,6,1,2,3,8,4,1,7,2};

void bubble(int a[], int N);

int main(void)
{
    int i;
    putchar('\n');
    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    bubble(arr,10);
    putchar('\n');

    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

void bubble(int a[], int N)
{
    int i, j, t;
    for (i = N-1; i >= 0; i--)
    {
        for (j = 1; j <= i; j++)
        {
            if (a[j-1] > a[j])
            {
                t = a[j-1];
                a[j-1] = a[j];
                a[j] = t;
            }
        }
    }
}



/*---------------------- fin bubble_1.c -----------------------*/

La ordenación de burbuja es una de las más simples. El algoritmo escanea el array desde el segundo hasta el último elemento, comparando cada uno con el que le precede. Si el predecesor es mayor al actual elemento, se intercambian quedando el mayor más cerca del fin del array. El resultado de la primera pasada es que el elemento mayor queda el último en el array. El array ahora queda limitado a todos los elementos menos el último y se repite el proceso. En la siguiente pasada, el siguiente elemento mayor quedará como predecesor del elemento mayor anterior. El proceso se repite un número de veces igual al número de elementos menos uno. El resultado es un array ordenado.

Nuestra función está diseñada para ordenar un array de enteros, primero comparando los enteros y después utiliza un entero temporal para realizar el intercambio. Lo que vamos a ver ahora es si podemos convertir este código para utilizar cualquier tipo de dato, no sólo enteros.

A la vez, no queremos analizar nuestro algoritmo y el código correspondiente cada vez que queramos utilizarlo. Empezaremos creando una nueva función de comparación sustituyendo la parte de la comparación dentro de la función bubble() con la llamada a la nueva función, así no hará falta reescribir las partes relacionadas del algoritmo actual. El resultado lo vemos en bubble_2.c:


/*---------------------- bubble_2.c -------------------------*/

/* Programa bubble_2.c de PTRTUT10.HTM   6/13/97 */

   /* Separación de la función de comparación */

#include <stdio.h>

int arr[10] = { 3,6,1,2,3,8,4,1,7,2};

void bubble(int a[], int N);
int compare(int m, int n);

int main(void)
{
    int i;
    putchar('\n');
    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    bubble(arr,10);
    putchar('\n');

    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

void bubble(int a[], int N)

{
    int i, j, t;
    for (i = N-1; i >= 0; i--)
    {
        for (j = 1; j <= i; j++)
        {
            if (compare(a[j-1], a[j]))
            {
                t = a[j-1];
                a[j-1] = a[j];
                a[j] = t;
            }
        }
    }
}

int compare(int m, int n)
{
    return (m > n);
}
/*--------------------- fin de bubble_2.c -----------------------*/
Si el objetivo es hacer que nuestra rutina de ordenación sea independiente del tipo de datos, una forma de hacerlo es utilizar punteros de tipo void para apuntar a los datos en lugar de utilizar el tipo de dato entero. Para ello, modifiquemos algunas cosas del código anterior para incorporar punteros, empezando con los de tipo entero.

/*----------------------- bubble_3.c -------------------------*/

/* Programa bubble_3.c de PTRTUT10.HTM    6/13/97 */

#include <stdio.h>

int arr[10] = { 3,6,1,2,3,8,4,1,7,2};

void bubble(int *p, int N);
int compare(int *m, int *n);

int main(void)
{
    int i;
    putchar('\n');

    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    bubble(arr,10);
    putchar('\n');

    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

void bubble(int *p, int N)
{
    int i, j, t;
    for (i = N-1; i >= 0; i--)
    {
        for (j = 1; j <= i; j++)
        {
            if (compare(&p[j-1], &p[j]))
            {
                t = p[j-1];
                p[j-1] = p[j];
                p[j] = t;
            }
        }
    }
}

int compare(int *m, int *n)
{
    return (*m > *n);
}

/*------------------ fin de bubble3.c -------------------------*/

Fíjate en los cambios. Ahora estamos pasando un puntero de tipo entero (o un array de enteros) a bubble(). Dentro de la función bubble estamos pasando punteros a los elementos del array que queremos comparar con la función de comparación. Y, por supuesto, desreferenciamos estos punteros en nuestra función compare() para realizar la comparación actual. Lo siguiente será convertir los punteros en bubble() a punteros de tipo void para que la función no tenga en cuenta el tipo. Esto lo vemos en bubble_4.

/*------------------ bubble_4.c ----------------------------*/

/* Programa bubble_4.c de PTRTUT10,HTM   6/13/97 */

#include <stdio.h>

int arr[10] = { 3,6,1,2,3,8,4,1,7,2};

void bubble(int *p, int N);
int compare(void *m, void *n);

int main(void)
{
    int i;
    putchar('\n');

    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    bubble(arr,10);
    putchar('\n');

    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

void bubble(int *p, int N)
{
    int i, j, t;
    for (i = N-1; i >= 0; i--)
    {
        for (j = 1; j <= i; j++)
        {
            if (compare((void *)&p[j-1], (void *)&p[j]))
            {
                t = p[j-1];
                p[j-1] = p[j];
                p[j] = t;
            }
        }
    }
}

int compare(void *m, void *n)
{
    int *m1, *n1;
    m1 = (int *)m;
    n1 = (int *)n;
    return (*m1 > *n1);
}

/*------------------ fin de bubble_4.c ---------------------*/

En compare() hemos realizado una conversión del puntero de tipo void al actual tipo que estamos ordenando (int *), y como veremos después, esto es correcto. Como estamos pasando un puntero que apunta a un array de enteros (int *p) como parámetro a bubble(), tenemos que convertir esos punteros a void cuando los pasamos a compare().

Como queremos que el primer parámetro de bubble() sea también un puntero de tipo void, esto implica que dentro de bubble() hay que hacer algo con la variable t, que actualmente es un entero. Además, cuando usamos t = p[j-1]; es necesario conocer el tipo de p[j-1] para saber cuantos bytes hay que copiar a la variable t (o la que utilicemos para realizar el reemplazo).

Actualmente en bubble_4.c, dentro de bubble() sabemos que el tipo de dato para ordenar (y por tanto el tamaño de cada elemento individual) se obtiene a partir del hecho de que el primer parámetro es un puntero de tipo entero. Si queremos utilizar bubble() con cualquier tipo de dato, necesitamos que ese puntero sea de tipo void, pero al hacerlo, vamos a perder información relativa al tamaño de los elementos individuales del array. Por ello, en bubble_5.c vamos a agregar un parámetro independiente para manejar esta información.

Los cambios que tiene bubble_5.c respecto a bubble_4.c son, quizás, más numerosos que los realizados antes. Por ello es conveniente comparar con cuidado los dos módulos para dar con las diferencias.


/*---------------------- bubble_5.c ---------------------------*/

/* Programa bubble_5.c de PTRTUT10.HTM    6/13/97 */



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

long arr[10] = { 3,6,1,2,3,8,4,1,7,2};

void bubble(void *p, size_t width, int N);
int compare(void *m, void *n);

int main(void)
{
    int i;
    putchar('\n');

    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    bubble(arr, sizeof(long), 10);
    putchar('\n');

    for (i = 0; i < 10; i++)
    {
        printf("%ld ", arr[i]);
    }

    return 0;
}

void bubble(void *p, size_t width, int N)
{
    int i, j;
    unsigned char buf[4];
    unsigned char *bp = p;

    for (i = N-1; i >= 0; i--)
    {
        for (j = 1; j <= i; j++)
        {
            if (compare((void *)(bp + width*(j-1)),
                        (void *)(bp + j*width)))  /* 1 */
            {
/*              t = p[j-1];   */
                memcpy(buf, bp + width*(j-1), width);
/*              p[j-1] = p[j];   */
                memcpy(bp + width*(j-1), bp + j*width , width);
/*              p[j] = t;   */
                memcpy(bp + j*width, buf, width);
            }
        }
    }
}

int compare(void *m, void *n)
{
    long *m1, *n1;
    m1 = (long *)m;
    n1 = (long *)n;
    return (*m1 > *n1);
}

/*--------------------- fin de bubble_5.c ---------------------*/

He cambiado el tipo de datos del array de int a long para ver los cambios necesarios en la función compare(). Dentro de bubble() he eliminado la variable t (lo que hubiera supuesto cambiarla de tipo int al tipo long). He añadido un buffer con un tamaño de 4 caracteres sin signo para alojar un long (esto cambiará en las futuras modificaciones de este código). El puntero caracter sin signo *bp se utiliza para apuntar a la base del array a ordenar, es decir, al primer elemento de ese array.

También he modificado lo que se pasa a compare(), y cómo se realiza el cambio de elementos que indica la comparación. El uso de memcpy() y la notación de punteros en lugar de la notación de arrays permite reducir la sensibilidad al tipo.

De nuevo, para tener una mejor compresión de lo que sucede y porqué, es necesaria una atenta comparación entre bubble_5.c y bubble_4.c

Ahora vamos a ver a bubble_6.c donde utilizaremos la misma función bubble() de bubble_5.c, pero para ordenar cadenas en lugar de enteros largos (long). Obviamente tenemos que cambiar la función de comparación, pues la comparación entre cadenas y la comparación entre enteros largos es distinta. Además, en bubble_6.c hemos eliminado las líneas dentro de bubble() que estaban comentadas en bubble_5.c.

/*--------------------- bubble_6.c ---------------------*/
/* Programa bubble_6.c de PTRTUT10.HTM   6/13/97 */

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

#define MAX_BUF 256

char arr2[5][20] = {  "Mickey Mouse",

                      "Donald Duck",

                      "Minnie Mouse",

                      "Goofy",

                      "Ted Jensen" };

void bubble(void *p, int width, int N);
int compare(void *m, void *n);

int main(void)
{
    int i;
    putchar('\n');

    for (i = 0; i < 5; i++)
    {
        printf("%s\n", arr2[i]);
    }
    bubble(arr2, 20, 5);
    putchar('\n\n');

    for (i = 0; i < 5; i++)
    {
        printf("%s\n", arr2[i]);
    }
    return 0;
}

void bubble(void *p, int width, int N)
{
    int i, j, k;
    unsigned char buf[MAX_BUF];
    unsigned char *bp = p;

    for (i = N-1; i >= 0; i--)
    {
        for (j = 1; j <= i; j++)
        {
          k = compare((void *)(bp + width*(j-1)), (void *)(bp + j*width));
          if (k > 0)
            {
             memcpy(buf, bp + width*(j-1), width);
             memcpy(bp + width*(j-1), bp + j*width , width);
             memcpy(bp + j*width, buf, width);
            }
        }
    }
}

int compare(void *m, void *n)
{
    char *m1 = m;
    char *n1 = n;
    return (strcmp(m1,n1));
}

/*------------------- fin de bubble_6.c ---------------------*/

Como se da el hecho de que bubble() no tiene ninguna modificación desde bubble_5.c, esto indica que esta función puede ordenar una gran variedad de tipos de datos. Lo que queda por hacer es pasar a bubble() el nombre de la función de comparación que queremos usar para que pueda ser verdaderamente universal. Así como el nombre de un array es la dirección de su primer elemento en el segmento de datos, el nombre de una función está dentro de la dirección de esa función en el segmento de código. Por lo tanto necesitamos utilizar un puntero a una función, que en este caso es la función de comparación.

Los punteros a funciones deben coincidir con el número y tipos de parámetros y el tipo del valor de retorno de la función a donde apunten. En nuestro caso declaramos nuestro puntero a función así:

   int (*fptr)(const void *p1, const void *p2);
Fíjate que si escribimos:
    int *fptr(const void *p1, const void *p2);
tendríamos un prototipo de función para una función que devuelve un puntero de tipo int. Esto se produce porque en C, el operador paréntesis () tiene una precedencia mayor que el operador de puntero *. Indicamos que es una declaración de un puntero a función colocando los paréntesis alrededor de la cadena (*fptr).

Ahora modificaremos nuestra declaración de bubble() agregando como cuarto parámetro un puntero a función del tipo apropiado. El prototipo de la función sería:

    void bubble(void *p, int width, int N,
                int(*fptr)(const void *, const void *));
Cuando llamamos a bubble(), insertamos el nombre de la función de comparación que queramos usar. El código de bubble_7.c muestra como esta forma permite utilizar la misma función bubble() para ordenar diferentes tipos de datos.

/*------------------- bubble_7.c ------------------*/

/* Programa bubble_7.c de PTRTUT10.HTM  6/10/97 */

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

#define MAX_BUF 256

long arr[10] = { 3,6,1,2,3,8,4,1,7,2};
char arr2[5][20] = {  "Mickey Mouse",
                      "Donald Duck",
                      "Minnie Mouse",
                      "Goofy",
                      "Ted Jensen" };

void bubble(void *p, int width, int N,
            int(*fptr)(const void *, const void *));
int compare_string(const void *m, const void *n);
int compare_long(const void *m, const void *n);

int main(void)
{
    int i;
    puts("\nBefore Sorting:\n");

    for (i = 0; i < 10; i++)               /* muestra enteros largos */
    {
        printf("%ld ",arr[i]);
    }
    puts("\n");

    for (i = 0; i < 5; i++)                /* muestra cadenas */
    {
        printf("%s\n", arr2[i]);
    }
    bubble(arr, 4, 10, compare_long);         /* ordena enteros largos */
    bubble(arr2, 20, 5, compare_string);      /* ordena cadenas */
    puts("\n\nAfter Sorting:\n");

    for (i = 0; i < 10; i++)               /* muestra enteros largos ordenados */
    {
        printf("%d ",arr[i]);
    }
    puts("\n");

    for (i = 0; i < 5; i++)                /* muestra cadenas ordenadas */
    {
        printf("%s\n", arr2[i]);
    }
    return 0;
}

void bubble(void *p, int width, int N,
            int(*fptr)(const void *, const void *))
{
    int i, j, k;
    unsigned char buf[MAX_BUF];
    unsigned char *bp = p;

    for (i = N-1; i >= 0; i--)
    {
        for (j = 1; j <= i; j++)
        {
            k = fptr((void *)(bp + width*(j-1)), (void *)(bp + j*width));
            if (k > 0)
            {
                memcpy(buf, bp + width*(j-1), width);
                memcpy(bp + width*(j-1), bp + j*width , width);
                memcpy(bp + j*width, buf, width);
            }
        }
    }
}

int compare_string(const void *m, const void *n)
{
    char *m1 = (char *)m;
    char *n1 = (char *)n;
    return (strcmp(m1,n1));
}

int compare_long(const void *m, const void *n)
{
    long *m1, *n1;
    m1 = (long *)m;
    n1 = (long *)n;
    return (*m1 > *n1);
}

/*----------------- fin de bubble_7.c -----------------*/

Referencias del Capítulo 10:

  1. "Algorithms in C"
    Robert Sedgewick
    Addison-Wesley
    ISBN 0-201-51425-7
Continuar con el Tutorial de Punteros

Tabla de Contenidos