Un concepto de programación muy útil en muchos lenguajes es la capacidad de introducir en sus funciones un número indeterminado de parámetros y poder crear diferentes multiconstructores según las necesidades del usuario.
El lenguaje de Arduino está basado en C++ y por fortuna se pueden utilizar librerías y funcionalidades asociadas a este lenguaje, como la programación orientada a objetos.
Estas funcionalidades se pueden extender sobre todo si disponemos de una librería ya creada, pero de todas maneras haremos un ejercicio para introducir este concepto y más adelante asociaremos la capacidad de crear multiconstructores con parámetros de entrada variables a un objeto.
Éste es un modo avanzado de programación extensible a otros lenguajes de programación, como vimos en el multiconstructor PHP.
Un primer caso de estudio
Para empezar comenzaremos definiendo una función en la que no conocemos a priori el número de parámetros de entrada. En este caso trataremos de calcular la media de todos los valores introducidos.
Para realizar este ejemplo emplearemos “variable argument list” que se asocia a una librería llamada <stdarg.h> del compilador de C.
En esta librería se pueden declarar objetos referenciados como: va_list.
Existen solo 4 funciones en esta librería que operan con este tipo de lista que son:
- va_start(va_list,n) –> Inicialización de la lista y puesta a cero del puntero. n es el parámetro auxiliar desde el que se comienza a contar la lista.
- va_arg(va_list, format) –> Lectura del siguiente puntero de la lista. El parámetro format se refiere al tipo de dato asociado en la lista, que puede ser entero, caracter, etc…
- va_end(va_list)–> Cierre de la lista
- va_copy(va_list dest, va_list src) –> Copia de la lista
#include <stdarg.h> //Es importante declarar la función antes del Setup float media(int n, ...){ float sum=0; va_list list; va_start(list,n); for(int i=0; i<n; i++){ int arg= va_arg(list,int); // Se indica como parámetro el tipo de dato que ha de leer. sum+=arg; Serial.println(arg); } va_end(list); Serial.print("El valor medio es: "); Serial.println(sum/n); return sum/n; } void setup(){ Serial.begin(9600); //Hay que determinar el número de parámetros introducidos al inicio(En este caso 5 valores) y a continuación cada uno de ellos. float value=media(5,1,2,3,4,5); } void loop(){ }
Hay que comentar cuales son las carencias y faltas de ésta manera de programar.
Para empezar en la programación en C, se debe al menos introducir un parámetro de entrada. Es decir que no podemos crear la función con solo unos puntos suspensivos haciendo referencia a que se pueden introducir todos los parámetros que se quieran. En este ejemplo hemos elegido éste parámetro de entrada como la longitud de la lista, pero podría ser otro cualquiera, como veremos más adelante.
Como curiosidad y aunque parezca extraño, ésta función se ha de declarar siempre antes del setup, porque si no no la reconoce como una función declarada dentro del programa. Cosa que no ocurre con cualquier otro tipo de función.
Si se ejecuta el siguiente código podremos observar que el compilador no la reconoce.
#include <stdarg.h> // Declaration, but not definition, of media(). //float media(int n, ...); void setup(){ Serial.begin(9600); float value=media(5,1,2,3,4,5); } void loop(){ } float media(int n, ...){ float sum=0; va_list list; va_start(list,n); for(int i=0; i<n; i++){ int arg= va_arg(list,int); // Se indica como parámetro el tipo de dato que ha de leer. sum+=arg; Serial.println(arg); } va_end(list); Serial.print("El valor medio es: "); Serial.println(sum/n); return sum/n; }
Mientras que si creamos una función auxiliar que llame a ésta en lugar del setup y el loop podremos observar que sí que funciona. Otra opción es declarar la función antes del setup sin definirla y dejar su definición debajo del todo.
#include <stdarg.h> void setup(){ Serial.begin(9600); float value=media(); } void loop(){ } float media(int n, ...){ float sum=0; va_list list; va_start(list,n); for(int i=0; i<n; i++){ int arg= va_arg(list,int); // Se indica como parámetro el tipo de dato que ha de leer. sum+=arg; Serial.println(arg); } va_end(list); Serial.print("El valor medio es: "); Serial.println(sum/n); return sum/n; } float media2(){ return media(5,1,2,3,4,5); }
Esto es un problema si lo que queremos es introducir esta misma función dentro de una librería, ya que de esta misma manera si la ejecutamos en el setup o en el loop y está definida libremente; nos encontraremos con que la función no está declarada. Para solucionar este problema deberemos crear ésta función dentro de otra función o dentro de la declaración de un objeto.
Por otra parte, se advierten que su comportamiento no está bien definido (the behaviour is undefined), ya que no hay compatibilidad entre los tipos de datos arrojados por la lista y se puede acceder a una dirección a priori desconocida. Así que su uso se ha de restringir mucho para una tarea muy concreta que se sepa que va a funcionar bien, pero por sí sola no es del todo robusta.
Segundo caso de estudio
Ahora se va a crear otro programa extendiendo su uso para la identificación de el tipo de dato introducido. En este ejemplo podremos observar que añadimos robustez a lo que se introduce mediante la extensión de un objeto con propiedades que identifican el tipo de dato.
// ShowVar takes a format string of the form "ifs", // where each character specifies the // type of the argument in that position. // // i = int // f = float // s = string (char *) // // Following the format specification is a variable // list of arguments. Each argument corresponds to // a format character in the format string to which // the szTypes parameter points void ShowVar( char *szTypes, ... ) { va_list vl; int i; // szTypes is the last argument specified; you must access // all others using the variable-argument macros. va_start( vl, szTypes ); // Step through the list. for( i = 0; szTypes[i] != '\0'; ++i ) { union Printable_t { int i; float f; char *s; } Printable; switch( szTypes[i] ) { // Type to expect. case 'i': Printable.i = va_arg( vl, int ); Serial.println( Printable.i ); break; case 'f': Printable.f = va_arg( vl, double ); Serial.println( Printable.f ); break; case 's': Printable.s = va_arg( vl, char * ); Serial.println( Printable.s ); break; default: break; } } va_end( vl ); } void setup(){ Serial.begin(9600); ShowVar( "siff", "hi", 5, 6.34f, 4 ); } void loop(){ }
Para empezar, hay que explicar que el parámetro va_start no está determinado por el número de parámetros introducidos, sino por una cadena de caracteres que identifican los tipos de dato que se establecen a continuación. De esta manera se leerán tantos parámetros en la lista como caracteres “i“, “f” o “s” haya en la cadena.
El termino “union” es una clase declarada dentro de C. y Printable es el nombre que se le ha dado a este objeto que tiene asociadas unas propiedades que son los diferentes valores leidos de la lista.
**Las librerias stdio y stdarg están incluidas por defecto dentro de Arduino, así que es muy posible que no haga falta hacer el include previamente.