Archivo

Entradas Etiquetadas ‘callback’

Listas enlazadas en Arduino (primera versión)

Miércoles, 30 de Noviembre de 2011 Gaspar Fernández Sin comentarios

Es parte de un proyecto personal que quiero compartir. Se trata de una clase para utilizar listas enlazadas desde Arduino, aunque la memoria RAM de estos bichillos no sea gran cosa (1K para la versión Diecimila), puede dar para mucho, y es que hay ocasiones en las que podemos necesitar esta flexibilidad a la hora de almacenar y acceder a la información (aunque no pueda ser mucha).

La clase ha sido desarrollada utilizando templates, por lo que podréis particularizar esta clase para cualquier tipo de dato que estéis manejando. Declarando la lista de la siguiente forma:

1
LList <tipo> lista;

Antes de dar el código, hagamos algunas operaciones, vamos a ver lo que podemos hacer con 1K (vale con 600 bytes libres):

  • Con un tipo int, cada nodo ocupará 4 bytes: 600 / 4 = 150 nodos (alguno menos en realidad). Pero estos nodos pueden indicar posiciones en un mapa, un pequeño informe de lo que ha transcurrido, lecturas, estado de periféricos, etc
  • Con un tipo:
    1
    2
    3
    4
    5
    struct Map
    {
      int key;
      int value;
    }

    : podemos almacenar 600/6 = 100 elementos con clave y valor, por ejemplo, una matriz o vector disperso que nos pueden ayudar para un cálculo matemático

  • Lo malo son las cadenas, no podemos escribir un libro, pero si hacemos un tipo que almacene unas 28 letras, cuyo nodo ocupará 30 bytes, podremos almacenar 600/30=20 mensajes distintos, aunque recomiendo que esos mensajes sean generados por el programa, ya que si los mensajes son predefinidos, podemos almacenarlos en la memoria flash, que tendremos más sitio.

Ahí va el código para los copy-paste aunque el archivo lo podéis bajar de aquí (9.5Kb). Por cierto, al final del archivo hay un código de ejemplo para probar que todo funcione bien.

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
/**
*************************************************************
* @file ArduinoList.pde
* Another implementation of linked lists to use in Arduino.
*    Copyright (C) 2011 Gaspar Fernández
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
* @brief Linked list implementation for Arduino
*
* Features:
*   - Can access to head and tail directly
*   - Can pop back and front from this class
*   - Can access any item from the item number
*   - Can set a callback function when an error occurs
*
* @author Gaspar Fernández <blakeyed@totaki.com>
* @web http://totaki.com/poesiabinaria
* @version 0.1
* @date 27 nov 2011
* Change history
*
* To-do:
*   - Better control over non-critical errors, maybe another callback
*   - Pre-defined error methods: by serial, by output leds...
*************************************************************/


#define LLIST_ERROR_NOMEMORY     1 // Not enough memory to insert element
#define LLIST_ERROR_NOTAIL       2 // Can't get last items
#define LLIST_ERROR_NOFRONT      3 // Can't get head items
#define LLIST_ERROR_NOITEMS      4 // List empty, can't read items
#define LLIST_ERROR_NEITEMS      5 // Not enough items (pos>=sz)

template <typename Type>
class LList
{
public:
  typedef void (*ErrorCallback)(int);

  // We can add more error types
  enum ListErrorType
    {
      NO_ERROR_REPORTING,   // No error reporting
      CALLBACK          // Errors will trigger a callback
    };

  // Destruction. Clear data
  ~LList();

  // Initialization
  LList();

  // Inserts new item in the last position
  void push_back(const Type item);
  // Is the list empty?
  inline bool isEmpty() const;
  // Returns the size of the list
  inline int size();
  // Returns the last item
  inline Type back();
  // Returns the first item
  inline Type front();
  // Return the last item and delete it
  Type pop_back();
  // Return the first item and delete it
  Type pop_front();

  // Get the item at position pos
  Type getElement(unsigned pos);
  // Insert a new item at position pos
  void insert(int pos, Type item);
  // set error callback function
  void setErrorCallback(ErrorCallback cback);
  // no error reporting
  void setNoErrorReporting();
  // clears the list
  void clear();
private:
  // Reports an error
  void report_error(int err);
 
  // Deletes a list when there is only one element
  void delete_list();

  typedef struct node
  {
    Type item;
    node *next;
  } node;

  node *head;           // Pointer to the head
  node *tail;           // Pointer to the tail
  int sz;           // List size
  ListErrorType errorType; 
  ErrorCallback ecback;     // Callback when reporting an error
};

template <typename Type>
LList<Type>::LList()
{
  head=NULL;
  tail=NULL;
  sz=0;
  errorType=NO_ERROR_REPORTING;
}

template <typename Type>
LList<Type>::~LList()
{
  clear();          // Clears the list before destroying
}

template <typename Type>
void LList<Type>::push_back(const Type item)
{
  node *aux=tail;
 
  // Reserve memory for a new node
  tail=(node*)malloc(sizeof(node));
  if (tail==NULL)
    {
      report_error(LLIST_ERROR_NOMEMORY);
      return;
    }

  // Stores the item information
  tail->item=item;
  tail->next=NULL;

  // Link to the list
  if (isEmpty())
    head=tail;
  else
    aux->next=tail;

  sz++;

}

template <typename Type>
bool LList<Type>::isEmpty() const
{
  return (sz==0);       // (sz==0) = EMPTY
}

template <typename Type>
int LList<Type>::size()
{
  return sz;
}

template <typename Type>
inline Type LList<Type>::back()
{
  if (isEmpty())        // List empty = No last item
    {
      Type t;
      report_error(LLIST_ERROR_NOTAIL);
      return t;
    }

  return tail->item;
}

template <typename Type>
inline Type LList<Type>::front()
{
  if (isEmpty())        // List empty = No first item
    {
      Type t;
      report_error(LLIST_ERROR_NOFRONT);
      return t;
    }

  return head->item;
}

template <typename Type>
Type LList<Type>::pop_back()
{
  node *aux=head;
  Type t;

  if (isEmpty())        // List empty = No last item
    {
      report_error(LLIST_ERROR_NOTAIL);
      return t;
    }
 
  if (head==tail)       // If there is only 1 item
    {
      t=head->item;
      delete_list();
      return t;
    }
  else
    {               // More than one item
      while (aux->next!=tail)   // Searches for the item previous to the last
    aux=aux->next;
     
      t=tail->item;     // I will return the tail item
      free(tail);      
      tail=aux;         // Define the new tail
      tail->next=NULL;
      sz--;         // 1 item less

      return t;
    }
}

template <typename Type>
Type LList<Type>::pop_front()
{
  Type t;
  node *aux=head;
 
  if (isEmpty())        // List empty = No head
    {
      report_error(LLIST_ERROR_NOFRONT);
      return t;
    }

  t=aux->item;          // I will return the head
  head=head->next;      // The new head is the item after the head
  free(aux);
  if (head==NULL)       // If I had deleted the last item, replace the tail
    tail=NULL;          // too.
  sz--;

  return t;
}

template <typename Type>
void LList<Type>::delete_list()
{
  free(head);           // I will call this method when deleting
  head=NULL;            // a list with only one element, so all
  tail=NULL;            // the variables are set.
  sz=0;
}

template <typename Type>
void LList<Type>::insert(int pos, Type item)
{
  node *newitem;
  node *aux=head;
  int i;

  // Allocate memory for the new item
  newitem=(node*)malloc(sizeof(node));
  if (newitem==NULL)
    {
      report_error(LLIST_ERROR_NOMEMORY);
      return;
    }
  // Stores the item information
  newitem->item=item;
  newitem->next=NULL;

  // Link the item to the list
  if (isEmpty())
    {               // If the list is empty there will be only
      head=newitem;     // one item, so it will be the head and the
      tail=newitem;     // tail.
    }
  else if (pos==0)      // If the item is going to be inserted at the beginning
    {
      newitem->next=head;   // we will move the head
      head=newitem;
    }
  else if (pos>=sz)     // If the position is greater than the last item's position
    {               // it will be inserted after this item, at the tail.
      tail->next=newitem;      
      tail=newitem;
    }
  else
    {               // If not, we will have to find the position of the
      i=0;          // previous item to link its next pointer to the new
      while (i<pos-1)       // element.
    {
      aux=aux->next;
      ++i;
    }
      newitem->next=aux->next;  // And then link the next pointer of our new element
      aux->next=newitem;    // to where the next pointer of the previour element
    }               // was pointing.
  sz++;
}

template <typename Type>
Type LList<Type>::getElement(unsigned pos)
{
  int i=0;
  Type t;
  node *aux=head;

  if (isEmpty())        // If the list is empty = No data
    {
      report_error(LLIST_ERROR_NOITEMS);
      return t;
    }

  if (pos>=sz)          // If the item asked for is greater than the
    {               // last item's position. There is no valid element
      report_error(LLIST_ERROR_NEITEMS);
      return t;
    }
 
  if (pos==sz-1)        // If the item we asked for is the last, we can
    return tail->item;      // return it inmediately


  while (i<pos)         // If not, we must look for it
    {
      aux=aux->next;
      i++;
    }
  return aux->item;
}

template <typename Type>
void LList<Type>::setErrorCallback(ErrorCallback cback)
{
  ecback=cback;         // This method sets the error callback to invoke
  errorType=CALLBACK;       // when an error occurs.
}

template <typename Type>
void LList<Type>::report_error(int err)
{
  if (errorType==CALLBACK)  // If the error reporting is through a callback,
    {               // call it.
      ecback(err);
    }
}

template <typename Type>
void LList<Type>::clear()
{
  node *aux;

  while (head!=NULL)        // Delete all elements
    {
      aux=head;
      head=head->next;
      free(aux);
    }
 
  tail=NULL;            // Restore variable stats
  sz=0;
}  

template <typename Type>
void LList<Type>::setNoErrorReporting()
{
  errorType=NO_ERROR_REPORTING;
}

// Test program
// LList<int> l;

// void blink(int err)
// {
//   while (1)
//     {
//       digitalWrite(11,HIGH);
//       delay(1000);
//       digitalWrite(11, LOW);
//       delay(1000);  
//     }
// }

// void setup()
// {
//   pinMode(11, OUTPUT);
//   Serial.begin(9600);
//   l.setErrorCallback(blink);
//   l.push_back(45);
//   l.push_back(42);
//   l.push_back(47);
//   l.push_back(89);
//   l.insert(0, 33);
//   l.insert(1, 53);
//   l.insert(9, 73);
//   // 33
//   // 53
//   // 45
//   // 42
//   // 47
//   // 89
//   // 73
// }

// void loop()
// {
//   char r;
//   if (Serial.available()>0)
//     {
//       while (Serial.available()>0)
//  {
//    r=Serial.read();
//    delayMicroseconds(10000);
//  }

//       for (unsigned i=0; i<l.size(); i++)
//  {
//    Serial.println(l.getElement(i), DEC);
//  }
//       Serial.print("Elements: ");
//       Serial.println(l.size(), DEC);
//       Serial.print("Last: ");
//       Serial.println(l.pop_front(), DEC);
//     }
// }

Conociendo el proceso que me ha enviado una señal (signal)

Jueves, 24 de Noviembre de 2011 Gaspar Fernández Sin comentarios

Hace tiempo hablábamos de capturar señales, aunque en ocasiones, es necesario saber quién me envía esa señal, si por ejemplo nos envían un SIGINT o SIGTERM, tal vez queremos saber qué proceso nos quiere muerto y qué usuario lo ha invocado. O tal vez estamos esperando una señal de control (SIGUSR1, por ejemplo) por parte de un proceso cliente específico.

El problema es que por nuestro modo actual de direccionar señales (con signal(señal, funcion)) sólo comunicamos el número de señal que se ha recibido, sin más información.

signals_from

Para capturar esta información necesitamos utilizar sigaction para establecer la función a la que llamaremos cuando llegue la señal, para utilizarla podemos utilizar el siguiente código:

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
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

void recibe_signal(int signum, siginfo_t *siginfo, void *context)
{
  /* Mostramos toda la información necesaria */
  printf ("[%d] Recibida %d (%s) de %d (UID: %d) ¿Error? %d \n",
      getpid(),
      signum,
      strsignal(signum),
      siginfo->si_pid,
      siginfo->si_uid,
      siginfo->si_errno);
}


int main(int argc, char *argv[])
{
    struct sigaction sact;
   
    sigfillset(&sact.sa_mask);
    /* Definimos la función callback */
    sact.sa_sigaction = recibe_signal;
    /* Queremos usar el callback sigaction en lugar del dado por signal() */
    sact.sa_flags     = SA_SIGINFO;

    sigaction(SIGUSR1, &sact, NULL);

    /* Bucle infinito, para cerrar el proceso, utilizar kill -9 PID */
    while (1)
      pause;

    return 0;
}

Como vemos sigaction tiene 3 parámetros:

  • La señal que queremos capturar
  • Un registro de tipo struct sigaction
  • La acción que se ejecutaba antes, por si queremos guardarla para restaurarla más tarde

Dentro del registro, tendremos como valores importantes (struct sigaction act):

  • sact.sa_sigaction : que especifica la función que la llamaremos, esta función será del tipo void funcion(int, siginfo_t*, void*), donde el primer int recibirá la señal que nos han enviado (tal y como hacía la función que asignábamos con signal), el segundo parámetro será información asociada a la señal, y la tercera el contexto de ejecución.
  • sact.sa_handler : será la función a la que se llama, es la misma que cuando hacemos signal(señal, funcion)
  • sact.sa_flags : Es un conjunto de flags, si especificamos SA_SIGINFO, estaremos dando paso a la función especificada por sa_sigaction, si no utilizaremos sa_handler. Aunque esta variable vale para más cosas, para esta práctica nos vale con saber esto. (man sigaction para más info)
  • sact.sa_mask
  • : Nos proporciona una máscara de las señales que serán bloquedas, podemos utilizar sigfillset() para rellenar toda la máscara automáticamente. Si queremos que unas se bloqueen y otras no podemos utilizar sigaddset() y sigdelset()

Ahora bien, la función que se llamará cuando venga una señal tendrá el prototipo que hemos visto antes con tres parámetros: la señal recibida, la información de la señal con un puntero a siginfo_t y el contexto de ejecución, que se pasará con un puntero a void, que por ahora no utilizaremos. La estructura que controla la información de la señal entre otros muchos datos nos facilitará lo siguiente:

  • si_pid : PID que nos envía la señal (y esta acción da título al post)
  • si_uid : Usuario que ejecuta el proceso que nos manda la señal
  • si_errno : Algún error que haya causado la señal
  • … para más información, man sigaction

Para preparar la sentencia tendremos que escribir varias líneas definiendo los valores del registro así como la llamada a la función sigaction(), por lo tanto podremos crear una función que tenga los parámetros más comunes a la hora de definir este tipo de acciones, así lo llamamos como a signal():

1
2
3
4
5
6
7
8
9
10
11
12
int sigact(int signum, void funcion(int, siginfo_t *, void *))
{
    struct sigaction sact;
   
    sigfillset(&sact.sa_mask);
    /* Definimos la función callback */
    sact.sa_sigaction = funcion;
    /* Queremos usar el callback sigaction en lugar del dado por signal() */
    sact.sa_flags     = SA_SIGINFO;

    return sigaction(signum, &sact, NULL);
}

En esta función es importante poner bien el prototipo de la función como parámetro, eso lo comenté en un post anterior sobre callbacks

Ahora, vamos a dejar el código más bonito, este programa implementará el control de errores y nos permitirá salir cuando recibamos 5 SIGINT (pulsemos 5 veces control+C):

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 <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

void error(char *cadena);
int sigact(int signum, void funcion(int, siginfo_t *, void *));
void llegasigint();
void recibe_signal(int signum, siginfo_t *siginfo, void *context);

int main(int argc, char *argv[])
{
  if (sigact(SIGUSR1, recibe_signal)==-1)
    error("No puedo establecer SIGUSR1");

  if (sigact(SIGINT, recibe_signal)==-1)
    error("No puedo establecer SIGINT");
  if (sigact(SIGTERM, recibe_signal)==-1)
    error("No puedo establecer SIGTERM");
  if (sigact(SIGUSR2, recibe_signal)==-1)
    error("No puedo establecer SIGUSR2");
  if (sigact(SIGQUIT, recibe_signal)==-1)
    error("No puedo establecer SIGQUIT");

    /* Bucle infinito, para cerrar el proceso, utilizar kill -9 PID */
    while (1)
      pause();

    return 0;
}

void error(char *cadena)
{
  fprintf(stderr, "ERROR: %s (errno: %d: %s)\n", cadena, errno, strerror(errno));
  exit(1);
}

void llegasigint()
{
  static int num=0;     /* No he querido usar variables globales */

  num++;
  if (num==5)
    {
      printf ("FIN del programa\n");
      exit(1);
    }
}

void recibe_signal(int signum, siginfo_t *siginfo, void *context)
{
  /* Mostramos toda la información necesaria */
  printf ("[%d] Recibida %d (%s) de %d (UID: %d) ¿Error? %d \n",
      getpid(),     /* PID del proceso actual */
      signum,       /* Número de señal recibida */
      strsignal(signum),    /* Señal escrita en texto (inglés) */
      siginfo->si_pid,  /* PID del proceso que nos manda la señal */
      siginfo->si_uid,  /* Usuario que nos manda la señal */
      siginfo->si_errno);   /* Error producido */

  if (signum==SIGINT)
    llegasigint();
}

int sigact(int signum, void funcion(int, siginfo_t *, void *))
{
    struct sigaction sact;
   
    sigfillset(&sact.sa_mask);
    /* Definimos la función callback */
    sact.sa_sigaction = funcion;
    /* Queremos usar el callback sigaction en lugar del dado por signal() */
    sact.sa_flags     = SA_SIGINFO;

    return sigaction(signum, &sact, NULL);
}

Cuando PHP jura en hebreo (PAAMAYIM NEKUDOTAYIM)

Sábado, 22 de Enero de 2011 Gaspar Fernández 2 comentarios

Son dos palabras raras que parece que quieren despistarnos. Y no es inglés, ni japonés, ni ruso… es hebreo.
Y todo se remonta al 1997 cuando Zend Technologies reescribe el motor de PHP planteado por Rasmus Lerdorf.
Zend es una compañía de origen israelí, y de ahí que haya palabras en hebreo.

Pero… qué quiere decir esto ? La traducción es “dobles dos puntos” o lo que es lo mismo “::”. Esto es el operador de resolución de contexto (o ámbito) (Scope Resolution Operator en inglés) que tiene PHP y que desde PHP 3 identifica a qué nos referimos; y cuando programamos orientado a objetos, ayudará a PHP a identificar si llamamos a un método, una constante, atributo, etc. (Como C++).
Cuando hacemos Clase::método() para referirnos a un método estático PHP pone en práctica todo esto.

¿Qué pasa cuando hay un error en esta llamada? PHP nos devolverá un error del tipo: “Unexpected T_PAAMAYIM_NEKUDOTAYIM”, diciendo que hemos puesto “::” de forma inesperada. Podemos probarlo así desde consola:

$ php -r “::”

Y es que :: tiene la misión de detectar que delante ponemos una clase, o una palabra clave que le haga referencia ($this, self, parent, static…), y luego un método/atributo/constante/… aunque como veremos a continuación hay que tener cuidado con la versión de PHP que se utilice.

Es un error común cuando utilizamos PHP5 (<5.3.0) y queremos llamar a un método que está contenido en una variable:

1
2
$metodo="metodo_que_quiero_llamar";
Clase::$metodo

ya que no está soportado en esta versión. En cambio a partir de la 5.3 sí que podremos hacerlo. Mientras tendremos que utilizar call_user_func().

Así que si nos encontramos con este error, querrá decir que PHP no tiene claro a qué nos referimos, a veces lo encontramos cerca de :: pero otras veces no es tan claro, como por ejemplo aquí:

1
2
3
4
5
<?php
define('miConstante', 'algo');
if (empty(foo))
   echo 'vacía';
?>

Aquí fallará al hacer la llamada a empty() y es que tenemos que introducirle una variable como parámetro y no una constante (recordemos que el operador de resolución de contexto es el encargado de identificar variables, constantes y todo esto).

Más información:
Scope Resolution Operator [ Wikipedia ]
Ámbito de las variables [ PHP.NET ]
PHP Paamayim Nekudotayim [ The secret's in the code ]

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

Lunes, 17 de Enero de 2011 Gaspar Fernández Sin comentarios

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.

Función curiosa: preg_replace_callback (PHP)

Lunes, 15 de Marzo de 2010 Gaspar Fernández 2 comentarios

A veces tenemos la necesidad de reemplazar un texto dentro de una cadena larga. Por ejemplo, el uso principal que le doy a esta función es el procesamiento de plantillas para páginas web, en donde tengo la página en HTML puro por un lado, y en su interior hay ciertas palabras clave, por ejemplo —seccionA—, —fotoUsuario—, —menuSesion—, etc; y en la web definitiva, aparecerá otra cosa en lugar de ese texto, aparecerá el objeto al que hace referencia.
Aunque cada cadena de texto seccionA, fotoUsuario, requiere un procesamiento distinto y es posible que no tengamos por qué procesar siempre todas las equivalencias de todos los textos. Queremos en definitiva reemplazar —seccionA— por el resultado de una función que ejecutaremos cuando encontremos ese texto. Sería como:

1
2
3
4
$disparadores=array("---seccionA---", "---fotoUsuario---", "---menuSesion---");
$reemplazos=array(dibuja_seccionA(), get_fotoUsuario(), dibuja_menuSesion);

$web=str_replace($disparadores, $reemplazos, $web);

Aunque, como dijimos, en el ejemplo anterior, se tienen que ejecutar dibuja_seccionA(), get_fotoUsuario() y dibuja_menuSesion().

Pero podemos aprovecharnos de las expresiones regulares, para extraer texto que esté entre “—” y “—” y pasar lo que hay entre medias a otra función que decidirá qué hacer; eso hace preg_replace_callback().

1
2
3
4
5
6
7
8
9
10
function quehacer($texto)
{
  switch ($texto[1]) // [0] devuelve: ---texto---, [1] devuelve texto
  {
     case 'seccionA': return dibuja_seccionA();
     case 'fotoUsuario': return get_fotoUsuario();
     case 'menuSesion': return dibuja_menuSesion();
  }
}
$web=preg_replace_callback('/---([a-zA-Z0-9_]*)---/', 'quehacer', $web);

Hasta aquí bien, el primer parámetro es una expresión regular (es un tema muuuy extenso), el segundo es la función a la que llamamos cuando encontramos el texto, y el tercero es de dónde lo sacamos. Bueno, ahora surge un problema, imaginad que necesito pasarle parámetros a esa función callback. Lo podemos hacer así:

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
function guardar_datos($data=null)
{
  static $guardados; // Una variable estática, muy importante

  // Si se especifica $data, lo guardamos en $guardados, al ser estática, aunque salgamos de la función, el valor de la variable no se perderá.
  if ($data)
    $guardados=$data;
 
  return $guardados;
}

function quehacer($texto)
{
  $datos = guardar_datos();

  switch ($texto[1]) // [0] devuelve: ---texto---, [1] devuelve texto
  {
     case 'seccionA': return dibuja_seccionA();
     case 'fotoUsuario': return get_fotoUsuario($datos);
     case 'menuSesion': return dibuja_menuSesion($datos);
  }
}

guardar_datos(array($datos_usuario, $datos_sesion));
$web=preg_replace_callback('/---([a-zA-Z0-9_]*)---/', 'quehacer', $web);

Nota: las variables $datos_usuario, $datos_sesion y $web no están definidas, como esto es parte de un programa, hay que imaginar un poco cuál será la información que contendrán.

Visita otras webs de la red