Pages - Menu

jueves, 2 de mayo de 2013

Curso básico de C 4/10: Control de flujo, Estructuras condicionales

Hasta ahora todo lo que hemos ido viendo durante éste curso básico de C ha ido encaminado a realizar programas lineales, con un proceso de ejecución inalterable donde se realizan todas y cada una de las instrucciones sin necesidad de modificar nada. En este capítulo vamos a ver las estructuras de control de flujo disponibles en C, con las que podremos alterar el curso de la ejecución según las necesidades que tengamos.


Foto tomada de freedigitalphotos.net

Como ya hemos definido en anteriores artículos, existen dos tipos de estructuras de control de flujo, las selectivas y las iterativas, gracias a éstas podremos modificar el flujo de ejecución, haciendo que se ejecuten unas instrucciones u otras (selectivas) o ejecutar instrucciones un número repetido de veces (iterativas).

Estructuras de control de flujo selectivas.

1 – if.

La estructura de selección fundamental es el if, gracias a ésta podremos evaluar una expresión con el fin de que si el resultado de esa evaluación es un booleano TRUE la sentencia o sentencias incluídas en él se ejecutarán, en caso contrario el flujo de ejecución no realizará ninguna de esas instrucciones y seguirá su curso.

Un if tiene la siguiente estructura:

if (expresión a evaluar) {
sentencias;
}

Es importante que respetemos la sintaxis en todo momento, los paréntesis son esenciales para que el programa sepa que sentencia evaluar.

En caso de que dentro del if sólo haya una sentencia, las llaves podemos omitirlas.

if (expresión a evaluar)
            sentencia;

Pero cuidado, en caso de que el if contenga varias sentencias debemos obligatoriamente encerrarlas en las llaves, si no lo hacemos sólo se ejecutará la primera.

No es lo mismo esto:

if (expresión a evaluar) {
sentencia1;
sentencia2;
sentencia3;
sentencia4;
}

Que esto:

if (expresión a evaluar)
sentencia1;
sentencia2;
sentencia3;
sentencia4;

Como expresión a evaluar podemos utilizar variables, funciones, operadores de comparación, booleanos o concatenar varias expresiones. No se permite la asignación de valores.

if (a > b) {
            printf(“a es mayor que b\n”);
}

if (a + b == c) {
            printf(“a + b es igual a c\n”);
}

if (a + b == c && a > b) {
            printf(“a + b es igual a c y además a es mayor que b\n”);
}

if (a + b == c || a > b) {
            printf(“o bien a + b es igual a c o bien a es mayor que b\n”);
}

Pruebe a realizar éste código en el IDE:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int a,b,c;
     
    a=2;
    b=0;
    c=3; 
     
    if (a > b) {
            printf("a es mayor que b\n");
    }
   
    if (a + b == c) {
            printf("a + b es igual a c\n");
    }
   
    if (a + b == c && a > b) {
            printf("a + b es igual a c y además a es mayor que b\n");
    }
   
    if (a + b == c || a > b) {
            printf("o bien a + b es igual a c o bien a es mayor que b\n");
    }
 
    system("PAUSE");
    return 0;
}

2 – if...else

Hay momentos en los que necesitaremos que nuestro programa ejecute unas acciones u otras dependiendo de la valoración de la expresión en el if, para eso tenemos a nuestra disposición la palabra reservada else, con else podemos determinar qué código se va a ejecutar en caso de que la valoración de la expresión del if sea FALSE.

El else no puede ser utilizado sin un if, tampoco puede utilizarse junto a una expresión para valorar (éste caso lo veremos más adelante). La estructura del else es la siguiente:

if (expresión a evaluar) {
sentencias;
}
else {
            sentencias;
}

Así si la evaluación de la expresión da como resultado FALSE el programa procederá a ejecutar el código que haya dentro del else.

La regla de las llaves del if también se aplican al else.

No es lo mismo:

if (expresión a evaluar) {
sentencia1;
}
else {  
sentencia2;
sentencia3;
sentencia4;
}

Que esto:

if (expresión a evaluar) {
sentencia1;
}
else     
sentencia2;
sentencia3;
sentencia4;

La eliminación de las llaves, insisto, es completamente opcional, se puede aplicar como más cómodo nos encontremos, pudiendo hacer todas estas opciones:

if (expresión a evaluar) {
sentencia1;
}
else {  
sentencia2;
}

if (expresión a evaluar)
sentencia1;
else {  
sentencia2;
}

if (expresión a evaluar) {
sentencia1;
}
else     
sentencia2;

if (expresión a evaluar)
sentencia1;
else
sentencia2;

Pruebe a realizar éste código en el IDE:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int a,b,c;
     
    a=2;
    b=0;
    c=3; 
     
    if (a > b)
            printf("a es mayor que b\n");
    else
    {
        printf("a no es mayor que b\n");
    }  
   
    if (a + b == c) {
            printf("a + b es igual a c\n");
    }
    else
        printf("a + b no es igual a c\n");
   
    if (a + b == c && a > b)
            printf("a + b es igual a c y además a es mayor que b\n");
    else
        printf("a + b no es igual a c y además a es mayor que b\n");
   
    if (a + b == c || a > b) {
            printf("o bien a + b es igual a c o bien a es mayor que b\n");
    }
    else
    {
        printf("ni a + b es igual a c ni a es mayor que b\n");
    }
 
    system("PAUSE");
    return 0;
}

¿Sencillo, verdad? Con el uso del if tenemos muchas más posibilidades de cara a controlar el flujo del programa, con else además podremos controlar las evaluaciones incorrectas, las posibilidades para ir realizando cada vez mejores programas van creciendo.

3 – if anidado.

En el programa anterior teníamos el siguiente código:

if (a + b == c && a > b)
printf("a + b es igual a c y además a es mayor que b\n");
else
printf("a + b no es igual a c y además a es mayor que b\n");

Si lo compilábamos y ejecutábamos la salida era correcta debido a que la variable a tiene el valor 2 y la variable b tiene el valor 0, a + b nunca puede ser 3, y si además vemos la segunda expresión tenemos que, efectivamente, a es mayor que b, por lo tanto el mensaje del else "a + b no es igual a c y además a es mayor que b" es correcto. ¿Pero que pasaría si le diésemos a la b el valor 4? Ya no sería correcto y nuestro programa funcionaría mal.

Para ello es posible utilizar estructuras if dentro de otras estructuras if, a esto se le conoce como if anidado. La estructura de un if anidado sería la siguiente:

if (expresión a evaluar) {
if (expresión a evaluar) {
sentencias;
}
}
else {
sentencias;
}

Cabe resaltar que las estructuras if podemos anidarlas cuantas veces sean necesarias, dentro del bloque if o dentro del bloque else, no existen límites.

Ahora veamos la estructura del if anidado pero suprimiendo las llaves del primer if.

if (expresión a evaluar)
if (expresión a evaluar) {
sentencias;
}
else {
sentencias;
}

¿Es ésta estructura igual que la anterior? En teoría tiene sentido ¿no? Tenemos un if que lo único que va a hacer es ejecutar dentro otra sentencia if, tenemos un solo elemento por lo que no es necesario poner llaves, además el aspecto es similar, el else se ejecutará si el primer if resulta en FALSE. Además de todo esto el else sigue perteneciendo al primer if, ¿correcto? Pues no.

C trabaja con una regla muy sencilla, el else pertenece al último if que no tenga else. Es decir, que si indentamos correctamente la estructura anterior quedaría así:

if (expresión a evaluar)
if (expresión a evaluar) {
sentencias;
}
else {
sentencias;
}

Hemos perdido completamente la estructura que teníamos definida debido a la necesidad de ahorrarnos unas llaves, pues precisamente esto será una de las cosas que C tendrá en cuenta a la hora de decidir qué else pertenece a qué if.

if (expresión a evaluar) {
if (expresión a evaluar) {
sentencias;
}
}
else {
sentencias;
}

Ahora vuelve a estar todo como antes, si el primer if resulta positivo comenzará a evaluar el segundo if, y si éste sale positivo ejecutará las sentencias pertinentes, en caso contrario no hará absolutamente nada, el programa se saldrá del primer if y continuará con el código siguiente, el primer else no se tendrá en cuenta porque la evaluación del primer if ha sido TRUE, no le afecta en nada que la evaluación del segundo if sea FALSE.

Al igual que anidamos if podemos anidar if...else:

if (expresión a evaluar) {
if (expresión a evaluar) {
sentencias;
}
else {
            sentencias;
}
}
else {
sentencias;
}

También es posible anidar if...else dentro de un else:

if (expresión a evaluar) {
if (expresión a evaluar) {
sentencias;
}
else {
            sentencias;
}
}
else {
if (expresión a evaluar) {
sentencias;
}
else {
            sentencias;
}
}

Podemos poner tantas estructuras como queramos y de la forma que queramos:

if (expresión a evaluar) {
if (expresión a evaluar) {
sentencias;
}
else {
            if (expresión a evaluar) {
sentencias;
}
else {
                       sentencias;
}
}
}
else {
if (expresión a evaluar) {
sentencias;
}
else {
            sentencias;
}

if (expresión a evaluar) {
sentencias;
}
else {
           
}

if (expresión a evaluar) {
sentencias;
}
else {
           
}
}

Ahora volvamos al trozo de código del apartado anterior, el que hemos visto que está mal:

if (a + b == c && a > b)
printf("a + b es igual a c y además a es mayor que b\n");
else
printf("a + b no es igual a c y además a es mayor que b\n");

Ahora con los if anidados podemos solucionar esto, quedando algo así:

if (a + b == c && a > b) {
printf("a + b es igual a c y además a es mayor que b\n");
}
else {
            if (a > b) {
printf("a + b no es igual a c y además a es mayor que b\n");
}
else {
printf("a + b no es igual a c y además a no es mayor que b\n");
}
}

Controlado el problema ¿verdad? Pues no, aún queda un caso que contemplar. Imaginemos que a vale 0 y b vale 3, la condición  a + b == c se cumple pero la otra no, por lo tanto iría al else y después de evaluar llegaría a la conclusión de que "a + b no es igual a c y además a no es mayor que b", esto es incorrecto, debemos arreglarlo también usando nuevos if y else.

Una posible solución para éste problema es la siguiente:

if (a + b == c && a > b) {
printf("a + b es igual a c y además a es mayor que b\n");
}
else {

            if (a + b == c) {
printf("a + b es igual a c");
}
else {
printf("a + b no es igual a c");
}

printf(" y además ");

            if (a > b) {
printf("a es mayor que b\n");
}
else {
printf("a no es mayor que b\n");
}
}

Combinando los posibles usos del printf y omitiendo los primeros \n para evitar saltos de línea ha quedado un código que parece bastante seguro y que controla todos los casos.

4 – else if.

Existen ocasiones en las que la estructura if...else se puede quedar corta para nuestras necesidades, por eso en C disponemos de otra forma de estructura, la que utiliza el else if.

Con else if se puede evaluar una nueva condición en caso de que la valoración del primer if sea FALSE, se ejecuta antes de else, dejando a éste como última opción a tener en cuenta.

La estructura sería la siguiente:

if (condición) {
            sentencias;
}
else if (condición) {
            sentencias;
}
else {
            sentencias;
}

El else if puede hacer uso de las reglas de llaves que ya hemos visto, se pueden obviar en caso de ser una única instrucción a ejecutar, no obstante también hay que tener en cuenta la regla de pertenencia del else que ya hemos citado antes (un else pertenece al último if abierto), también se aplica al else if. Además else if tiene un ventaja con respecto a los demás, se pueden poner tantos como se necesite.

Una posible forma de utilizar el else if para el ejemplo anterior sería la siguiente:

if (a + b == c && a > b) {
printf("a + b es igual a c y además a es mayor que b\n");
}
else if (a + b == c) {
printf("a + b es igual a c");
}
else if (a > b) {
printf("a es mayor que b\n");
}
else {
printf("a + b no es igual a c");
printf(" y además ");
printf("a no es mayor que b\n");
}

Con éste último código se termina de contemplar el caso ya visto dentro del ejercicio de inicio del post.

5 – Sentencia switch.

Existen ocasiones en las que necesitaremos ejecutar acciones en función del valor de una expresión o del valor de una variable concreta, esto hacerlo mediante if puede resultar bastante tedioso, se llenaría el código de if anidados y si cometemos algún error de indentación o de llaves puede resultar que nuestro código no funcione correctamente, para eso existe la sentencia switch.

La sentencia switch realiza esta acción perfectamente, ahorrando tiempo de codificación y obteniendo control sobre los resultados, su estructura es la siguiente:

switch (expresión) {
case valor1:
sentencias
break;
case valor2:
sentencias
break;
...
default:
 sentencias
 break;
 }

Un ejemplo bastante común de uso de las sentencias swtich es la creación de un menú de opciones, mediante la cual al elegir una opción se ejecutará una serie de instrucciones. Pruebe a ejecutar el siguiente código en su IDE:

#include <stdio.h>

int main(void)
{
int opcion;

printf ("Elija una opción: \n");
printf ("1) Inicio\n");
printf ("2) Salir\n");
scanf ("%d", &opcion);

switch (opcion) {
case 1:
printf ("Estas en el inicio\n");
break;
case 2:
printf ("Has salido del programa\n");
break;
default:
printf ("Opción no válida\n");
break;
}
return 0;
}

La sentencia break se utiliza para que el flujo de ejecución se salga de toda la sentencia switch y siga ejecutando las sentencias siguientes.

La opción default es completamente opcional y su uso sería similar al del else de la estructura if, es decir, cuando ninguna de las opciones contempladas se corresponde con la de la variable o expresión que se está valorando.

Conclusiones.

Las sentencias de control de flujo condicionales nos sirven para contemplar casos en los que necesitamos tener control sobre las variables y expresiones, evitar errores y tomar decisiones. En el ámbito de los videojuegos es esencial tener controlados todos los casos posibles ante la amplia libertad que le damos al jugador con el personaje protagonista.

Con esto terminamos el capítulo de estructuras de control de flujo condicionales, el próximo día veremos las estructuras de control de flujo iterativas o repetitivas, veremos cómo podemos hacer que una acción o acciones se ejecuten un número determinado de veces. Con eso tendremos los elementos necesarios para ir construyendo algún juego simple, el cual haremos también.

¿Qué otras formas de estructuras de control de flujo condicionales conocéis de otros lenguajes?