ARD-SYN 04 – Sintaxis (IV). Control de flujo

Facebooktwittergoogle_pluslinkedinmailFacebooktwittergoogle_pluslinkedinmail

El proceso “natural” de ejecución de un sketch implica que las instrucciones se ejecuten una detrás de otra, desde la primera hasta la última. Por supuesto, esto supone una forma de trabajo que no es adecuada en la práctica. Hay veces que, dependiendo del estado de ciertas variables, el skecth debe ejecutar una parte del código, u otra distinta. También existen fragmentos del código que deben ejecutarse reiterativamente un cierto número de veces, antes de seguir adelante. De todo esto se encargan las estructuras de control de flujo, de las que vamos a hablar en este artículo.

Si ya has programado antes en algún lenguaje, como JavaScript, PHP, u otros, casi puedes leer este artículo “en diagonal”, ya que las estructuras de control de flujo presentan muchas similitudes entre lenguajes modernos, con muy pocos detalles propios del lenguaje. Si eres nuevo en este mundillo, espero que este artículo será una buena referencia para empezar.

CONDICIONALES

En ocasiones queremos que, llegados a un punto, el sketch analice una o más variables y, en función del estado de estas ejecute un fragmento de código o no lo ejecute. Para ello empleamos la estructura if (condición) {sentencias}. Observa las siguientes líneas de ejemplo:

if (creditos < 500) {
    Serial.println ("Aún no tienes bastantes créditos.");
    Serial.println ("Debes continuar jugando.");
}

Este ejemplo está basado en un juego hipotético en el que, por ejemplo, para pasar de nivel sería necesario alcanzar un mínimo de créditos (o puntos, o lo que sea). En la condición evaluamos si el número de créditos es menor que 500. El operador < evalúa que el término situado a la izquierda sea menor que el situado a la derecha (al pie de este artículo se recopilan todos los operadores que se usan para comparación de valores).

También podemos darle al sketch una salida “alternativa”. Si se cumple cierta condición, se ejecutarán algunas sentencias. Si la condición no se cumple, se ejecutarán otras diferentes. La clave de esto está en la palabra reservada else, cuyo significado podríamos traducir por “en caso contrario”. Observa las siguientes líneas:

if (creditos < 500) {
    Serial.println ("Aún no tienes bastantes créditos.");
    Serial.println ("Debes continuar jugando.");
} else {
    Serial.println ("Lo has conseguido.");
    Serial println ("Pasas al siguiente nivel.");
}

Como ves, aquí le decimos al sketch que si el número de créditos es menor que 500 (la condición evaluada es cierta) muestre un determinado mensaje. En caso contrario mostrará un mensaje diferente. En todo caso, el mensaje que se muestre dependerá del resultado de haber evaluado la condición. Nunca se mostrarán los dos mensajes a la vez.

CONDICIONALES COMPUESTOS

No siempre se evalúan condiciones tan simples como las que hemos puesto de ejemplo arriba. En ocasiones se evalúan dos o más condiciones simultáneamente, formando una estructura compuesta más compleja. Observa el siguiente ejemplo:

if (edad < 18) {
    Serial.println ("Aún no estás en edad de votar.");
} else if (edad > 17 && edad < 67) {
    Serial.println ("Usted está en edad laboral.");
} else {
    Serial.println ("Usted ha alcanzado la edad de jubilación.");
}

En el primer caso se evalúa si la variable edad es menor que 18. Si la condición resultara ser cierta, se muestra el primer mensaje en la consola serie y se acabó. La ejecución del sketch continuará debajo del final de la estructura condicional mostrada. Si la primera condición no es cierta, se encadena con otra evaluación. En este caso, se comprueba si la edad es mayor que 17 y menor que 67. El operador && (léase AND) une dos condiciones, de forma que, para que el condicional se evalúe como verdadero, ambas deben cumplirse. Una vez más te remito al final de este artículo, donde se recopilan todos los operadores. Siguiendo con el ejemplo, si la edad es, pongamos, 50, se cumple que es mayor que 17 y al mismo tiempo, menor que 67. Esto significa que se mostrará en la consola el segundo mensaje, y la ejecución continuará por debajo del sketch. Vemos otro ejemplo a continuación:

if (creditos < 500 && ! jefe_derrotado) {
    Serial.println ("Has jugado fatal. Mejor empieza de nuevo.");
} else if (creditos > 500 && jefe_derrotado) {
    Serial.println ("Felicidades. Pasas al siguiente nivel.");
} else {
    Serial.println ("Tu juego es mediocre. Debes seguir entrenando.");
}

Veamos que ocurre. En la primera condición se evalúan dos condiciones simples, basadas en dos variables diferentes. La primera evalúa una variable numérica y la segunda evalúa una variable booleana (que, en este ejemplo, hemos llamado jefe_derrotado). Cunado se evalúa una variable booleana, si vale true, la condición es cierta. Si vale false, la condición es falsa. Si precedemos el nombre de la variable con el operador ! (léase NOT) esto cambia: si la variable vale false, la condición es cierta, y si vale true, es falsa. Aprender a jugar con soltura con condiciones basadas en variables booleanas puede ser un poco cuesta arriba al principio, pero si te paras a pensarlo, siempre estás evaluando un resultado booleano: si se cumple la condición, el resultado es true. En caso contrario, es false.

CONDICIONES MÚLTIPLES

Suponte el siguiente ejemplo:

if (continente = "EU") {
    Serial.println ("Europa");
} else if (continente = "AM") {
    Serial.println ("América");
} else if (continente = "AS") {
    Serial.println ("Asian");
} else if (continente = "AF") {
    Serial.println ("África");
} else {
    Serial.println ("Oceanía");
}

Esta estructura funciona, y hace lo que se espera de ella. Según el código de continente que recibe, muestra el respectivo nombre. Sin embargo, estos condicionales, con varias evaluadciones encadenadas, se pueden poner en otra sintaxis, así;

switch (continente) {
    case "EU":
        Serial.println ("Europa");
        break;
    case "AM":
        Serial.println ("América");
        break;
    case "AS":
        Serial.println ("Asia");
        break;
    case "AF":
        Serial.println ("África");
        break;
    default:
        Serial.println ("Oceanía");
}

Lo que hacemos es crear una estructura de conmutador (switch), que recibe el nombre de la variable que vamos a evaluar. A continuación comprobamos cada posible valor de la variable con la sentencia case, seguida del valor que queremos comprobar, y el signo : (dos puntos). Debajo ponemos las sentencias que queremos que se ejecuten si de da el caso (case) de que la variable en evaluación tiene el valor especificado.

Observa que cada evaluación la finalizamos con la sentencia break; (romper), que indica que no se siga evaluando nada. Si no lo hiciéramos, una vez encontrada la condición cierta, se ejecutarían todas las sentencias. Por ejemplo, si continente vale AS y no usamos break;, en la consola veremos

Asia
África
Oceanía

lo que, desde luego, no es lo que pretendemos

Especialmente interesante es el último caso (default, opción por defecto). Si se han evaluado las condiciones anteriores y ninguna se ha cumplido, esta se cumple. Es decir, si nuestra variable continente tiene cualquier valor que no sea "EU", "AM", "AS" ni "AF", en la consola veremos Oceanía.

BUCLES

En ocasiones es necesario que un grupo de una o más sentencias se ejecuten de forma reiterada, un número de veces, o mientras se cumpla una condición. Veamos como hacer esto.

UN CIERTO NÚMERO DE VECES

Existen bucles que se ejecutan un número determinado de veces. Para saber el número de veces que se deben ejecutar, así como las que ya se han ejecutado, se emplea lo que se llama una variable de control. Observa el siguiente fragmento de ejemplo:

for (i = 0; i <= 10; i ++) {
    Serial.println (i);
}

En primer lugar tenemos la definición del bucle. Se compone de tres términos, separados por punto y coma, basados en la variable de control que, en nuestro ejemplo, hemos llamado i. En el primer término, le decimos que, inicialmente, vale 0. En el segundo término, le decimos que el bucle se estará repitiendo mientras i tenga un valor menor o igual que 10 (operador <=). Por último, en el tercer término le decimos que, tras la repetición del bucle, se incremente i en una unidad. Esto nos daría en la consola la siguiente salida:

0
1
2
3
4
5
6
7
8
9
10

Podemos cambiar la forma en que se repite el bucle. Imagina que solo quieres ver los números pares:

for (i = 0; i <= 10; i += 2) {
    Serial.println (i);
}

Observa el tercer término de la definición del bucle. Le decimos que, tras cada repetición, se debe incrementar la variable de control en dos unidades. La salida en consola será:

0
2
4
6
8
10

SIN NÚMERO DE VECES

Podemos hacer que un bucle se repita no un número determinado de veces, sino mientras se cumpla una condición. Imagina el siguiente fragmento:

while (temperatura < 30) {
    digitalWrite (pinLedVerde, HIGH);
    digitalWrite (pinLedRojo, LOW);
    temperatura = analogRead (pinTemperatura);
}
digitalWrite (pinLedVerde, LOW);
digitalWrite (pinLedRojo, HIGH);

Este bucle se estará repitiendo mientras la condición que se evalúa en while() resulte ser cierta. Si la variable temperatura es menor que 30, se enciende el led verde, se apaga el rojo, y se vuelve a leer la temperatura. En el momento que alcance o supere 30, se termina el bucle y se continúa por debajo: se apaga el led verde y se enciende el rojo.

Como la condición se define al principio del bucle, si, al llegar a la instrucción while la variable temperatura ya tuviera un valor igual o mayor que 30, el bucle no se ejecutaría ni una sola vez, pasando la ejecución directamente a las sentencias que hay después (las que encienden el led rojo y apagan el verde).

En ocasiones, esto no es lo que queremos. Hay veces que, por razones de diseño del sketch, necesitamos que un bucle se ejecute, al menos, una vez. Para ello lo que hacemos es evaluar la condición al final del bucle, en lugar de al principio, mediante la estructura do...while, así;

do{
    digitalWrite (pinLedVerde, HIGH);
    digitalWrite (pinLedRojo, LOW);
    temperatura = analogRead (pinTemperatura);
} while (temperatura < 30);
digitalWrite (pinLedVerde, LOW);
digitalWrite (pinLedRojo, HIGH);

ROMPER O SEGUIR

Hay ocasiones en que no nos vale que un bucle que se ejecute completamente. Hay veces que, durante el ciclo de ejecución, hay que volver a empezar, con el siguiente valor de la variable de control, antes de ejecutar todo el bucle. Volviendo al ejemplo anterior, supongamos que queremos imprimir los números impares del 1 al 10. Podemos hacerlo así:

for (i = 1; i <= 10; i ++) {
    if (i % 2 == 0) {
        continue;
    }
    Serial.println (i);
}

Lo que hacemos en el bucle es comprobar si el valor de la variable de control es par (es decir, si el resto de dividirla entre dos da 0). Si se cumple esta condición nos encontramos con la sentencia continue. Lo que hace es incrementar la variable de control según el tercer término del bucle, como si la ejecución hubiera llegado hasta el final, y vuelve a empezar con el siguiente valor de la variable de control. Es decir, comienza la siguiente iteración del bucle, omitiendo todas las sentencias que estén por debajo de continue, dentro del propio bucle.

Ahora supongamos un bucle que tiene que contar, digamos, de 1 a 10000 pero, si se pulsa un botón, debe interrumpir la cuenta. Observa el siguiente ejemplo:

for (i = 1; i <= 10000; i ++) {
    if (digitalRead (pinBoton) == HIGH) {
        break;
    }
    Serial.println (i);
}

LOS OPERADORES DE COMPARACIÓN

A la hora de comparar si una variable contiene cierto valor, o no lo contiene, o es mayor, etc, en Arduino contamos con los siguientes operadores:

OPERADOR LECTURA SIGNIFICADO
== Igual a (no confundir con el operador de asignación =) Se cumple si la expresión situada a la izquierda tiene el mismo valor que la situada a la derecha.
!= Distinto de (no igual a) Se cumple si la expresión situada a la izquierda NO tiene el mismo valor que la situada a la derecha.
! NOT (se usa en evaluación de variables booleanas) Se cumple la la variable booleana es false.
> Mayor que Se cumple si la expresión situada a la izquierda tiene un valor mayor que la situada a la derecha.
< Menor que Se cumple si la expresión situada a la izquierda tiene un valor menor que la situada a la derecha.
>= Mayor o igual que Se cumple si la expresión situada a la izquierda tiene un valor mayor o igual que la situada a la derecha.
<= Menor o igual que Se cumple si la expresión situada a la izquierda tiene un valor menor o igual que la situada a la derecha.

Además, en caso de evaluar simultáneamente dos o más condiciones, estas se pueden unir por los siguientes operadores:

OPERADOR SIGNIFICADO
&& (léase AND) Cuando unimos dos o más condiciones con este operador, todas y cada una de ellas deben ser ciertas para que el condicional se cumpla.
|| (léase OR) Cuando unimos dos o más condiciones con este operador, basta con que una de ellas sea cierta para que el condicional se cumpla. También se cumple si dos o más son ciertas. Sólo no se cumplirá si ninguna es cierta.

Las condiciones se pueden agrupar mediante el uso de paréntesis. Observa la siguiente evaluación:

if ((a == 23 && estado == "CP") || caracter == 'X') {

Este condicional se cumplirá si la variable a vale 23 y estado vale "CP", o bien si caracter contiene 'X'. Si a vale algo que no sea 23, la primera parte no se cumplirá, pero, si caracter contiene 'X', el condicional sí se cumple. Las condiciones se evalúan empezando por las que están entre paréntesis, de más interior a más exterior.

 

     

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *