TEMA 2: Control de Flujo de programa
2.0 Introducción
En tema anterior aprendimos a trabajar con variables, leer
sus valores por teclado, visualizarlas en pantalla y realizar
operaciones elementales con ellas.
Los programas que escribimos hasta ahora se ejecutaban
secuencialmente, es decir, instrucción tras instrucción, sin
posibilidad de volver a ejecutar una instrucción ya ejecutada o
evitar la ejecución de un grupo de instrucciones si se dan unas
características determinadas.
En este tercer tema se describirán las instrucciones que nos
permiten escribir programas que no se ejecuten de una forma
secuencial en el sentido explicado en el párrafo anterior.
2.1 Expresiones condicionales.
En ciertas ocasiones nos puede interesar que un programa
llegado a un punto de su ejecución vuelva hacia atrás y se ejecute
de nuevo, pero lo que en general nos interesará será que este
regreso a una línea de terminada de nuestro código se realice si se
cumple una cierta condición. Por esta razón es necesario explicar,
antes de comenzar con las instrucciones propiamente dichas de
control de flujo de programa, como le indicamos al ordenador que
deseamos evaluar una condición.
Las expresiones que nos permiten realizar ésto reciben el
nombre de expresiones condicionales o booleanas. Estas expresiones
sólo pueden tomar dos valores: VERDADERO (TRUE) o FALSO (FALSE). En
general un valor de 0 indica que la expresión es falsa y un valor
distinto de 0 indica que la expresión es verdadera.
Como hemos indicado se trata de expresiones condicionales, y
análogamente a las expresiones aritméticas podemos comparar
variables entre sí, constantes entre sí (lo cual no es muy útil
puesto que si conocemos los dos valores ya sabemos la relación que
existe entre ambas constantes) y por supuesto variables y
constantes. Además podemos agrupar condiciones entre sí formando
expresiones más complejas y ayudarnos de los paréntesis para indicar
el orden de evaluación. Los operadores condicionales son:
== Representa igualdad.
!= Representa desigualdad
> Mayor que.
< Menor que.
>= Mayor o igual que.
<= Menor o igual que.
Podemos encadenar distintas expresiones condicionales, las
cuales deben de ir entre paréntesis (comparamos de dos en dos)
utilizando los operadores:
&& Y lógico.
|| O lógico.
Veamos un ejemplo de expresión condicional
(a==2)||((b>=0)&&(b<=20))
la expresión será cierta si la variable a es igual a dos o
si la variable b tiene un valor comprendido entre 0 y 20.
2.1.1 La instrucción if... else.
En inglés if significa si condicional, por ejemplo, si
llueve me llevaré el paraguas, else significa sino, sino llueve me
iré a la playa. Este es el significado que poseen en programación.
Su sintaxis es:
if (condición) instrucción;else instrucción;
NOTA: La sintaxis real del IF es la siguiente: if (condición)
bloque else bloque.
Un programa ejemplo nos indicará su funcionamiento con
claridad. Supongamos que deseamos dividir dos números. El número por
el que dividimos no puede ser cero, ésto nos daría un valor de
infinito, provocando un error en el ordenador. Por tanto antes de
dividir deberíamos de comprobar si el divisor es cero. El programa
sería algo como ésto:
#include <stdio.h>
main()
{
float dividendo,divisor;
printf ("\nDime el dividendo:");
scanf ("%f",÷ndo);
printf ("\nDime el divisor:");
scanf ("%f",&divisor);
if (divisor==0)
printf ("\nNo podemos dividir un número por 0");
else
printf ("\nEl resultado es: %f",dividendo/divisor);
}
Como en todas los comandos del lenguaje C una instrucción,
en general, puede ser solamente una o un conjunto de ellas incluidas
entre llaves.
Por último el lenguaje C dispone de un operador ternario (de
tres elementos) que permite construir determinadas estructuras
if-else, en concreto toma un valor u otro dependiendo de una
expresión condicional. Su sintaxis es:
exp1 ? exp2 : exp3
Si exp1 es cierta la expresión tomará el valor exp2, sino
tomará el valor exp3. Un ejemplo de su utilización:
/* La variable z toma el valor máximo entre a y b */
z = ( (a>b) ? a : b);
Como se puede observar se trata de una secuencia if else
pero muy concreta, probablemente el compilador generará un código
mucho más eficiente para este tipo de secuencia de ahí su inclusión
en el juego de operadores del C.
A continuación se describirán las instrucciones que nos
permiten controlar el flujo de programa, en las cuales tendremos que
utilizar expresiones condicionales continuamente, por lo cual no
insistiremos más en este tema.
2.2 Control del flujo de programa
2.2.0 Introducción
A estas alturas el lector ya debería conocer lo que es el
flujo de programa. El flujo de programa es la secuencia de
instrucciones que un programa ejecuta desde su comienzo hasta que
finaliza. En principio la ejecución es secuencial, comienza con la
primera instrucción y termina con la última. Sin embargo es común
que nos interese que nuestro programa no termine con la última de
las instrucciones (si por ejemplo no podemos abrir un fichero y la
función del programa es modificar ese fichero, el programa no
debería realizar ninguna operación y terminar al detectar el error),
o puede que nos interese que un grupo de instrucciones se ejecute
repetidamente hasta que le indiquemos que pare. Todo esto se puede
conseguir con las instrucciones que se describirán a continuación.
2.2.1 Creación de bucles de ejecución.
2.2.1.0 Concepto de bucle
En la introducción ya se ha mencionado lo que es un bucle.
Una secuencia de instrucciones que se repite un número determinado
de veces o hasta que se cumplan unas determinadas condiciones.
Los bucles son extremadamente útiles en nuestros programas,
algunos ejemplos son:
* Lectura/Visualización de un número determinado de datos,
como por ejemplo una matriz.
* A veces se hace necesario introducir esperas en nuestros
programas ya sea por trabajar con un periférico lento o
simplemente por ralentizar su ejecución. Los primeros se
llaman bucles de espera activa y los segundo bucles
vacíos.
* En aplicaciones gráficas como trazado de líneas o
rellenado de polígonos.
* Lectura de datos de un fichero...
A continuación describiremos las opciones que nos
proporciona el lenguaje de programación C para crear y gestionar los
bucles.
2.2.1.1 Bucle for
La primera opción de que disponemos es el bucle for. Este
tipo de instrucción se halla presente en la mayoría de los lenguajes
de programación estructurados, y permite repetir una instrucción o
conjunto de instrucciones un número determinado de veces. Su
sintaxis es como sigue:
for (exp1;exp2;exp3) instrucción;
exp1 es una expresión que sólo se ejecuta una vez al
principio del bucle. El bucle for suele utilizarse en combinación
con un contador. Un contador es una variable que lleva la cuenta de
las veces que se han ejecutado las instrucciones sobre las que actúa
el comando for. Por tanto exp1 suele contener una expresión que nos
permite inicializar ese contador generalmente a 0 aunque eso depende
de para qué deseemos utilizar el bucle.
exp2 es la expresión que nos indica cuando debe finalizar el
bucle, por tanto se tratará de una expresión condicional. Su
interpretación sería algo como; repite la instrucción (o
instrucciones) mientras se cumpla exp2. Esta expresión se evaluará
en cada ciclo del bucle para determinar si se debe realizar una
nueva iteración.
NOTA: Hay que recordar que exp2 se evalúa al principio del
bucle, y no al final. Por tanto es posible no ejecutar el bucle
NINGUNA vez.
exp3 es una expresión que se ejecuta en cada iteración.
Puesto que como ya indicamos el bucle for se utiliza junto a un
contador, exp3 en general contiene una instrucción que actualiza
nuestro contador.
Por tanto en un bucle con contador distinguimos tres partes
diferenciadas:
* La inicialización del contador (exp1).
* La condición de fin de bucle (exp2).
* Y la actualización del contador (exp3).
El bucle for esta especialmente pensado para realizar bucles
basados en contadores. Se puede utilizar en bucle del tipo "repite
esto hasta que se pulse una tecla", pero para estos tenemos
instrucciones más apropiadas. Veamos unos ejemplos que nos permitan
comprender más fácilmente el funcionamiento del comando for.
Ejemplo 1: Contar hasta diez.
#include <stdio.h>
main()
{
int i; /* Esta variable la utilizaremos como contador*/
for (i=0;i<10;i++) printf ("\n%d",i);
}
Este programa mostrará en pantalla numeros de 0 a 9 (diez en
total). exp1 inicializa nuestro contador que en este caso es una
variable de tipo entero, con el valor 0, exp2 nos dice que nuestra
instrucción (la función printf) debe repetirse mientras el contador
sea menor que diez y finalmente exp3 indica que el contador debe de
incrementarse en una unidad en cada ciclo del bucle.
Nos podría interesar contar desde diez hasta 1, en este caso
el comando for debería de ser:
for (i=10;i>0;i--) printf ("\n%d",i);
Ejemplo 2: Visualizar dos tablas de multiplicar en pantalla.
#include <stdio.h>
main()
{
int i;
int tabla1,tabla2;
tabla1 = 2; /* Primero la tabla del dos */
tabla2 = 5; /* y a continuación la tabla del cinco*/
for (i=1;i<11;i++)
{
printf ("\n %2dx%2d=%3d",tabla1,i,tabla1*i);
printf (" %2dx%2d=%3d",tabla2,i,tabla2*i);
}
}
El ejemplo es análogo al anterior, pero en este caso
visualizamos valores desde uno a diez, en lugar de visualizarlos de
0 a 9. En este ejemplo, el bucle actúa sobre un conjunto de
instrucciones, no sobre una sola, por tanto debemos introducirlas
entre las llaves para indicar al compilador que la instrucción for
actúa sobre las dos instrucciones. Estamos considerando todo lo que
se encuentre entre las llaves como una sola instrucción.
Para terminar con los bucles de tipo for un leve comentario
sobre los bucles añadados, simplemente lo que se hace es incluir un
bucle dentro de otro. Supongamos que deseamos introducir los datos
de nuestro jugadores preferidos por teclado para almacenarlos en el
ordenador, y que de cada jugador queremos almacenar por ejemplo, su
nacionalidad, su peso y su altura. En este caso nos sería útil un
bucle anidado. El bucle exterior nos contaría jugadores, mientras
que para cada jugador tendríamos otro bucle que nos leyera los tres
datos que necesitamos. Si tenemos veinte jugadores preferidos,
incluiríamos una unas instrucciones como estas:
for (i=0;i<20;i++)
{
printf ("Jugador preferido %d",i);
for (j=0;j<3;j++)
{
leo característica j;
la almaceno donde sea;
}
}
Nada más en lo que a bucles de tipo for respecta. A
continuación veremos las otras estructuras que nos proporciona el
lenguaje C para la realización de bucles.
2.2.1.2 Bucles while.
La sintaxis de este bucle será algo así:
while (exp2) instrucción;
En inglés while significa mientras, por tanto la línea
anterior significaría mientras de cumpla exp2 ejecuta la
instrucción. Obviamente la instrucción que ejecutemos debe de
permitir que en algún caso se cumpla exp2, de lo contrarío el
ordenador permanecería eternamente ejecutando instrucción. También
es evidente que exp2 debe ser una expresión condicional. Como vemos
este tipo de bucles no está orientado a contadores, es mucho más
genérico, sin embargo se puede utilizar de forma análoga a for. Con
la nomenclatura utilizada anteriormente tendríamos algo como ésto:
exp1;
while (exp2)
{
instrucción;
exp3;
}
Con este esquema se hace patente la utilidad de la
instrucción for para bucles con contador puesto que se "centraliza"
todo el proceso de gestión del contador (inicialización y
actualización) en una sola instrucción. Un error común al escribir
un bucle con contador con una estructura while es olvidar introducir
exp3, con lo cual nunca se cumple exp2 y nunca salimos del bucle. De
nuevo un bucle infinito aunque a veces nos interesa tener un bucle
infinito. La forma más sencilla de realizar un bucle infinito es con
la expresión:
while (1) instrucción;
Como indicamos exp2 es una expresión condicional y para
estas expresiones un valor distinto de 0 es verdadero por tanto un 1
es siempre cierto y no hay manera de salir del bucle puesto que es
una constante y ninguna modificación de variables por parte de
instrucción tendría repercusiones sobre ella.
Los bucle while son útiles en aplicaciones como; lee datos
de este fichero mientras no llegues al final ó muestra estos datos
en la pantalla mientras no pulse una tecla.
Una peculiaridad de esta instrucción es que puede no
ejecutarse nunca la instrucción afectada por el while. Algunos
ejemplos:
Ejemplo 1: Contar hasta diez.
#include <stdio.h>
main()
{
int i;
i = 0;
while (i<10)
{
printf ("\n%d",i);
i++;
}
}
El primer valor que visualizaremos será el 0. Cuando i tenga
el valor nueve la condición i<10 todavía se cumple por lo que
entraremos en el bucle de nuevo, visualizaremos el nueve e
incrementamos i con lo que pasa a tener el valor 10 momento en el
cual se vuelve a evaluar la expresión i<10 que en este caso sería
falsa y no volveríamos a entrar en el bloque de instrucciones (las
que están entre llaves). Así visualizamos nueve número de 0 a 9 como
antes. Si incluyésemos una instrucción para visualizar el valor de i
antes de abandonar el programa (justo antes de las últimas llaves el
valor que se mostraría sería 10.
Ejemplo 2: Lee números enteros hasta que se introduzca el
valor hasta que se introduzca el valor 0.
#include <stdio.h>
main()
{
int numero;
numero = 10;
while (numero!=0)
{
printf ("\nDime un número:");
scanf ("%d",&numero);
}
}
En este ejemplo tenemos que introducir en la variable número
un valor distinto de cero antes de entrar en el bucle, puesto que en
principio al declarar una variable el valor de ésta no está
determinado y podría valer cero, en cuyo caso nunca se ejecutaría el
bucle. Esta es la misión de la instrucción numero = 10;.
2.2.1.3 Bucle do.. while
Su funcionamiento es análogo al anterior, con la única
salvedad de que la condición ahora se evalúa después de ejecutar la
instrucción su sintaxis sería:
do instrucción while (exp2);
Si en el ejemplo anterior utilizamos esta estructura no
sería necesario actualizar numero con un valor distinto de cero,
puesto que antes de comprobar si es cero leeríamos el valor.
#include <stdio.h>
main()
{
int numero;
do
{
printf ("\nDime un numero :");
scanf ("%d",&numero);
} while (numero !=0);
La diferencia fundamental con la instrucción anterior es que
esta estructura ejecuta la instrucción sobre la que actúa al menos
una vez.
2.2.1.4 Modificadores del flujo de programa.
Puede que en ciertas ocasiones no nos interese que si se da
alguna condición sólo se ejecute un conjunto de todas las
instrucciones sobre las que actúa el bucle o simplemente salir del
bucle antes de llegar a la condición que hayamos indicado en el
comando for o en while. Esto es posible mediante dos modificadores:
continue y break.
El primero de ellos, continue, permite volver a reevaluar la
condición de salida, es decir, después de ejecutar continue la
siguiente instrucción que se ejecutará será el for o el while.
Veamos un ejemplo de aplicación para comprender mejor como funciona
este comando.
#include <stdio.h>
main()
{
int numero;
int contador;
contador =0;
do
{
printf ("\nIntroduce el número %2d:",contador);
scanf ("%d",&numero);
if ((numero<0)||(numero>20)) continue;
contador++;
} while (contador<50);
}
Este programa lee números en una variable hasta un máximo de
50, alcanzado este máximo el programa termina. Además si el número
no está entre 0 y 20 (si es menor que 0 o mayor que 20) vuelve a
pedir que lo introduzcamos. El comando continue en la instrucción if
obliga al programa a saltar a la instrucción while donde se vuelve a
evaluar la condición, sin pasar por la línea en la que se incrementa
el contador. De esta forma se nos vuelve a pedir el mismo número y
la entrada incorrecta no es tenida en cuenta.
La función de break es ligeramente distinta no salta a la
instrucción en la que se evalúa la condición sino que simplemente
abandona el bucle y continúa la ejecución en la línea inmediatamente
siguiente al bucle.
#include <stdio.h>
main()
{
int i;
for (i=0;i<20;i++)
{
if (i==5) break;
printf ("\n%d",i);
}
printf ("\n\n%d",i);
}
La salida de este programa sería algo como esto:
0
1
2
3
4
5
Y con esto terminamos todo lo relacionado con los bucles en
lenguaje C. Los bucles son una estructura básica y es necesario
utilizarla en la inmensa mayoría de los programas.
2.2.2 Menús de Opciones.
2.2.2.1 Introducción
La mayoría de los programas permiten realizar una serie de
operaciones sobre datos. Lo ideal sería poder indicarle al ordenador
que operación deseamos realizar sobre estos datos en lenguaje
natural. Por ejemplo, para una base de datos nos podría interesar
decirle al ordenador: "Buscame todas la fichas de libros que traten
sobre informática" ó "Borra de la base de datos el libro tal".
Existen en la actualidad herramientas de este tipo, pero aún distan
bastante del lenguaje natural, por ello una de las formas más
sencillas de indicar al ordenador la operación que deseamos realizar
es utilizar un menú de opciones.
La otra solución comúnmente adoptada es una línea de
comandos, es decir, escribimos una frase en un lenguaje muy reducido
indicando al ordenador lo que deseamos hacer (de una forma similar a
como lo hacemos en MS-DOS). Esta solución tiene la desventaja de
tener que aprender complicados comandos y distintas secuencias para
distintos programas.
Un menú nos muestra en pantalla todas las opciones que
podemos realizar con nuestro programa de forma que no es necesario
que el usuario conozca ninguna serie de ordenes complejas,
simplificando por tanto el uso de los programas por parte de los
usuarios.
2.2.2.2 Sentencia switch-case-default
La mayoría de los lenguajes de alto nivel disponen de alguna
instrucción que permite gestionar el valor de una variable de una
forma estructurada y sencilla, permitiendo por tanto la creación de
menús de programa de una forma sencilla. En lenguaje C esta
instrucción es switch-case-default. Veamos un ejemplo de como
funciona mediante un pequeño programita.
#include <stdio.h>
main()
{
int opcion;
printf ("\nEjemplo de Menú de Programa");
printf ("\n1.-Cargar fichero de datos");
printf ("\n2.-Almacenar fichero de datos");
printf ("\n3.-Modificar datos");
printf ("\n4.-Salir");
printf ("\n\nDime tu opción :");scanf ("%d",&opcion);
switch (opcion)
{
case 1:
/* Código para cargar fichero de datos*/
break;
case 2:
/* Código para almacenar datos */
break;
case 3:
/* Código para modificar datos */
break;
case 4:
/* Salir del programa */
exit (0);
default :
printf ("\nSu opción no está disponible");
printf ("\nInténtelo con otra");
}
Del ejemplo se deduce fácilmente el funcionamiento de esta
secuencia. El comando switch (var) realizará una bifurcación o salto
a la línea indicada por la variable var. Si var vale 2, el programa
se seguirá ejecutando a partir de la línea marcada con case 2. Todos
los separadores case están separador por un comando break, ya que de
no ser así la ejecución seguiría lineal hasta encontrar la llave que
termina el comando switch. La palabra clave default indica el código
que se debería ejecutar si el valor de la variable especificada en
el switch no corresponde con ninguno de los indicados por los case
dentro del switch. Así en nuestro ejemplo si opcion tiene un valor
distinto de 1, 2, 3 ó 4, se mostrará en pantalla un mensaje
indicando que la opción indicada no es correcta. La sintaxis de esta
estructura sería:
switch (variable)
{
case valor1-variable:
código asociado;
case valor2-variable:
código asociado;
.
.
case valorN-variable:
código asociado;
default:
código asociado;
}
Dentro del código asociado a cada opción se deberán incluir
las instrucciones break adecuadas. Ya se explicó el funcionamiento
de break y continue cuando se habló de bucles. Su funcionamiento es
análogo para los comandos for, while, do-while, y switch.
Un fragmento de código para la imprementación de un menú
completo sería:
while (Opcion!=0)
{
/* Secuencia de printfs que muestran en pantalla
el menú. En este caso la opción 0 debería ser salir */
switch (opcion)
{
/* Secuencia de cases */
default :
/* Mostrar mensaje de error */
}
}
Por su puesto las aplicaciones del comando switch van mucho
más allá de la simple creación de menús. Puede ser utilizada en
cualquier aplicación en la que se necesite realizar distintas
operaciones dependiendo de un valor. Un ejemplo sencillo podría ser
un un programa que imprimiese textos en impresora. Podríamos marcar
en el texto mediante una secuencia especial como debería ser impreso
el texto a continuación. Por ejemplo:
@N - Activa Negrita.
@n - Desactiva Negrita.
@C,@c - Activa/desactiva cursiva.
@S,@s - idem sibrayado
etc...
Leeríamos estos valores provenientes del teclado o de un
fichero y con algo de procesamiento y una instrucción switch con
dicha variable, en cada case enviaríamos a la impresora la secuencia
adecuada para realizar cada una de la opciones.