Así que, hoy, volvemos a un tema ya tratado aquí, y sobre el cual (me dí cuenta ayer) se podría añadir algo interesante. Estoy hablando, otra vez, de las funciones callback.
Por supuesto, antes de seguir leyendo, debéis refrescaros la memoria releyendo el otro post sobre el tema, ya que es una extensión de esto, y están estrechamente vinculados.
Pausa para la reflexión...
¿Ya estáis de vuelta? ¿pero cómo? Todavía no habéis releído el antiguo post? Hay que leerlo, gracias...
Otra pausa para la reflexión...
Bueno, ahora podemos continuar.
Así que, cómo habréis re-notado, el ejemplo que yo había propuesto era, yo diría, clásico, inspirado en el uso de qsort(), entonces con una callback que se llama sin argumentos, pero que, en realidad, necesita dos que se generaran internamente por la misma función (la mysort() del ejemplo, o la qsort(), si prefereis).
Entonces, resumamos el flujo del ejemplo: he escrito una función main() que utiliza mysort() que, para que funcione, necesita una función para comparar dos valores. He escrito también, por lo tanto, la función de comparación (que puede ser tan simple como la del ejemplo, pero también mucho más compleja, depende de lo que se quiere lograr). La función respetaba, por supuesto, el prototipo provisto por mysort(): o sea, una función que necesita una callback, también tiene que declarar el prototipo de la callback misma (y si no ¿como la escribimos?). la mysort() misma se ocupa, pues, de llenar los dos parámetros de la callback con los valores a comparar.
Y ahora llegamos a la parte nueva: siempre refiriéndose al ejemplo de mysort() (que, imagino, ya conocéis de memoria) supongamos que necesitamos pasar otro parámetro a la callback, un parámetro externo disponible solo a nivel de la llamada principal, y que la mysort() no puede generar internamente.
¿Cómo podemos hacerlo? Veamos ahora el nuevo código (presentado como un solo bloque, pero, en realidad, a dividir en tres file):
Como se puede ver es muy similar al ejemplo del otros post, sólo que ahora, a nivel de main(), abrimos un archivo para registrar datos de elaboración y tenemos que pasar el file descriptor a la mysort(), ya que no podemos pensar de escribir una función de librería que llevas imprimido el código el nombre del file de log: es la aplicación llamante que tiene que pasarlo./* parte que tendría que estar en el file mysort.h */ // prototipos para mySort() typedef int (*mycallback)(int, int, void *); void mySort(int *array, int nelems, mycallback cmpFunc, void *fp); /* parte que tendría que estar en el file mysort.c */ // mySort() - funzione di sort che usa l'algoritmo bubblesort void mySort(int *array, int nelems, mycallback cmpFunc, void *fp) { // loop sobre todos los elementos de array while (nelems > 0) { // loop interno con decremento longitud int i; for (i = 0; i < (nelems - 1); i++) { // eseguo callback di comparazione if (cmpFunc(array[i], array[i + 1], fp)) { // eseguo swap di array[i] e array[i+1] int temp = array[i]; array[i] = array[i + 1]; array[i + 1] = temp; } } // decremento nelems nelems--; } } /* parte que tendría que estar en el file mymain.c */ // cbCmpFunc() - función de comparación static int cbCmpFunc(int elem_a, int elem_b, void *fp) { // escribo resultados parciales en un file fprintf(fp, "%d > %d = %d\n", elem_a, elem_b, elem_a > elem_b); return elem_a > elem_b; } // main int main(void) { int array[] = {34,12,32,9,10,72,82,23,14,7,94}; int nelems = sizeof(array) / sizeof(int); FILE *fp = fopen("result.txt", "w"); // ejecuto sort array mySort(array, nelems, cbCmpFunc, fp); // cierro file fclose(fp); // enseño resultados int i; for (i = 0; i < nelems; i++) printf("%d - ", array[i]); printf("\n"); // esco return 0; }
¿Y cómo lo hacemos? Es muy simple: se añade un parámetro (del tipo oportuno) a mysort() y a la callback, y en la llamada principal (en el main(), en nuestro caso) se pasa el valor a la mysort(), que se ocupará de propagarlo hasta la callback, que es el usuario del nuevo parámetro; la mysort() no lo usa, lo transporta solamente. Con este método podemos pasar todos los parámetros que queremos: en el ejemplo he añadido uno, pero se pueden añadir al gusto.
Por supuesto, todo lo anterior es válido para las funciones que implementamos nosotros: no se puede pensar en añadir parámetros a funciones de libreria que no tenemos bajo control: por ejemplo, la qsort() sólo necesita la callback con dos parámetros, y así la tenemos que aguantar.
Alguien se puede preguntar porqué el parámetro fp es un void* y no un tipo más específico (en este caso un FILE*): lo he escrito así para demostrar que, con este método, se puede pasar cualquier valor (por ejemplo, en C++ se utiliza para pasar el puntero this): de hecho, he visto código donde se agregan void* a las callback* (en fase de proyecto) sólo para uso en el futuro, de manera de poder escribir, luego, callback muy personalizadas sin necesidad de cambiar la función base (que, como se ha dicho, sólo es transportadora de estos parámetros)
¿Qué os parece? Sí, también esta vez el tema suena un poco obvio, pero si un día os vais a pelear con las callback espero que os pueda ser útil. Yo, por ejemplo, por falta de documentación y otras razones inevitables, en su momento (hace mucho tiempo) tuve que aguantarme con sólo leer el código escrito por otros, y no sé lo que habría dado por tener a mi disposición un ejemplo tan simple como esto que acabo de proponer (pero, entonces, vivimos en una época de gran suerte... pero sólo para los informáticos. Y tampoco tanto. Pero esta es otra historia ...).
Hasta el próximo post.