16 de diciembre de 2019

Apuntadores a funciones.

    En la entrada Ordenamiento por burbuja se presentó, describió e implementó dicho ordenamiento, en esta entrada se retomará ese algoritmo para abordar la siguiente problemática: se desea ahora poder ordenar los datos de manera ascendente o descendente, ¿cómo lo solucionaría con lo que se ha discutido hasta ahora?

   Una posible solución sería hacer dos funciones: una que ordene los datos de manera  ascendente y otra que los ordene de manera descendente, tal y como se muestra en el Ejemplo 7.10.

   La función burbujaAscendente ordena los datos de forma ascendente, mientras que la función burbujaDescendente los ordena de forma descendente tal y como sus identificadores lo sugieren.

   Compare las funciones y notará que son exactamente iguales excepto por las líneas 14 y 29 respectivamente, que es en donde se realiza la comparación de los elementos para determinar el orden en que están e intercambiarlos, si fuera necesario, en base al tipo de ordenamiento.

   La solución presentada, aunque es útil y válida, no es una solución eficiente en espacio ni elegante. El lenguaje de programación C proporciona un mecanismo para abordar de mejor forma este tipo de situaciones: los apuntadores a funciones.

   Un apuntador a función almacena la dirección en memoria del inicio de las instrucciones de una función, la cuál está representada por su identificador.

   De manera informal puede decirse entonces, que un apuntador a función es un mecanismo que permite hacer referencia a una función de manera implícita.

   Los apuntadores a funciones pueden asignarse, ser almacenados en arreglos, pasados a funciones, regresados por funciones, etcétera. El Ejemplo 7.11 muestra la forma de definir un apuntador a funciones en la función burbuja de las líneas 17-30.

   La explicación de los ejemplos siguientes se centrará exclusivamente en el tema de la entrada: apuntadores a funciones, no en el ordenamiento por burbuja.

   El tercer parámetro de la función burbuja (línea 17) indica que se recibirá un apuntador a funciones que tengan la siguiente estructura:
  • Reciban dos argumentos de tipo int.
  • Regresen un valor de tipo int.
   Cualquier función que cumpla con esta plantilla, podrá ser referida por el apuntador a función orden.

   Note que en la expresión:

int (*orden) (int, int)

los paréntesis que circundan a *orden son muy importantes, debido a que le indican al compilador que orden es un apuntador a funciones que reciben dos argumentos de tipo int, y que regresen un valor de tipo int.

   Por otro lado, la expresión:

int *orden(int, int)

le indicaría al compilador que orden es una función que recibe dos argumentos de tipo int, y regresa un apuntador a int, lo cual es definitiva y completamente diferente.

   Observe ahora la línea 23 del Ejemplo 7.11, en la cual ocurre la des referencia del apuntador a función, es decir, en ella se invocará a la función cuyo identificador coincida con el tercer argumento enviado a la función burbuja en su invocación (líneas 16 y 19 del Ejemplo 7.12).

   Ahora bien, el Ejemplo 7.12 muestra la forma de pasar el apuntador denotado por el identificador respectivo, a la función burbuja en las líneas 16 y 19. Note que en la línea 6 se está incluyendo la biblioteca de funciones del Ejemplo 7.11.

   Observe que el apuntador a funciones orden hace que la línea 23 del Ejemplo 7.11 se transforme en un llamado explícito a las funciones ascedente o descedente; es decir, convierte la expresión:

if ( (*orden) (a[ i ], a[ i + 1 ]) )

en la expresión:


if ( ascendente (a[ i ], a[ i + 1 ]) )

para el llamado a la función burbuja (línea 16) del Ejemplo 7.12. Mientras que la expresión:

if ( (*orden) (a[ i ], a[ i + 1 ]) )
es convertida en la expresión:

if ( descendente (a[ i ], a[ i + 1 ]) )

para el llamado a la función burbuja (línea 19) del Ejemplo 7.12.

   Por último, la función ascendente (líneas 9-11) del Ejemplo 7.11, regresa 1 (verdadero) si a es mayor que b, y 0 (falso) si no lo es, lo cual corresponde al criterio requerido para un ordenamiento ascendente. Observe que la expresión de la línea 10:

return a > b;

es lógicamente equivalente a la estructura de selección if-else:

                    if(a > b)
                             return 1;
                       else
                             return 0;

y que la función descendente (líneas 13-15) se comporta de manera análoga a la función ascendente. La salida del Ejemplo 7.12 se muestra en la siguiente figura:

Salida del Ejemplo 7.12.
   En resumen, en el contexto de apuntadores a variables los apuntadores almacenan las direcciones de memoria de otras variables, mientras que en el contexto de funciones los apuntadores almacenan la dirección de memoria del inicio de las instrucciones de una determinada función. Note que en ambos casos lo que el compilador y la computadora controlan son direcciones de memoria, mientras que el programador controla identificadores, lo cual es una abstracción sumamente conveniente.