Publi

Callbacks, retrollamadas o delegados o cómo crear código más flexible en C

Uno de los grandes objetivos de la programación es la de evitar repetirse. Ok, depende del contexto en el que estemos trabajando. Pero en general, querremos escribir poco cuando trabajamos en un proyecto (y así terminamos antes) y de paso que lo que escribamos para un programa nos sirva para otro (reaprovechamiento del código).

Un callback nos permite llamar a una función u otra dependiendo de la ocasión. Imaginemos dos funciones y la función principal (la sintaxis utilizada nos es correcta, pero mi objetivo es que en este punto se vea claramente)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void funcion_a()
{
  printf("Has llamado a la función A");
}  

void funcion_x(funcion ayudante)
{
  printf("Aquí procesamos información");
  ayudante();
  printf("Aquí seguimos procesando información");
}

int main()
{
  funcion_x(funcion_a);
}

Lo que hcemos es llamar a función_x() y, como parámetro, le decimos que llame a funcion_a(), lo que conseguimos es que cuando funcion_x() esté ejecutándose, necesitará ayuda en algún momento de otra función, y desde main() decimos que esa función sea funcion_a().

Esto, por ejemplo nos puede servir cuando implementemos un algoritmo de ordenación (basado en el intercambio de elementos), es decir, el algoritmo va a ser uno de los que elijamos, pero podemos implementar la ordenación con varios criterios, ascendente y descendente y para ello no tenemos que crear decenas de funciones que implementen la ordenación; podemos crear una función de ordenación y que esta se ayude de otra función que dependiendo del criterio elegido diga si debemos intercambiar los elementos o no). De hecho algo parecido está implementado en stdlib.h en la función qsort(), la cual en uno de sus parámetros debemos indicar qué función llamar para hacer las comparaciones; la función debe ser muy general y existen infinitos casos específicos ( por ejemplo si se ordenan registros, cadenas, etc).

O por ejemplo, para el caso en el que nuestro programa tenga varios tipos de salida, por ejemplo: por pantalla, a fichero y por e-mail; cada uno deberá tener un formato diferente, por pantalla será simple, pero a fichero debemos incluir una serie de cabeceras y por e-mail tendremos otras necesidades. Una forma intuitiva sería hacer un case siempre que toque hacer una salida (con lo que estaríamos repitiéndonos un poco); pero también podemos introducir una función callback y comprobar el tipo de salida una sola vez).

Dejo aquí un ejemplo con la sintaxis de una función que admitirá como parámetro otra función; debemos tener en cuenta que una función puede tener salida de un tipo y N parámetros de tipos determinados, por lo que antes de decir a qué función B (llamada) debe llamar una función A (llamadora), debemos decidir qué forma tendrá esa función B.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h>

int llamada(char *cadena, int numero)
{
  return (strlen(cadena)+numero);
}

void llamadora(int (*func)(char*, int))
{
  int num;
  printf("Voy a llamar a una función con el texto HOLA y el numero 20\n");
  num=func("HOLA", 20);
  printf("La he llamado y me ha devuelto: %d\n", num);
}

int main()
{
  llamadora(llamada);
}

En el ejemplo anterior, podemos implementar funciones llamada() diferentes, pero siempre tendrán que devolver un int, y necesitarán como parámetro un char* y un int y debemos pasarle el parámetro a llamadora() antes de invocarla.

Por tanto, el parámetro que equivale a una función es:

tipo_devolución (*nombre)(tipo param1, tipo param2, …)

En este punto, no nos hace falta darle nombre a los parámetros, lo que necesitamos saber es el tipo de los parámetros y su tipo.

Una buena práctica es crear un typedef que defina la función, de la siguiente forma:

typedef tipo_devolución (*nombre)(tipo param1, tipo param2, …);

Exactamente igual que para declarar el parámetro. Con lo que el código anterior quedaría de la sigueinte forma (lo único que cambia es el typedef y la declaración de llamadora):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <string.h>

/* TYPEDEF */
typedef int (*funcion_callback)(char*, int);

int llamada(char *cadena, int numero)
{
  return (strlen(cadena)+numero);
}

/* DECLARACIÓN DE LLAMADORA */
void llamadora(funcion_callback func)
{
  int num;
  printf("Voy a llamar a una función con el texto HOLA y el numero 20\n");
  num=func("HOLA", 20);
  printf("La he llamado y me ha devuelto: %d\n", num);
}

int main()
{
  llamadora(llamada);
}

Por último, indicar un ejemplo con qsort() como se dijo antes. En este ejemplo, podemos ver cómo generamos un array con números aleatorios, y lo ordenamos según ciertos criterios (hay ciertas líneas comentadas, por defecto la ordenación es ascendente, pero si quitamos los comentarios de alguna otra ordenación y comentamos la activa podremos ver cómo el array se va reordenando con diferentes criterios.

La función que se llamará desde qsort() deberá devolver un número mayor, igual o menor a 0, indicando si un objeto A es mayor, menor o igual a un objeto B (en este caso, los objetos será números).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <stdio.h>
#include <stdlib.h>     /* qsort() */
#include <time.h>       /* time() - Para generar la semilla aleatoria */
#define MAX_NUMEROS 10

/* Genera números aleatorios del 1 al 100 y los almacena en el array */
/* numeros, esta función generará como máximo max_numeros */
/* max_numeros suele coincidir con el tamaño del array */
void genera_numeros(int numeros[], int max_numeros)
{
  int i;
  srand(time(NULL));
  for (i=0; i<max_numeros; i++)
    {
      numeros[i]=rand()%100+1;
    }
}

/* Pone en pantalla un array de números */
void print_numeros(int numeros[], int max_numeros)
{
  int i;
  for (i=0; i<max_numeros; i++)
    {
      printf("Número %d = %d\n", i, numeros[i]);
    }
}

int ordena_ascendente(const void *a, const void* b)
{
  return (*(int*)a>*(int*)b);
}

int ordena_descendente(const void *a, const void* b)
{
  return (*(int*)a<*(int*)b);
}

int ordena_aleatorio(const void *a, const void* b)
{
  return (rand()%5<3);
}

int ordena_curioso(const void *a, const void* b)
{
  /* Para quitarnos punteros y typecasting */
  int n_a=*(int*)a;
  int n_b=*(int*)b;

  if (n_a>50)
    {
      /* Si n_b>50 (igual que n_a), como ordenamos de mayor a menor, */
      /* la comparación resultará 1 si n_a es menor */
      /* Si no, lo dejamos tal cual */
      return (n_b>50)?(n_a<n_b):0;
    }
  else
    {
      /* Si n_b es menor o igual de 50 (igual que n_a), al ordenar de menor a mayor, */
      /* la comparación resultará 1 si n_a es mayor */
      /* Si no, la comparación será 1 (para separar los mayores de 50 de los menores */
      /* o iguales */
      return (n_b<=50)?(n_a>n_b):1;
    }

}

int main()
{
  int numeros[MAX_NUMEROS];

  genera_numeros(numeros, MAX_NUMEROS);

  qsort(numeros, MAX_NUMEROS, sizeof(int), ordena_ascendente);
  /* qsort(numeros, MAX_NUMEROS, sizeof(int), ordena_descendente); */
  /* qsort(numeros, MAX_NUMEROS, sizeof(int), ordena_aleatorio); */
  /* qsort(numeros, MAX_NUMEROS, sizeof(int), ordena_curioso); */

  print_numeros(numeros, MAX_NUMEROS);

  return 0;
}

Como vemos, esto se suele utilizar en funciones de librería, por el hecho de que son muy generales, es más el comportamiento de la función es siempre el mismo, con pequeñas variaciones en medio del algoritmo, lo que nos obligaría (sin esta técnica) a crear funciones casi iguales, sólo con pequeñas variaciones.

Por eso, aunque quede muy abstracto, si tenemos una función A que en su transcurso ejecuta un fragmento de código que puede variar, podemos hacer que en su ejecución llame una función B y ésta función pueda ser variable.

También podría interesarte...

There are 9 comments left Ir a comentario

  1. Pingback: Bitacoras.com /

  2. Pingback: BlogESfera.com /

  3. Marc /
    Usando Mozilla Firefox Mozilla Firefox 20.0 en Ubuntu Linux Ubuntu Linux

    muy útil

    1. admin / Post Author
      Usando Mozilla Firefox Mozilla Firefox 20.0 en Ubuntu Linux Ubuntu Linux

      Muchas gracias!

  4. Pingback: C++ Punteros a función miembro (pointer-to-member function) o callbacks con clase | Poesía Binaria /

  5. Pingback: Callbacks en C++ con boost | Poesía Binaria /

  6. kovalevsky /
    Usando Google Chrome Google Chrome 34.0.1847.116 en Linux Linux

    Muy buen artículo.
    Esto amplía más mi conocimiento en cuanto a los callback. Es bueno saber que aquí siempre hay artículos para aprender y buenos sobre todo.

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 29.0 en Ubuntu Linux Ubuntu Linux

      Muchas gracias !! Esto de los callbacks nos puede dar una flexibilidad impresionante. Como idea, nos puede ayudar con la ejecución de un lenguaje de scripting que metamos en nuestro programa y también a la hora de simplificar un programa (muchas veces podemos utilizar una misma función para dos tareas parecidas en las que cambia un dato dando parámetros de entrada diferentes, pero y si se tienen que hacer llamadas a funciones también?)

      Un saludo !!

  7. Pingback: Callbacks en C++11 nuevas posibilidades para un software más potente (I) – Poesía Binaria /

Leave a Reply