Introducción a la Programación

4 Control de Flujo

Hasta ahora un programa no era más que una secuencia de instrucciones que son ejecutadas en el orden en que son formuladas. Además, si quisieramos que un conjunto de ellas se repitiera varias veces, no tenemos más remedio que escribirlas varias veces. El orden de ejecución de las sentencias es lo que se conoce por el flujo del programa, y se puede variar a voluntad utilizando las sentencias de control de flujo, con las que es muy fácil alterarlo para adecuarlo a nuestras necesidades. Esto es lo que hace potentes a los lenguajes de programación. Básicamente, existen tres tipos de sentencias, secuencial, condicional e iterativa, y con ellas se puede escribir cualquier programa.

4.1 Sentencias Condicionales

Exiten dos formas de alterar el flujo dependiendo de una condición. La idea básica es la de encontrarnos ante una bifurcación de un camino, en la que seguiremos por uno u otro camino dependiendo de la respuesta a una pregunta que se halla escrita en la bifurcación. Una variante de esto es que en lugar de dos posibilidades, se nos presenten más caminos por los que poder seguir.

4.1.1 Sentencia if-else

La forma más elemental de control de flujo condicional es la sentencia if-else, cuya sintaxis es la siguiente:

if (expresión_condicional)
   sentencia1
else
   sentencia2

Una sentencia if evaluará primero su condición, y si se evalúa como true ejecutará la sentencia o bloque de sentencias que se encuentre justo después de la condición. Si la condición se evalúa como false y existe una cláusula else, entonces se ejecutará la sentencia o bloque de sentencias que siguen al else. Esta parte es opcional. La única restricción que se impone a la expresión condicional es que sea una expresión válida de tipo booleano que se evalúe como true o como false.

Un ejemplo de código con un if-else es el siguiente, en el que se elige el mayor de dos enteros:

int a=3, b=5, m;

if (a<b)
   m=b;
else
   m=a;

Un ejemplo de condicional sin parte else es el siguiente, donde se imprime si un número es par o impar:

int a=3;

String mensaje="El número " + a + " es ";

if (a%2 != 0)
   mensaje+="im";

mensaje+="par.";
System.out.println(mensaje);

produciendo el resultado

El número 3 es impar.
El número 4 es par.

dependiendo de si a es inicializada a 3 o a 4.

Es posible construir una serie de comprobaciones uniendo un if a la cláusula else de un if anterior. Por ejemplo, el siguiente trozo de código muestra una calificación dada una nota numérica:

float nota=6.7F;
String calificacion;

if(nota>=9.0) 
   calificacion="Sobresaliente";
else if(nota>=7.0)
   calificacion="Notable";
else if(nota>=5.0)
   calificacion="Aprobado";
else
   calificacion="Suspenso";
   
System.out.println("La calificación obtenida es: " + calificacion);

En este tipo de secuencias de sentencias if-else anidadas las cláusulas else siempre se emparejan con el if más cercano sin else. Para variar este comportamiento se deben utilizar llaves para crear bloques de sentencias.

4.1.2 Sentencia switch

Una sentencia switch se emplea para efectuar selecciones múltipes que bifurcarán a diferentes segmentos de código dependiendo del valor de una variable o una expresión entera.

La forma general de una sentencia switch es la siguiente:

switch (expresion) {
   case valor1:
      bloque_código_1;
      break;
   case valor2:
      bloque_código_2;
      break;
   ...
   case valorN:
      bloque_código_N;
      break;
   default:
      bloque_código_default;
      break;
}

Las expresiones empleadas en una sentencia switch deben ser de tipo entero o de cualquier otro tipo que pueda ser convertido implícitamente en tipo int sin pérdida de información: byte, short y char. Para los tipos long, float y double se necesita una conversión explícita. Los valores case deben ser expresiones que puedan ser evaluadas como constantes enteras o convertidas implícitamente a un valor de tipo int.

Una sentencia switch evalúa una expresión entera cuyo valor se usa para encontrar una etiqueta case adecuada entre las presentes en el bloque siguiente. Si se encuentra una estiqueta case adecuada, se ejecutan las sentencias comenzando por la primera sentencia que la sigue hasta que encontremos una cláusula break o la llave } del bloque switch. Si no, se ejecutan las sentencias que se encuentran tras la etiqueta default si la hubiere. Si no hay etiqueta default se salta toda la sentencia switch.

Todas las etiquetas case deben ser expresiones constantes, es decir, deben contener sólo literales o campos static final inicializados con expresiones constantes. Los valores case deben ser únicos y haber como máximo una etiqueta default.

El siguiente código asigna un color a un número:

int codigoColor=4;
String color;

switch (codigoColor){
   case 0:
      color="negro";
      break;
   case 1:
      color="rojo";
      break;
   case 2:
      color="amarillo";
      break;
   case 3:
      color="azul";
      break;
   default:
      color="indefinido";
}
System.out.println("El color "+codigoColor+" corresponde al "+color);

En algún caso puede convenir agrupar más de un caso bajo el mismo comportamiento; y otras veces no se especificará una sentencia break en cada caso para que la ejecución traspase más de un caso en cascada. Esto se puede ver en el siguiente ejemplo que calcula la potencia i-ésima de 2:

int i=3;
long base=2;
long número=base;

if (0<=i && i<=4){
   switch (i){
      case 4:
         número*=base;
      case 3:
         número*=base;
      case 2:
         número*=base;
         break;
      case 1:
         número=base;
         break;
      case 0:
         número=0;
         break;
   }
   System.out.println("la potencia "+i+"-esima de "+base+" es "+número);
} else
   System.out.println("ERROR: exponente negativo o demasiado grande.");

4.2 Sentencias Repetitivas

Las sentencias repetitivas o de bucle permiten la ejecución repetida de bloques de sentencias. Existen tres tipos de sentencias: bucles while, do-while y for. Los bucles de tipo while y for comprueban si deben continuar repitiendo la sentencia o bloque de sentencias que forman el cuerpo del bucle antes de que el cuerpo del bucle se ejecute, mientras que el bucle do-while lo hace después de haberlo ejecutado.

Otra consideración a hacer es que debemos utilizar un bucle for cuando sepamos el número de veces que se ha de repetir la ejecución del cuerpo del bucle, mientras el bucle while será más adecuado cuando el control de la repetición recae en una condición de otro tipo.

4.2.1 Bucles while y do-while

La forma general de un bucle while es la siguiente:

while (expresiónBooleana)
   sentencia;

En un bucle while primero se evalúa la expresión booleana. Si esta es true, entonces el cuerpo del bucle será ejecutado. En otro caso, la ejecución pasará a la sentencia inmediatamente siguiente al bucle while. El cuerpo del bucle puede ejecutarse 0 o más veces, ya que la primera vez que se evalúe la expresión booleana puede ser false. Si la expresión booleana nunca se evalúa a false nos encontraremos con un bucle infinito, ya que nunca acabará. Este tipo de situación conviene ser evitada, ya que produce programas que nunca acaban.

El siguiente trozo de código muestra los números del 1 al 10:

int n=1;

while (n<=10){
   System.out.println(n);
   n++;
}

Si la expresión booleana nunca se evalúa a false nos encontraremos con un bucle infinito, ya que nunca acabará. Este tipo de situación conviene ser evitada, ya que produce programas que nunca acaban. Esto se puede ver si en el ejemplo anterior comentamos la sentencia de incrementa n:

int n=1;
 
while (n<=10){
   System.out.println(n);
   //n++;
}

Como n nunca se incrementa, nunca llegará a valer 10, y por lo tanto la expresion booleana siempre será evaluada como true.

A veces puede venir bien que el cuerpo del bucle se ejecute al menos una vez, razón por la que Java tiene el bucle do-while, cuya forma general es la siguiente:

do
   sentencia;
while (expresiónBooleana);

4.2.2 Bucles for

La forma general de un bucle for es la siguiente:

for (exprInicialización; exprBooleana; exprIncremento)
   sentencia;

que es equivalente a

exprInicialización;
while (exprBooleana){
   sentencia;
   exprIncremento;
}

Un ejemplo de utilización de un bucle for es el siguiente, donde se calcula el factorial de un número positivo:

int n=4;  // número para calcular su factorial
int i;  // indice del for
long factorial=1;  // 1! y 0! son igual a 1
 
if (n>1) {
   for(i=n;i>1;i--)
      factorial*=i;
}
System.out.println(n+"!="+factorial);

Las sentencias de inicialización e iteración de un bucle for pueden ser una lista de expresiones separadas por comas, que se evalúan, de izquierda a derecha. Todas las expresiones del bucle for son opcionales. Si exprInicialización o exprIncremento se omiten, se suprime su participación en el bucle. Si la exprBooleana se omite, se supone que es true, por lo que el siguiente puede ser un la representación de un bucle infinito:

for (;;)
   sentencia;

Los bucles están relacionados de una manera natural con el tratamiento de los elementos de un array. Así, el bucle básico que recorre todos los elementos de un array puede ser el siguiente:

int [] a={10,11,12,13,14,15,16,17,18,19};

for(int i=0;i<a.length;i++)
   System.out.println("a["+i+"]="+a[i]);

4.3 Sentencias de Alteración del Flujo

Java soporta tres tipos de sentencias de alteración del flujo: break, continue y return. break se usa para salir de sentencias switch, bucles y bloques de sentencias. continue se usa para bifurcar al final de un bucle justo tras la última sentencia del cuerpo del bucle. return se usa para salir de un método o de un constructor.

Se utilizará break para salir de cualquier bloque, no sólo de un switch, aunque se suele utilizar para salir de un bucle. Un break termina el switch, for, while o do-while más interno.

Una sentencia continue salta al final del cuerpo de un bucle y evalúa la expresión booleana que controla dicho bucle.

La forma general de la sentencia return es la siguiente:

return expresión;

Se usa para devolver el control al lugar desde el que se efetuó la llamada al método o constructor en cuestión. El tipo de la expresión incluida en el return debe ser compatible con el tipo declarado del método. Como los métodos no tienen tipo devuelto, son void, el return no debe llevar expresión asociada.

4.4 Un Ejemplo

A continuación se presenta un ejemplo en el que se manejan sentencias condicionales e iterativas. En el ejemplo se crea una clase de matrices de dos dimensiones y se aportan un constructor para crear e inicializar la matriz y varios métodos para manejar dicha matriz.

/**
 M2 es una clase de una matriz de enteros 
positivos de dos dimensiones 
*/

class M2{

   private int[][] a;   // tenemos una matriz de dos dimensiones 

   /** constructor no-arg */
   M2(){
   a=new int[3][];
   int fila0[]={1,4,5,3,3};
   int fila1[]={1,1,4,8};
   int fila2[]={9,7,2,5,4};
   a[0]=fila0;
   a[1]=fila1;
   a[2]=fila2;
   }

   /** método para leer un elemento de la matriz */
   public int leerM2(int i, int j){
      /* si las coordenadas son correctas */
      if ( (i>=0 && i<a.length) && (j>=0 && j<a[i].length))
         return a[i][j];  // devolver el elemento solicitado
      else  // si no son correctas
         return -1;  // ERROR en las coordenadas
   }

   /** método para conseguir una versión imprimible del objeto M2 */
   public String toString(){
      String mensaje="";
      for(int i=0; i<a.length; i++){
         for(int j=0; j<a[i].length;j++)
            mensaje+=leerM2(i,j)+" "; // mensaje+=a[i][j]+" ";
         mensaje+="\n";
      }
      return mensaje;
   }

   /** método para encontrar el mayor elemento de un objeto M2 */
   public int mayor(){
      int mayor=a[0][0];  // se quedara con el mayor
      int x;  // almacen temporal 
      for(int i=0; i<a.length; i++){
         for(int j=0; j<a[i].length;j++){
            x=leerM2(i,j); // almacenamos en x el i,j-esimo elemento
            if (mayor<x) mayor=x;  // x es el nuevo mayor
         }
      }
      return mayor;
   }
}

/** clase de prueba para M2 */
class prueba{
   public static void main(String [] args){

      M2 mm = new M2();

      System.out.println(mm);
      System.out.println("El mayor número es "+mm.mayor());
   }
}

La ejecución de este programa genera la siguiente salida:

1 4 5 3 3 
1 1 4 8 
9 7 2 5 4 

El mayor número es 9

4.5 Bibliografía


Jesús Vegas
Dpto. Informática
Universidad de Valladolid
jvegas@infor.uva.es