Objetivos
Material requerido.
Kit inicio UNO | |
Kit Inicio Mega |
Las variables
En los tutoriales de introducción a Arduino, hemos ido jugando con variables de una forma más o menos informal, por una decisión deliberada. Hemos usado los diferentes tipos de variables y hemos operado con ellas, pero sin entrar en muchos detalles.
En concreto hemos utilizado variables locales y globales, pero evitando, discretamente, hacer comentarios sobre el tema. Sin embargo ha llegado el momento de entrar en detalle en una propiedad clave de las variables en C++ (y en prácticamente cualquier lenguaje de programación moderno), llamada el ámbito de las variables y su persistencia.
Para entender el concepto de ámbito (Scope) de una variable, nos conviene dar un pequeño rodeo y hacer historia (Tranquilos, será una batallita muy corta).
Históricamente, en los primeros lenguajes de programación, todas las variables eran variables globales. Esto quiere decir, que cuando definías una de cualquier tipo, su valor era accesible desde cualquier punto del programa. Podías leerla o modificar su valor sin restricciones.
Al principio, puede parecer lo ideal. Pero a medida que los programas van creciendo en tamaño y complejidad, la cosa se complica y tiende a generar problemas complicados de detectar y resolver.
Por ejemplo, a lo largo de las sesiones del curso de introducción hemos usado variables tipo como i, j hasta la saciedad en los bucles for, por ejemplo. Sin un programa tiene media docena de bucles, y por costumbre usamos estas nombres de variables para indexar la iteración, es cuestión de tiempo (y poco, creedme) que la variable de un bucle, se confunda o mezcle con otra de un bucle distinto.
De repente el programa empezara a actuar de forma imprevisible, porque una variable que creemos controlada y sencilla en una zona de programa, toma valores diferentes de los previstos porque usamos ese mismo nombre en otra zona y sencillamente no nos hemos percatado de ello
Establecer la relación entre esa confusión de nombres y los problemas extraños que aparecen, puede ser mucho menos evidente en la realidad de lo que parece ahora en un ejemplo ad hoc, y a medida que el programa crece el problema tiende a crecer hacia el infinito.
Por eso, se propuso una solución muy elegante (Que son las que nos gustan), que las variables no fueran visibles en todo el ámbito del programa, si no solamente en ciertas zonas, de acuerdo a unas reglas claras.
Como C++ exige que las variables se declaren, antes de usarlas, Las variables que se declaran fuera de un bloque de código, son variables Globales, están disponibles desde cualquier punto de un programa.
- Ya hablamos como sin querer en sesiones previas, del concepto de bloque de código como aquel grupo de instrucciones contenidas entre llaves (Apertura y cierre).
Las variables que se declaran dentro de un bloque, se llaman variables locales, y solo existen dentro del bloque en que se definen y no fuera. Cuando nuestro programa entra en el bloque, la variable que se declara, se crea sobre la marcha para su uso interno.
Cuando el programa abandona el bloque, esa variable local se destruye y desaparece en el olvido. La próxima vez que regresemos al bloque se creará de nuevo, sin recordar si ha tenido existencia previa.
De este modo, cuando necesitamos definir variables instrumentales, como para indexar un for, por ejemplo, las variables se crean ad hoc y se destruyen al salir, imposibilitando que una variable local se confunda con otra variable local de otro bloque de código.
Sencillamente no existen variables externas al bloque, excepto las globales, y por tanto su confusión no puede provocar errores imprevistos por utilizar el mismo nombre.
Resolvemos el problema de un plumazo y definitivamente. ¿Elegante, NO?
Vamos a ver algunos ejemplos de esto.
Ejemplos de variables locales y globales
Os recomiendo que no leáis simplemente los ejemplos que os pongo. Copiad el programa en vuestro Arduino y haced pruebas, porque el tema es más delicado de lo que parece y no es fácil ir cogiendo una idea clara.
Usaremos una variable global llamada control, para impedir que el resultado se repita en la consola. Para definir una variable global, basta con declararla fuera de un bloque. Al principio está bien
bool control = true; void setup() { Serial.begin(9600); } void loop() { int j = 3 ; if ( control) { for (j=0 ; j<10 ;j++) { Serial.print("j = "); Serial.println(j); } Serial.println("..........................."); Serial.println(j); } control = false ; // Para que no vuelva a imprimir el resultado }
Piensa un momento antes de ver el resultado. ¿Cuál crees tú que será?
Fijaros que definimos la variable j al principio del loop, pero no la declaramos de nuevo en el for, sino que usamos una variable preexistente.
Por eso el for asume que queremos usar una variable pre declarada y la utiliza para la iteración
El resultado es 10, naturalmente. Hagamos una ligera modificación del programa previo. Vamos a volver a declarar la variable j como int, pero dentro del for.
void loop() { int j = 3 ; if ( control) { for (int j=0 ; j<10 ;j++) // Atentos aquí { Serial.print("j = "); Serial.println(j); } Serial.println("..........................."); Serial.println(j); } control = false ; // Para que no vuelva a imprimir el resultado }
Esta vez, el resultado cambia radicalmente:
La última línea es 3. La variable j del loop es diferente de la j del for, porque la hemos definido dentro de un bloque distinto. Por eso al salir del for su variable local j, se desvanece y la que existe es la variable local del loop y el resultado tras la línea de puntos es 3 y no 10 como en el ejemplo anterior.
- En la instrucción for, y en cualquier otra que se aplica a un bloque, las definiciones que se hagan, se consideran incluidas dentro del bloque al que afecta y no a la parte exterior.
- Una variable solo se puede declarar una vez dentro de un bloque. Cualquier intento de declarar la misma variable por segunda vez, provocara un error fulminante del compilador:
Es importante que comprendáis que esta regla no solo tiene que ver con el concepto formal del bloque, como instrucciones contenidas entre apertura y cierre de llaves, sino que se aplica también a los bloques implícitos en los que prescindimos de estas:
void loop() { int j = 3 ; if ( control) { for (int j=0 ; j<10 ;j++) // Atentos aqui Serial.println(j); Serial.println("..........................."); Serial.println(j); } control = false ; }
Cuyo resultado es el mismo que antes, aunque ahora no hay llaves de por medio, pero si un bloque implícito:
Todos los lenguajes de programación modernos incluyen el concepto de variables locales y variables globales y aplican las reglas que hemos descrito aquí (Los lenguajes se copian unos a otros claro, y las buenas ideas se reproducen)
A esa propiedad de las variables, de existir durante un cierto tiempo, se le suele llamar persistencia de la variable.
Variables static
Esta propiedad característica de las variables locales, su persistencia, para crearse de la nada y desaparecer en el olvido al salir de un bloque, es una gran ventaja y ahorra muchas horas de vagar en pena por los pasillos a los programadores (Mientras meditan en qué demonios será lo que está mal. De aquí la injusta fama de pirados).
Pero a veces (La vida es complicada) esto puede ser un inconveniente y nos gustaría que ciertas variables locales, no desapareciesen al salir, o al menos que no perdiéramos su valor.
Imagínate un Arduino que está leyendo datos de un sensor y almacenando su valor en una variable local. Nos gustaría saber el último valor de lectura cuando volvamos a esa función.
- Siempre podemos definir una variable global, pero puede no tener mucho sentido si solo se utiliza en una función en exclusiva.
Para esto, podemos definir una variable local como estática (static). Quiere decir que solo será accesible desde esta función, pero que su valor no desaparecerá al salir, sino que persistirá en el tiempo, mientras no cerremos el programa, conservando su último valor.
Veamos un ejemplo sencillo sacado de la Wikipedia:
void loop() { if ( control) { Funcion1 (); // muestra 0 Funcion1 (); // muestra 1 Funcion1 (); // muestra 2 } control = false ; } void Funcion1 () { int x = 0; // Declaramos x como local Serial.println(x); x = x + 1; }
En la definición de Funcion1, declaramos x como int, sin más, por que el resultado será así:
Cuando entramos em Funcion1, la int x se declara, se crea sobre la marcha y se le asigna el valor 0.
Cada vez que salimos de Funcion1, la variable x se desvanece y se vuelve a crear la próxima vez que entremos en ella, tal y como corresponde a una variable local obediente. Y se repite el proceso.
Pero hagamos un pequeño cambio. En Funcion1 vamos a declarar la variable x como static.
void loop() { if ( control) { Funcion1 (); // muestra 0 Funcion1 (); // muestra 1 Funcion1 (); // muestra 2 } control = false ; } void Funcion1 () { static int x = 0; // Declaramos x como static Serial.println(x); x = x + 1; }
El resultado es, que la variable x no se desvanece ya al salir, sino que conserva su valor para la próxima vez que volvemos a Funcion1. Sigue siendo una variable local, no puede ser usada por ningún programa o función fuera de Funcion1, pero no desaparece al salir sino que persiste.
Nótese además, de que como la variable estática no se destruye al salir, se crea solo una vez y persiste indefinidamente. La declaración de variable y la asignación de la variable se realizan una única vez.
- Esto hace que a x solo se le asigna el valor 0 cuando se crea y no cada vez que entra en la Funcion1, como podría pensarse inicialmente. Mucho cuidado con esto
Resumen de la sesión
En este curso arduino hemos aprendido lo siguiente: