|
En los lenguajes modernos existen distintos tipos de estructuras repetitivas, comunmente llamadas "bucles". Cada una de ellas tiene una funcionalidad concreta. En la mayor parte de casos sólo una de ellas es la indicada.
Si utilizamos un tipo de bucle que no es adecuado para resolver un problema, es probable que escribamos código ineficiente o confuso.
Antes de utilizar un bucle u otro, es conveniente dedicar unos instantes a decidir qué bucle es el idóneo.
Que yo sepa, podríamos distinguir cinco tipos de bucles:
- El bucle while o mientras
- El bucle do-while, o repetir-mientras
- El bucle repeat-until, o repetir-hasta (éste es una variante del anterior, o al revés)
- El bucle for o para
- El bucle foreach o para cada
En lenguajes algo antiguos, podríamos encontar un sexto tipo de bucle, el incondicional, montado con una instrucción de tipo goto. Este bucle deja su repetición y la salida del bucle a saltos incondicionales, que por supuesto, rompen el flujo lineal de ejecución y no entran dentro de la programación estructurada, así que ni merece la pena mencionarlos.
Vamos a ver las características de cada tipo de bucle por separado.
EL BUCLE WHILE
La característica típica de este tipo de bucle es que SE REPITE 0 O MÁS VECES. Es decir, las instrucciones que contiene NO TIENEN POR QUÉ EJECUTARSE AL MENOS UNA VEZ. Podría ser que no se ejecutaran.
En el bucle while, se evalúa una expresión booleana antes de entrar al bucle. Si la expresión es cierta, entonces se ejecuta el contenido del bucle, y comienza un nuevo ciclo. En ese nuevo ciclo, vuelve a comprobarse la condición antes de entrar.
En este bucle, es necesario que se modifique de alguna manera el contenido de las expresiones involucradas en la condición para poder salir de él.
Es un bucle muy tentador, ya que si los lenguajes no dispusieran de otras estructuras repetitivas (otros tipos de bucles), sólo con while podríamos resolver todos los problemas. Sin embargo, los otros bucles vienen mucho mejor y son más fáciles y claros para situaciones concretas. Si no tenemos soltura con la utilización de los bucles, a la hora de plantearnos cuál utilizar, while debería ser nuestra última opción.
Su sintaxis suele ser muy similar en todos los lenguajes. Por ejemplo, en los lenguajes tipo C y sus derivados (C#, C++, Java...) se utiliza de la siguiente manera:
while (condición)
contenido del bucle
Siendo "contenido del bucle" una sola instrucción, o una secuencia de instrucciones, encerrada entre { y }.
En otros lenguajes, como por ejemplo los Pascaloides (aquellos que de alguna u otra manera se inspiran en Pascal (como Delphi, Modula-2, Oberon... etc) tiene una forma muy similar
while condicion do
contenido del bucle
En estos lenguajes, la condición no va necesariamente entre paréntesis, pero termina con la palabra "do". Nuevamente, "contenido del bucle" puede ser una sóla sentencia, o un bloque, encerrados en este caso entre las palabras begin y end.
Bueno... la sintaxis es lo de menos. Lo importante es cuándo utilizarlo.
Para utilizar un bucle while, debemos enfrentarnos a una situación que cumpla estas condiciones simultáneamente:
1) No podemos determinar a priori cuántas veces se va a ejecutar el bucle. Es decir, el bucle se ejecutará mientras se cumpla una condición tal que no podemos determinar en cuántas vueltas del bucle dejará de cumplirse.
2) Es posible que no necesitemos ejecutar el bucle ni una sola vez.
Por ejemplo, casos para los que NO está indicado este bucle.
-Hacer un programa que saque por pantalla los números del 1 al 10... No cumple la primera condición: sí sé a priori cuántas vueltas del bucle necesito: 10.
-Hacer un programa saque por pantalla números contando al revés, desde 1000 hasta n (n<1000), sólo los pares... Tampoco cumple la condición 1: si sé a priori cuántas vueltas del bucle necesito: (1000-n)/2. No es tan evidente como en el caso anterior pero sí que lo sé.
-Pedir una serie de números por teclado y hallar la suma de todos ellos, hasta que se introduzca un "-1", que no se suma y se termina... No cumple necesariametne la segunda condición... Hay que pedir al menos un número. El contenido del bucle debería repetirse al menos una vez.
EL BUCLE DO-WHILE
La característica típica de este tipo de bucle es que SE REPITE 1 O MÁS VECES. Es decir, las instrucciones que contiene SE EJECUTAN SIEMPRE AL MENOS UNA VEZ.
En el bucle do-while, se ejecutan una serie de instrucciones, y después, se comprueba una expresión booleana. Si esta expresión es cierta, se ejecuta de nuevo el bucle, y si no, se termina.
No todos los lenguajes poseen este bucle. En C y sus derivados, tiene esta forma:
do
contenido del bucle
while (condicion)
Este bucle se utiliza en situaciones típicas en las cuales es necesario realizar algún tipo de acción o cálculo, y posteriormente, una comprobación sobre éste.
Para utilizar un bucle do-while, debemos enfrentarnos a una situación que cumpla estas condiciones simultáneamente:
1) No podemos determinar a priori cuántas veces se va a ejecutar el bucle
2) Seguro que el contenido del bucle debe ejecutarse al menos una vez.
Este bucle, por ejemplo, sí es el indicado para una situación de este estilo:
-Pedir una serie de números por teclado y hallar la suma de todos ellos, hasta que se introduzca un "-1", que no se suma y se termina: Como mínimo, debemos pedir al menos un número por teclado... así que podemos utilizar un bucle de este estilo, que se ejecuta al menos una vez... y después, contiuar en el bucle mientras el valor introducido sea distinto de "-1"
-Abrir un fichero de texto, e ir leyendo líneas hasta que una de ellas contenga la palabra "fin"... El mismo caso: es necesario leer al menos una línea.
No estaría indicado en casos como estos:
-Hacer un programa que saque por pantalla los números del 1 al 10... No cumple la primera condición: sí sé a priori cuántas vueltas del bucle necesito: 10.
-Recorrer una cadena de texto e imprimir las vocales de esa cadena, parando cuando encuentre una "K": Cumple la primera condición, ya que debo parar al encontrar una "K" (eso hace que a priori no sepa cuántas vueltas del bucle necesito). Sin embargo, no debería ejecutarse al menos una vez: si la cadena estuviera vacía, no sería necesario entrar en el bucle ni una sola vez.
EL BUCLE REPEAT-UNTIL
Los lenguajes pascaloides (inspirados en Pascal) y algunos otros no poseen el bucle do-while, sino una variante denominada repeat-until.
Básicamente, se utiliza de la misma manera que el do-while, con una salvedad: se sale del bucle cuando la condición es verdadera, a diferencia del do-while, en el que es justo al revés.
Su sintaxis suele ser de éste estilo:
repeat
contenido del bucle
until condición;
Por supuesto, se utiliza en las mismas situaciones que do-while.
EL BUCLE FOR
La característica de este bucle es que se utiliza cuando el número de iteraciones que se necesitan está perferctamente definido.
A diferencia de los anteriores, cuando se entra en un bucle for, no se sale hasta que han concluido todas las iteraciones previstas.
La sintaxis del bucle for en los lenguajes pascaloides tiene una sintaxis muy clara y estricta, a diferencia de los lenguajes derivados del C, en los cuales éste bucle tiene una utilización más abierta que lo hace propicio a abusar de él, utilizándolo casi de la misma manera que un while.
Vamos a comentarlo desde el punto de vista de los lenguajes pascaloides. Su sintaxis es algo así:
FOR variable:=valor_inicial TO valor_final DO
cuerpo del bucle;
Básicamente, en este tipo de bucle se utiliza una variable que en la primera iteración del bucle vale valor_inicial, en la segunda, vale el valor siguiente a valor_inicial... y así sucesivamente. Cuando esa variable alcanza el valor_final, el bucle se ejecuta por última vez y termina.
Por ejemplo,
for j:=1 to 10 do
writeln(j);
En éste caso, la variable j vale 1 en la primera iteráción, 2 en la segunda, 3 en la tercera...... y 10 en la última. En cada iteración, el cuerpo del bucle consiste simplemente en imprimir j.
Es decir, en el bucle for, una variable designada por el programador toma todos los valores en un rango. Un valor en cada iteración.
Por supuesto, el valor inicial y final no tienen por qué ser valores constantes... puede ser cualquier expresión. Si el valor final fuera menor que el inicial, el bucle no se ejecutaría ninguna vez.
Por ejemplo,
for j:=10 to 1 do
writeln(j);
No produciría ningún resultado.
Si quisieramos contar al revés, desde 10 hasta 1, sería necesario utilizar otra sintaxis: sustituir la palabra to por downto.
for j:=10 downto 1 do
writeln(j);
En este caso, j vale 10 en la primera iteración, 9 en la segunda..... hasta llegar a 1 en la última
Si quisieramos contar de dos en dos, basta con añadir "STEP 2" antes del "do". Para contar de tres en tres, "STEP 3". Por ejemplo,
for j:=1 to 10 step 2 do
writeln(j);
hace que j valga 1 en la primera iteración, 3 en la segunda, 5 en la tercera, 7 en la cuarta, 9 en la quinta..... y ya, porque el siguiente valor deberia ser 11, pero ese valor ya no está entre 1 y 10.
Hay que tener en cuenta una serie de consideraciones acerca de este bucle:
1) Bajo ningún concepto debe modificarse en el contenido del bucle la variable que se utiliza
2) De este bucle no se puede salir con ninguna acción realizada en su interior. El bucle sólo termina cuando la variable ha tomado todos los valores del rango que se le ha definido.
3) No se puede hacer ninguna suposición acerca del valor de la variable una vez terminado el bucle.
Ejemplos de código INCORRECTO Ninguno de ellos provocará un error de compilación. Simplemente, son malas utilizaciones del bucle for.
(* No se puede modificar j en el interior
del bucle *)
for j:=1 to 10 do
begin
writeln(j);
j=j+1;
end;
(* No se debe intentar salir mediante
ninguna acción del interior del bucle*)
for j:=1 to 10 do
begin
writeln(j);
j=11;
end;
(* No se puede hacer ninguna suposición
acerca del valor de j una vez terminado
el bucle *)
for j:=1 to 10 do
begin
writeln(j);
end;
i:=j+3; (* j tiene un valor indefinido *)
Es frecuente ver programas en los cuales no se sigan estas reglas.
Con respecto a la primera de ellas, ten en cuenta que el propio mecanismo del bucle es el encargado de darle valor a la variable en cada iteración. Hacer una modificación a esta variable puede desbaratar ese mecamismo. A veces hay alguien que hace una modificación de este estilo y argumenta la manida frase "pues funciona...". Sí... es posible que funcione en tu compilador y en esa versión concreta... pero los compiladores e intérpretes no esperan que un programador modifique la variable del bucle for en el interior del bucle, y no actuarán de manera regular: el comportamiento de un bucle for cuando se modificala variable en el interior es INDEFINIDO. Por supuesto, ejecutar una acción cuyo comportamiento es indefinido dice muy poco en favor de la calidad de nuestros programas. Por otro lado, nadie podrá entender nuestro código, porque ningun programador puede predecir lo que pasa cuando se modifica la variable del bucle en el interior del bucle.
Con respecto a la segunda, pasa lo mismo. Nadie espera que se necesite salir de un bucle for si no es porque la variable ya ha alcanzado todos los valores. Si estás en ese caso, el bucle que necesitas no es for, sino un while, do-while o repeat-until. Utilizar un bucle for e intentar salir de él por otra circunstancia distinta de haber recorrido todo el rango sólo demuestra que no sabemos escoger el tipo de bucle correcto.
Con respecto a la tercera, el valor de la variable cuando ha terminado el bucle está INDEFINIDO. No se puede hacer ninguna suposición acerca de él. Por supuesto, la variable puede reutilizarse para cualquier otra cosa, pero siempre asignándole un valor nuevo. El valor que contenga cuando el bucle ha terminado no vale para nada.
EL BUCLE FOR EN C, C#, C++, Java Y SIMILARES
El bucle for en estos lenguajes es mucho más permisivo, pero su utilización debe ser la misma que la que hemos comentado en el apartado anterior: que una variable tome valores en un rango, con las mismas restricciones y condiciones.
El bucle for de estos lenguajes permite una muy mala utilización de él... prácticamente idéntica a la de un bucle while. Si ese es el caso, no utilices un for, utiliza un while.
La sintaxis de este bucle en estos lenguajes es ésta:
for (inicialización; condición; accion_al_final)
cuerpo del bucle
La parte inicialización sirve para darle el valor inicial a una variable. Se ejecuta antes de entrar al bucle y una sola vez.
La parte condición sirve para comprobar si la variable está dentro del rango que queremos. Se comprueba antes de cada iteración. Si la condición es verdadera, se produce una iteración del bucle. Si es falsa, no se entra en una nueva iteración.
La parte acción_al_final se ejecuta después de cada iteración, y sirve para que la variable tome su próximo valor dentro del rango.
Así pues, un bucle para mostrar por pantalla los números entre 1 y 10 puede ser de este estilo:
for (j=1; j<=10; j++)
Console.WriteLine(j);
Para contar del 10 al 1
for (j=10; j>=1; j--)
Console.WriteLine(j);
Para contar del 1 al 10 pero de dos en dos
for (j=1; j<=10; j=j+2)
Console.WriteLine(j);
No obstante, la sintaxis de este bucle admite otras muchas utilizaciones. Por ejemplo, éste código sería válido:
int c;
bool b;
c=0;
for(b=false; !b&&c<10; c++) {
b=c*2>10; //cambiar b... da igual por qué motivo
}
Aunque el bucle for de C admite esta utilización ¿Por qué marear a quien lee nuestro código? Nadie espera que utilicemos un bucle for de esa manera. Sería mucho más sensato utilizar un while.
int c;
bool b=false; //inicialización
c=0;
while(!b&&c<10) {
b=c*2>10;
c++; //acción al final
}
Muchos programadores de C están acostumbrados a hacer este abuso del bucle for (y muchos otros) simplemente porque el lenguaje C lo permite. Desde luego, ésto no hace más que dificultar la lectura y comprensión del código.
El bucle for en C debe utilizarse exclusivamente para que una variable adopte todos los valores en un rango bien definido y siguiendo un cierto orden, con un valor en cada iteración. Nada más. Si tenemos una necesidad distinta debemos recurrir a otro tipo de bucle.
El bucle foreach es de aparición más reciente que los otros en los lenguajes. Lo tienen por ejemplo, lenguajes modernos como C# o Java (a partir de la versión 1.5)
Es similar al for: sirve para que una variable tome en cada iteración del bucle un cierto valor. La diferencia es que en el bucle for, la variable toma valores en un cierto rango escalar, típicamente enteros, y con menos frecuencia carácteres o reales; sin embargo, en el bucle foreach, la variable va tomando valores de un conjunto de objetos que se pueda recorrer. Está íntimamente ligado a la programación orientada a objetos.
En estos lenguajes, se pueden construir estructuras de datos que sean contenedores, o colecciones de otros objetos. El bucle foreach sirve para que una variable tome en cada iteración los valores contenidos en uno de estos contenedores...
Por ejemplo, observa este código en C#
List<string></string> lista = new List<string></string>();
lista.Add("Uno");
lista.Add("Dos");
lista.Add("Tres");
foreach (String s in lista)
{
Console.WriteLine(s);
}
En éste código construimos un objeto contenedor, en este caso una lista. El bucle foreach hace que la variable s tome en cada iteración el valor de cada uno de las cadenas de esa lista. Es decir, en la primera iteración vale "Uno", en la segunda vale "Dos" y en la tercera vale "Tres".
Al igual que for, es un bucle pensado para repetirse un número conocido de veces: tantas como elementos contenga el contenedor al que hace referecia. Igualmente, no se debe intentar modificar s desde el interior del bucle, ni se debe intentar salir modificando ningún valor.
EN RESUMEN,
Cada tipo de bucle fue ideado para ser utilizado en una situación concreta. El no seguir estar normas básicas, en muchos casos provocará que no resolvamos correctamente o eficientemente un problema.
En el mejor de los casos, una mala utilización de los bucles provocará que nuestros algoritmos no sean portables, mantenibles, reusables o incluso inteligibles.
Aunque el bucle while puede utilizarse en todas las ocasiones, existen varios tipos más, adecuados para ciertas situaciones.
Aunque el lenguaje C y derivados permitan la utilización del for como si fuera prácticamente un while no es una práctica elengante utilizarlo como un while...... ¡¡¡para eso está el while!!!
Debemos ser consecuentes y dedicar unos instantes a la correcta elección del bucle. No debemos nunca olvidar que nuestros algoritmos deben ser eficientes, eficaces, robustos, reutilizables, portables, fácilmente mantenibles y ampliables, y sobre todo..... inteligibles. Aun poniendo todo de nuestra parte, no siempre salen las cosas bien. Sin poner de nuestra parte, por ejemplo, una correcta utilización de los bucles, el fracaso está garantizado.
Si alguien necesita expresarse artísticamente en la elaboración de sofware haciendo complicadas virguerías extrañas, me parece fantástico: para eso existen concursos como los de obfuscated code , pero en la programación de aplicaciones, la máxima calidad posible debe estar garantizada. |