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.