Ciclos H. Tejeda Marzo 2016 Índice 1. Estructura de ciclo 1 2. Creación de ciclos while 2 3. Operadores aritméticos abreviados 7 4. Creación de ciclos for 9 5. Creación de ciclos do...while 11 6. Ciclos anidados 13 7. Rendimiento de ciclos 15 1. Estructura de ciclo Con las decisiones se hace que un programa parezca inteligente, con ciclos se hace que el programa se vea poderoso. Un ciclo, o bucle, es una estructura que permite repetir la ejecución de un bloque de sentencias. Una expresión booleana es evaluada dentro de la estructura de ciclo. Si la expresión es true, un bloque de sentencias, cuerpo del ciclo, se ejecuta y la expresión booleana vuelve a ser evaluada otra vez. El cuerpo del siglo puede ser una sola sentencia o un bloque de sentencias entre llaves. Mientras la expresión booleana sea true, las sentencias en el cuerpo del ciclo se continúan ejecutando. Cuando la evaluación booleana es false, el ciclo termina. Una ejecución de cualquier ciclo se denomina iteración. En la figura 1 se muestra un diagrama de la lógica de un ciclo. Se pueden emplear varios tipos de ciclos en Java. Se revisarán los siguientes tres tipos de ciclos. Ciclo while, la expresión booleana que controla el ciclo es la primera sentencia en el ciclo. 1 Figura 1: Diagrama de flujo de una estructura de bucle. Ciclo for, es una formato compacto para ejecutar ciclos. Ciclo do...while, la expresión booleana que controla el ciclo es la última sentencia en el ciclo. 2. Creación de ciclos while Se puede usar un ciclo while para ejecutar un cuerpo de sentencias mientras una expresión booleana que controla la entrada en el ciclo continúa siendo true. Un ciclo while consiste de la palabra reservada while seguida de una expresión booleana dentro de paréntesis, seguida por el cuerpo del ciclo. Se usan un ciclo while cuando se necesita hacer una tarea un predeterminado número de veces o un número impredecible de veces. Un ciclo que se ejecuta una cantidad especı́fica de veces es un ciclo definido, o un ciclo contado. Por otra parte, la cantidad de veces que el ciclo se ejecuta podrı́a no estar determinado cuando se está ejecutando el programa. Tal ciclo es un ciclo no definido ya que no se sabe cuántas veces será ejecutado. Ciclo while definido Para escribir un ciclo definido se inicializa una variable de control del ciclo, cuyo valor determina si la ejecución del ciclo continúa. Mientras el valor booleano que resulta de la comparación de la variable de control de ciclo y otro valor sea true, el cuerpo del ciclo while se continúa ejecutando. En el cuerpo del ciclo, se deberá incluir una sentencia que modifique la variable de control del ciclo. El siguiente segmento de código muestra la secuencia de enteros del uno al diez. La variable val es la variable de control del ciclo—esta inicia el ciclo teniendo el valor de uno, y mientras el valor es menor que once, val continúa mostrándose e incrementándose. int val; final int LIMITE = 11; val = 1; while (val < LIMITE) { 2 System.out.println(val); val = val + 1; } Cuando se escriben códigos conteniendo ciclos, es fácil cometer errores. La ejecución del siguiente código muestra el mensaje “Hola” siempre (teóricamente) porque no hay código para terminar el ciclo. Un ciclo que nunca termina se conoce como un ciclo infinito. while ( 4>2 ) { System.out.println("Hola"); } En el código anterior, la expresión 4 > 2 evalúa a true ası́ que en ese ciclo, se entra al cuerpo del ciclo y “Hola” es mostrado. Enseguida, la expresión es evaluada nuevamente. La expresión 4 > 2 todavı́a es true, ası́ que se ingresa al cuerpo otra vez. “Hola” se muestra repetidamente; el ciclo nunca termina porque 4 > 2 nunca es false. Es una mala idea escribir un ciclo infinito intencionalmente. Se sugiere que antes de empezar a escribir ciclos infinitos, se sepa como salir de este ciclo. Si al ejecutar una aplicación está en un ciclo infinito, se puede presionar las teclas CTRL-C para que el programa sea abortado. Para prevenir que un ciclo while se ejecute infinitamente, tres acciones separadas deberán ocurrir: Una variable de control del ciclo se deberá inicializar con un valor inicial. La variable de control del ciclo es probada en la sentencia while. La variable de control del ciclo es modificada dentro del cuerpo del ciclo. La variable deberá ser modificada para que la expresión de prueba pueda eventualmente evaluar a false y ası́ el ciclo pueda terminar. Las condiciones anteriores se cumplen en el siguiente segmento de código. Primero, una variable de control del ciclo contadorCiclo es puesta al valor uno. Segundo, la sentencia while(contadorCiclo < 3) es probada. Tercero, el cuerpo del ciclo es ejecutado porque la variable de control del ciclo contadorCiclo es menor que 3. El cuerpo del ciclo consiste de dos sentencias formando un bloque con las llaves que las encierran. La primera sentencia muestra “Hola”, y luego la segunda sentencia agrega uno a contadorCiclo. La siguiente vez que contadorCiclo es evaluado, este es dos, la cual todavı́a es menor que 3, ası́ que el cuerpo del ciclo se ejecuta nuevamente. “Hola” se muestra por segunda vez, y contadorCiclo tiene tres. Finalmente, como la expresión contadorCiclo < 3 evalúa a false, el ciclo termina. La ejecución del programa entonces continúa con cualquier sentencia siguiente. contadorCiclo = 1; while (contadorCiclo < 3) { System.out.println("Hola"); contadorCiclo = contadorCiclo + 1; } 3 Errores al codificar ciclos while Se muestran enseguida algunos errores de lógica que pueden aparecer al no tener cuidado al escribir un ciclo while. Variable de control del ciclo sin modificación en el cuerpo La variable de control del ciclo deberá ser modificada dentro del cuerpo del ciclo. En el siguiente código, parecido al anterior, se han eliminado las llaves. En este caso, el cuerpo del ciclo while termina en el punto y coma al final de la sentencia que muestra “Hola”. Agregar uno a contadorCiclo no es parte de un bloque del ciclo, ası́ que el valor de contadorCiclo nunca cambia, y un ciclo infinito es creado. contadorCiclo = 1; while (contadorCiclo < 3) System.out.println("Hola"); contadorCiclo = contadorCiclo + 1; Creación de un ciclo con un cuerpo vacı́o Al igual que con la sentencia de decisión if, la colocación del punto y coma es importante cuando se usa la sentencia while. Si un punto y coma es incorrectamente colocado al final de la sentencia parcial while(contadorCiclo < 3);, como se muestra en el siguiente código, el ciclo también es infinito. Este ciclo tiene un cuerpo vacı́o, o un cuerpo sin sentencias. Ası́, la expresión booleana es evaluada, y como sea true, se ingresa al cuerpo del ciclo. Como el cuerpo del ciclo está vacı́o, no se toma ninguna acción, y la expresión booleana es evaluada otra vez. Nada ha cambiado, ası́ que sigue siendo true, se entra al cuerpo vacı́o, y el ciclo infinito continúa. contadorCiclo = 1; while (contadorCiclo < 3); { System.out.println("Hola"); contadorCiclo = contadorCiclo + 1; } Modificación de la variable de control del ciclo definido Un ciclo definido es un ciclo controlado por contador porque la variable de control del ciclo es cambiada contando. Es habitual modificar el valor de la variable de control del ciclo agregando uno a esta, o incrementando la variable. Sin embargo, no todos los ciclos son controlados agregando uno. El siguiente ciclo muestra “Hola” dos veces, pero el ciclo es controlando sustrayendo uno a la variable de control de ciclo, o decrementándola. contadorCiclo = 3; 4 while (contadorCiclo > 1) { System.out.println("Hola"); contadorCiclo = contadorCiclo - 1; } En el segmento de programa previo, la variable contadorCiclo inicia con un tres. contadorCiclo es mayor que uno, ası́ el cuerpo del ciclo muestra “Hola” y decrementa contadorCiclo a dos. La expresión booleana en el ciclo while es revisada otra vez. Como dos es mayor que 1, “Hola” es mostrado otra vez, y contadorCiclo se hace uno. Ahora contadorCiclo no es mayor que uno, ası́ el ciclo termina. Hay varias formas de ejecutar un ciclo dos veces. Es importante no usar metodos inusuales para contar repeticiones porque hacen al programa confuso; por ejemplo iniciar con doce, continuar mientras sea mayor que dos, y decrementar el valor por cinco cada vez. El método más claro y mejor es iniciar la variable de control del ciclo en cero o uno, incrementar por uno cada vez a través del ciclo, y parar cuando la variable de control de ciclo alcance el lı́mite apropiado. Nota. Muchos programadores expertos inician los valores de los contadores en cero porque estos son usados cuando se trabaja con arreglos. En el caso de los arreglos sus elementos están numerados o identificados con un ı́ndice iniciando con cero, ya que todos los elementos comparten el mismo nombre. Ciclo while indefinido En ocasiones un ciclo es alterado por la entrada del usuario en vez de ser un ciclo controlado por un contador, un ciclo indefinido es un ciclo controlado por evento, es decir, un evento ocurre para determinar si el ciclo continúa. Un ciclo controlado por evento es un tipo de ciclo indefinido porque no se sabe cuántas veces será repetido. Por ejemplo, quizás se quiera continuar haciendo alguna tarea en tanto el usuario indique su deseo de continuar presionando alguna tecla, en este caso no se sabe la cantidad de veces que será ejecutado el ciclo. Considerar una aplicación en la cual se pide al usuario por un saldo bancario y enseguida se pide si el usuario quiere ver el saldo después de que el interés ha sido acumulado. Cada vez que el usuario decide continuar, un saldo incrementado aparece, mostrando un año más de interesés acumulados. Cuando el usuario finalmente decide salir, el programa termina. El programa aparece en el código 1. 5 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import j a v a . u t i l . Scanner ; public c l a s s S a l d o B a n c a r i o { public s t a t i c void main ( S t r i n g [ ] a r g s ) { double s a l d o ; int r e s p u e s t a ; int año = 1 ; f i n a l double TASA = 0 . 0 3 ; Scanner t e c l a d o = new Scanner ( System . i n ) ; System . out . p r i n t ( ” I n g r e s a r e l s a l d o b a n c a r i o i n i c i a l : ” ) ; s a l d o = t e c l a d o . nextDouble ( ) ; System . out . p r i n t l n ( ”¿ Q u i e r e s v e r e l s a l d o en e l s i g u i e n t e año ? ” ) ; System . out . p r i n t ( ” I n g r e s a 1 para SI ” ) ; System . out . p r i n t ( ” o c u a l q u i e r o t r o número para NO: ” ) ; respuesta = teclado . nextInt ( ) ; while ( r e s p u e s t a == 1 ) { s a l d o = s a l d o + s a l d o ∗ TASA; System . out . p r i n t l n ( ”\ t \ tDespué s d e l año ” + año + ” con ” + (TASA∗ 1 0 0 ) + ” % de i n t e r és , ” + ” e l saldo es $” + saldo ) ; año = año + 1 ; System . out . p r i n t l n ( ”¿ Q u i e r e s v e r e l s a l d o a l f i n a l de o t r o año ? ” ) ; System . out . p r i n t ( ” I n g r e s a 1 para SI ” ) ; System . out . p r i n t ( ” o c u a l q u i e r o t r o número para NO: ” ) ; respuesta = teclado . nextInt ( ) ; } } } Código 1: Aplicación SaldoBancario. La aplicación del código 1 declara las variables necesarias y una constante para una tasa de interés del 0.03, y luego pide al usuario el saldo. Enseguida la aplicación pide al usuario que ingresa un uno si el usuario quiere ver saldo del siguiente año. Mientras el usuario quiera seguir continuando, la aplicación seguirá mostrando los saldos bancarios incrementados. El ciclo en la aplicación del código 1 inicia con la lı́nea que contiene: while (respuesta == 1) Si el usuario ingresa cualquier entero diferente de uno, el cuerpo del ciclo nunca se ejecuta y el programa termina. Si el usuario ingresa uno, todas las sentencias dentro del cuerpo del ciclo se ejecutan. La aplicación incrementa el saldo por la tasa de interés, muestra el nuevo saldo, agrega uno al a~ no, y pregunta si el usuario quiere otro saldo. La última sentencia en el cuerpo del ciclo acepta la respuesta del usuario. Después de la ejecución del cuerpo del ciclo, el control regresa a la cima del ciclo, donde la expresión boolean en el ciclo while es probada nuevamente. Si el usuario ingresa uno, se entra al ciclo y el proceso inicia nuevamente. 6 Validación de datos Se emplean ciclos no definidos cuando se validan datos de entrada. La validación de datos es el proceso para asegurar que un valor está dentro del rango deseado. Por ejemplo, suponer que se requiere que el usuario meta un valor que no sea mayor que tres. El código 2 muestra una aplicación que no continúa hasta que el usuario ingresa un valor correcto. Si el usuario ingresa tres o menos en la primera petición, nunca se entra al ciclo. Pero, si el usuario ingresa un número mayor a tres, se ejecuta el ciclo, dando otra oportunidad al usuario de ingresar el valor correcto. Mientras el usuario dé datos incorrectos, el ciclo se repetirá. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import j a v a . u t i l . Scanner ; public c l a s s MeteValorChico { public s t a t i c void main ( S t r i n g [ ] a r g s ) { int e n t r a d a U s u a r i o ; f i n a l int LIMITE = 3 ; Scanner t e c l a d o = new Scanner ( System . i n ) ; System . out . p r i n t ( ” I n g r e s a r un e n t e r o que no s e a mayor a ” + LIMITE + ” > ” ) ; entradaUsuario = teclado . nextInt ( ) ; while ( e n t r a d a U s u a r i o > LIMITE ) { System . out . p r i n t l n ( ” El número i n g r e s a d o e s i n c o r r e c t o . ” ) ; System . out . p r i n t ( ” I n g r e s a r un e n t e r o no mayor a ” + LIMITE + ” > ” ) ; entradaUsuario = teclado . nextInt ( ) ; } System . out . p r i n t l n ( ” Se i n g r e s ó c o r r e c t a m e n t e ” + e n t r a d a U s u a r i o ) ; } } Código 2: La aplicación MeteValorChico. Los programadores novatos cometen el error de revisar datos inválidos usando una decisión en vez de un ciclo. Revisan si el dato es inválido usando una sentencia if; si el dato es inválido, lo vuelven a pedir al usuario. Sin embargo, no consideran que el usuario podrı́a meter datos incorrectos más de una vez. Actividad 1. Escribir una aplicación que valide que un entero ingresado por el usuario sea 1, 2, 3, 5, 7 u 11. 3. Operadores aritméticos abreviados Incrementar una variable en un ciclo para llevar la cantidad de ocurrencias de algún evento es conocido como contar. El proceso de repetidamente incrementar un valor por alguna cantidad es conocida como acumular. Java proporciona varios atajos para incrementar y acumular. La sentencia contar += 1; es idéntica a contar = contar + 1;. El operador sumar y asignar es +=. La sentencia saldo += saldo * TASA; incrementa el saldo por un porcentaje de la TASA. Otros atajos que se pueden usar son el operador de sustraer y asignar (-=), operador de multiplicar y asignar (*=), operador 7 de dividir y asignar (/=), operador de residuo y asignar ( %=). Cada uno de estos operadores es usado para hacer la operación y asignación del resultado en un paso. Para incrementar el valor de una variable en uno, se pueden usar otros dos operadores de atajo— el prefijo ++ también conocido como operador de incremento prefijo, y el postfijo ++, u operador de incremento postfijo. Para usar un prefijo ++, se ponen dos signos de suma antes del nombre de la variable. La sentencia algunValor = 6; seguida por ++algunValor; resulta en algunValor teniendo siete. Para usar un postfijo ++, se ponen dos signos más justo después del nombre de la variable. Enseguida se muestran cuatro formas para incrementar valor en uno; cada uno da el mismo resultado. No es obligatorio emplear atajos, pero son una ventaja para escribir menos código y tener menos errores. int valor; valor = 123; ++valor; // valor tiene 124 valor = 123; valor++; // valor tiene 124 valor = 123; valor = valor + 1; // valor tiene 124 valor = 123; valor += 1; // valor tiene 124 El prefijo ++ y el postfijo ++ se usan con variables pero no con constantes. Es ilegal una expresión como ++84;. Nota. Los operadores de incremento prefijo y postfijo son operadores unarios, sólo se usan con una variable. Otros operadores unarios son los operadores de conversión, o los operadores para indicar valor positivo (+) y negativo (-). Cuando sólo se quiere incrementar el valor de la variable en uno, no hay diferencia si se usa el operador incremento prefijo o postfijo. Sin embargo, cuando un operador prefijo o postfijo es usado como parte de una expresión, si hay diferencia en el resultado obtenido. Cuando un operador prefijo es usado en una expresión, primero se incrementa la variable y después se evalúa la expresión, y cuando un operador postfijo es usado primero se evalúa la expresión y luego se incrementa la variable. Considerar las siguientes sentencias: b = 4; c = ++b; El resultado es que ambos b y c tienen el valor de cinco porque b es incrementado a cinco y luego el valor de la expresión es asignado a c. Cuando se usa el postfijo ++, el valor de la expresión antes del incremento es guardado. Considerar la siguientes sentencias: 8 b = 4; c = b++; b tiene el valor de cinco, pero c es sólo cuatro. La aplicación DemoIncremento, código 3, ilustra la diferencia de la forma como trabajan los operadores prefijo y postfijo. Cuando el operador incremento prefijo es usado en miNumero, este pasa de 123 a 124, y el resultado se asigna a respuesta, ası́ que tendrá 124. Luego de que el es puesto 123 en miNumero, el operador incremento postfijo es usado; 123 es asignado a respuesta, y miNumero es incrementado a 124. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import j a v a . u t i l . Scanner ; public c l a s s DemoIncremento { public s t a t i c void main ( S t r i n g [ ] a r g s ) { int miNumero , r e s p u e s t a ; miNumero = 1 2 3 ; System . out . p r i n t l n ( ” Antes de i n c r e m e n t a r , miNumero e s ” + miNumero ) ; r e s p u e s t a = ++miNumero ; System . out . p r i n t l n ( ” Luego d e l i n c r e m e n t o p r e f i j o , miNumero e s ” + miNumero ) ; System . out . p r i n t l n ( ” y r e s p u e s t a e s ” + r e s p u e s t a ) ; miNumero = 1 2 3 ; System . out . p r i n t l n ( ” Antes de i n c r e m e n t a r , miNumero e s ” + miNumero ) ; r e s p u e s t a = miNumero++; System . out . p r i n t l n ( ” Luego d e l i n c r e m e n t o p o s t f i j o , miNumero e s ” + miNumero ) ; System . out . p r i n t l n ( ” y r e s p u e s t a e s ” + r e s p u e s t a ) ; } } Código 3: Aplicación DemoIncremento. Escoger si se usa operador prefijo o postfijo es importante cuando es parte de una expresión más grande. Por ejemplo, si d es 5, entonces 2 * ++d es 12, pero 2 * d++ es 10. Una lógica similar se aplica cuando se usan los operadores decremento prefijo y postfijo. Por ejemplo, si b = 4 y c = b--, 4 es asignado a c, pero b es decrementado y tiene el valor 3. Si b = 4 y c = --b, b es decrementado a 3 y 3 es asignado a c. Actividad 2. 4. Creación de ciclos for Un ciclo for es un ciclo especial que es usado cuando una cantidad definida de iteraciones del ciclo es requerida, ya que tiene una forma adecuada para crear un ciclo controlado por un contador. Un ciclo while puede ser usado para lo anterior, sin embargo el ciclo for da una notación breve para el ciclo definido. Al usar un ciclo for, se puede indicar el valor inicial para la variable de control del ciclo, la condición de prueba que controla la entrada al ciclo, y la expresión que altera la variable 9 de control del ciclo. El ciclo for inicia con la palabra reservada for seguido por un juego de paréntesis. Se puede usar una sola sentencia o un bloque de sentencias como el cuerpo de un ciclo for. Muchos programadores recomienden que siempre se use un juego de llaves para envolver el cuerpo de un ciclo for por claridad. Suponiendo que una variable llamada val ha sido declarada como un entero, entonces la sentencia for mostrada a continuación da la misma salida que la sentencia while que está enseguida—ambas muestran los enteros del 1 al 10. for (val = 1; val < 11; ++val) { System.out.println(val); } val = 1; while (val < 11) { System.out.println(val); } Dentro de los paréntesis de la sentencia for, del segmento de código anterior, la sección primera antes de los primeros dos puntos inicializa val a uno. El programa ejecuta esta sentencia una sola vez, sin importar cuantas veces el cuerpo del ciclo for se ejecuta. Después de la inicialización, el control del programa pasa al centro, o sección de prueba, de la sentencia for que está entre los dos puntos. Si la expresión booleana se evalúa a true, se entra al cuerpo del ciclo for. Como val es puesta a uno, la expresión val < 11 es true. El cuerpo del ciclo muestra val. Como el cuerpo del ciclo es una sola sentencia no se requieren las llaves. Después que se ejecuta el cuerpo del ciclo, la sección tercera final del ciclo for que sigue al segundo dos puntos se ejecuta, y val es incrementado a dos. El control del programa pasa de la sección tercera a la sección segunda, donde val es comparada contra once una segunda vez. Como val es todavı́a menor que once, se ejecuta el cuerpo: val (ahora teniendo dos) es mostrado, y luego la tercera, que modifica una porción del ciclo for se ejecuta otra vez. La variable val se incrementa a tres, y el ciclo for continúa. Eventualmente, cuando val no es menor que once (después de que se mostró del uno al diez), el ciclo for termina, y el programa continúa con la sentencia que siga al ciclo. Las tres secciones del ciclo for son también conocidas como inicialización, prueba, e incremento, también se pueden realizar las siguientes tareas: Inicializar más de una variable en la primera sección de la sentencia for usando comas entre estas, como sigue: for (g = 0, h = 1; g < 6; ++g) Realizar más de una prueba usando los operadores Y ó O en la segunda sección, por ejemplo: for (g = 0; g < 3 && h > 1; ++g) 10 Decrementar o realizar alguna otra tarea en la tercera sección, por ejemplo: for (g = 5; g >= 3; --g) Alterar más de una variable en la tercera sección separándolas con comas, como sigue: for (g = 0; g < 10; ++g, ++h, sum += g) Se pueden dejar una o más secciones de un ciclo for vacı́as, sin embargo se requiere seguir poniendo punto y coma. Suponiendo que x ha sido inicializada previamente, se podrı́a escribir: for (; x < 10; ++x) Dejar una sección, o varias, de una sentencia for vacı́a hace la lectura del código difı́cil. Se sugiere usar la misma variable de control del ciclo en las tres partes de la sentencia for. También deberı́a evitarse alterar la variable de control del ciclo en el cuerpo del ciclo, al hacer la modificación en el cuerpo es más difı́cil seguir la lógica del programa, además puede generar errores complejos de quitar. Se podrı́a encontrar un ciclo for que no contenga cuerpo, como en el siguiente: for (x = 0; x < 100000; ++x); Este ciclo se usa para consumir tiempo de tal forma que se dé una breve pausa durante la ejecución del programa. Java tiene un método nativo, sleep, para pausar la ejecución del programa. Este método es parte de la clase Thread del paquete java.lang. También se puede declarar una variable dentro de una sentencia for, como en la siguiente: for (int val = 1; val < 11; ++val) Actividad 3. Escribir una aplicación que muestre todos los números que sean divisores del 200. 5. Creación de ciclos do...while En los ejemplos revisados previamente, la variable de control del ciclo es evaluado en la “cima” del ciclo antes de que el cuerpo tenga oportunidad de ejecutarse. Ambos ciclos while y for son ciclos preprobados—aquellos en los cuales las variables de control del ciclo son probadas antes de que el cuerpo del ciclo se ejecute. En ocasiones se podrı́a necesitar asegurar que el cuerpo de un ciclo se ejecute al menos una vez. Ası́ se quiere escribir un ciclo que revise en la “fondo” del ciclo después de la primera iteración. Tal ciclo es el ciclo do...while; este es un ciclo postprobado—uno en el cual la variable de control del ciclo es probado después de que se ejecuta el cuerpo del ciclo. 11 La figura 5 muestra la estructura general de un ciclo do...while. Observar que el cuerpo del ciclo se ejecuta al menos una vez y antes de que se haga la pregunta de control del ciclo. El código 4 muestra la aplicación SaldoBancario2 la cual contiene un ciclo do...while. El ciclo inicia en la lı́nea 11. El cuerpo del ciclo está enseguida encerrada entre llaves. El saldo del primer año es mostrado antes de que el usuario tenga oportunidad de responder. En el fondo del ciclo, se le pregunta al usuario si quiere ver el saldo al final de otro año, ası́ el usuario tiene la opción de ver más saldos, pero ver el primer mensaje no se puede. Figura 2: Diagrama de flujo de un ciclo do...while. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import j a v a . u t i l . Scanner ; public c l a s s S a l d o B a n c a r i o 2 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { double s a l d o ; int r e s p u e s t a ; int año = 1 ; f i n a l double TASA = 0 . 0 3 ; Scanner t e c l a d o = new Scanner ( System . i n ) ; System . out . p r i n t ( ” I n g r e s a r e l s a l d o b a n c a r i o i n i c i a l : ” ) ; s a l d o = t e c l a d o . nextDouble ( ) ; do { s a l d o = s a l d o + s a l d o ∗ TASA; System . out . p r i n t l n ( ”\ t \ tDespué s d e l año ” + año + ” con ” + (TASA∗ 1 0 0 ) + ” % de i n t e r és , ” + ” e l saldo es $” + saldo ) ; año = año + 1 ; System . out . p r i n t l n ( ”¿ Q u i e r e s v e r e l s a l d o a l f i n a l de o t r o año ? ” ) ; System . out . p r i n t ( ” I n g r e s a 1 para SI ” ) ; System . out . p r i n t ( ” o c u a l q u i e r o t r o número para NO: ” ) ; respuesta = teclado . nextInt ( ) ; } while ( r e s p u e s t a == 1 ) ; } } Código 4: La aplicación SaldoBancario2 usando el ciclo do...while. 12 Cuando el cuerpo de un ciclo do...while contiene una sola sentencia, no se requiere usar llaves. El siguiente ciclo agrega valor a total mientras total permanezca menor que 200: do total += valor; while (total < 200); A pesar de que las llaves no son requeridas en este caso, varios programadores recomiendan usarlas, de esta forma se previene que en la tercera lı́nea del código parezca que inicia un nuevo ciclo while en vez de la finalización del ciclo do...while previo. El siguiente ejemplo incluye las llaves y es menos probable de confundir al lector: do { total += valor; } while (total < 200); No es obligatorio usar el ciclo do...while. Pero cuando se sabe que se quiere hacer alguna tarea al menos una vez, el ciclo do...while es más conveniente. 6. Ciclos anidados Al igual que la sentencia if que puede anidarse, también se puede con los ciclos. Cualquier tipo de ciclo se puede poner dentro de cualquier otro tipo de ciclo. Cuando los ciclos están anidados, cada par contiene un ciclo interno y un ciclo externo. El ciclo interno deberá estar enteramente contenido dentro del ciclo externo; los ciclos no se pueden traslapar. Se puede anidar virtualmente cualquier cantidad de ciclos; sin embargo, en algún punto, el sistema no podrá guardar toda la información necesaria del ciclo. Suponer que se quieren mostrar los saldos bancarios futuros mientras se varı́an los años y las tasas de interés. El códigó 5 muestra una aplicación que contiene un ciclo externo que varia las tasas de interés dentro de un rango. Al inicio del ciclo externo, el saldo es reiniciado a su valor inicial para que los cálculos sean correctos para cada valor de interés revisado. El ciclo interno varı́a la cantidad de años y muestra cada saldo calculado. 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import j a v a . u t i l . Scanner ; public c l a s s S a l d o I n t e r e s A n u a l { public s t a t i c void main ( S t r i n g [ ] a r g s ) { double s a l d o I n i c i a l ; double s a l d o ; int año = 1 ; double i n t e r e s ; int r e s p u e s t a ; f i n a l double BAJO = 0 . 0 2 ; f i n a l double ALTO = 0 . 0 5 ; f i n a l double INCREMENTO = 0 . 0 1 ; f i n a l int MAX AÑOS = 4 ; Scanner e n t r a d a = new Scanner ( System . i n ) ; System . out . p r i n t ( ” I n g r e s a r e l s a l d o b a n c a r i o i n i c i a l : ” ) ; s a l d o I n i c i a l = e n t r a d a . nextDouble ( ) ; f o r ( i n t e r e s = BAJO; i n t e r e s <= ALTO; i n t e r e s += INCREMENTO) { saldo = s a l d o I n i c i a l ; System . out . p r i n t l n ( ”\nCon un s a l d o i n i c i a l de $ ” + s a l d o + ” con una t a s a de i n t e r é s de ” + i n t e r e s ) ; f o r ( año = 1 ; año <= MAX AÑOS ; ++año ) { saldo = saldo + saldo ∗ interes ; System . out . p r i n t l n ( ”Despué s d e l año ” + año + ” saldo es $” + saldo ) ; } } } } Código 5: La clase SaldoInteresAnual contiene ciclos anidados. Cuando se anidan ciclos, en ocasiones no hay ninguna diferencia cual variable controla el ciclo externo y cual el interno, pero en ocasiones sı́ hay diferencia. Con ciclo anidados se debe pensar en el ciclo externo como el ciclo que abarca. La variable en el ciclo externo cambia con menor frecuencia. Suponer que se tiene un método imprimirEtiqueta que hace etiquetas de correo personalizadas con tres diferentes colores para hacer propaganda. Los siguientes ciclos anidados llaman al método 60 veces y producen tres etiquetas para el primer cliente, tres etiquetas para el segundo, y ası́ sucesivamente: for (cliente = 1; cliente <= 20; ++cliente) for (color = 1; color <= 3; ++color) imprimirEtiqueta(); Los siguientes ciclos anidados también llaman 60 veces a imprimirEtiqueta, y generan las mismas 60 etiquetas, pero crean primero 20 etiquetas con el primer color, 20 etiquetas del segundo color, y finalmente 20 etiquetas del tercer color. for (color = 1; color <= 3; ++color) for (cliente = 1; cliente <= 20; ++cliente) imprimirEtiqueta(); 14 Si cambiar el cartucho de color es un proceso consumidor de tiempo que sucede en el método, entonces el último ejemplo se ejecutará más rápido que el ejemplo que le antecede. Actividad 4. Escribir una aplicación que genere las tablas de multiplicar a partir del 91 y hasta el 100. Cada una de las tablas de multiplicar irá desde 0 hasta 20, ası́ entonces la primera tabla será desde 91 x 0, 91 x 1, . . . ,91 x 20, luego seguirı́a la del 92, y ası́ hasta llegar a la del 100. 7. Rendimiento de ciclos Ya sea que se use un ciclo while, for, o do...while, se puede mejorar el rendimiento del ciclo considerando lo siguiente: Asegurarse que el ciclo no incluya operaciones o sentencias innecesarias. Considerar el orden de evaluación para los operadores cortocircuitados. Hacer comparaciones con cero. Emplear fusiones de ciclos. Usar incremento prefijo en vez de incremento postfijo. Evitar operaciones innecesarias Se pueden hacer los ciclos más eficientes no haciendo operaciones o sentencias innecesarias, ya sea en la expresión de prueba del ciclo o dentro del cuerpo del ciclo. Por ejemplo, suponer un ciclo que se debe ejecutar mientras x es menor que la suma de dos enteros, a y b. El ciclo se podrı́a escribir como: while (x < a + b) // cuerpo del ciclo Si el ciclo se ejecuta 1,000 veces, entonces la expresión a + b también es calculado 1,000 veces. Se puede usar el siguiente código, con el mismo resultado, pero la suma es hecha una sola vez: int suma = a + b; while (x < suma) // cuerpo del ciclo Este código es más eficiente siempre y cuando a y b no sean alteradas en el cuerpo del ciclo, porque sino la suma se debe hacer en cada iteración del ciclo. De igual forma se debe evitar llamar a un método dentro de un ciclo si es posible. Por ejemplo, si el método getNumeroDeEmpleados() siempre regresa el mismo valor durante la ejecución del programa, entonces un ciclo como el siguiente estarı́a haciendo muchas llamadas innecesarias: 15 while (contador < getNumeroDeEmpleados())... Por lo que es más eficiente llamar al método una sola vez, dejando el resultado en una variable, y usar la variable en la prueba del ciclo, como sigue: int numEmpleados = getNumeroDeEmpleados(); while (contador < numEmpleados)... Orden de evaluación en operadores cortocircuitados Los operadores lógicos Y y O usan evaluación cortocircuitada, es decir, son evaluados tanto como sea necesario para determinar si la expresión es true o false. Suponer que se requiere pedir a un usuario una cantidad de copias de un reporte entre 0 y 15, y validar la entrada del usuario antes de proceder. Si la probabilidad de que el usuario dé un valor muy grande, es mayor respecto a la probabilidad de que dé un numero negativo, el ciclo que pide nuevamente al usuario la cantidad de copias se deberá escribir como: while (cantidadSolicitada > LIMITE || cantidadSolicitada < 0)... Comparaciones con cero Hacer comparaciones con cero es más rápido que hacer comparaciones contra cualquier otro valor. Por lo tanto, si en la aplicación hacer la comparación con cero es factible, se puede mejorar el rendimiento del ciclo estructurando los ciclos para comparar la variable de control del ciclo con cero en vez de cualquier otro valor. Por ejemplo, un ciclo que trabaje con una variable que varı́e de 0 hasta 100,000, se ejecuta el mismo número de veces como un ciclo en una variable que varı́e desde 100,000 hasta 0, pero este segundo ciclo termina más pronto. Fusiones de ciclos La fusión de ciclos es la técnica de combinar dos ciclos en uno. Por ejemplo, suponer que se quiere llamar dos métodos cada uno 100 veces. Se puede poner una constante con nombre VECES a 100 y usar el siguiente código: for (int x = 0; x < VECES; ++x) metodo1(); for (int x = 0; x < VECES; ++x) metodo2(); Sin embargo también se puede usar el siguiente código: 16 for (int x = 0; x < VECES; ++x) { metodo1(); metodo2(); } La fusión de ciclos no siempre se puede realizar, en ocasiones todas las actividades del metodo1() deberán ser terminadas antes que la del metodo2(). Por otra parte, si se logra terminar con unos cuantos milisegundos haciendo el código más difı́cil de entender, se deberá preferir un programa más lento pero más leı́ble. 17