domingo, 19 de enero de 2014

El lenguaje C III

Los operadores, las constantes y las variables son los constituyentes esenciales de las expresiones en C. Una expresión en C es cualquier combinación válida de estos constituyentes. En virtud de que la mayoría de las expresiones tienden a seguir las reglas generales del álgebra, consideramos que la evaluación de las mismas sigue la misma secuencia que la que se lleva a cabo en una expresión algebraica, y en lo único en lo que tenemos que tener precaución es en no mezclar datos de tipos distintos. En la mayoría de los lenguajes, está terminantemente prohibido llevar a cabo la mezcla de tipos de datos; no está permitido, por ejemplo, tratar de sumar una variable que es de tipo char a una variable que es un número entero de tipo int. Sin embargo, C ofrece cierta flexibilidad que permite mezclar datos de tipos diversos, lo cual puede ser una ventaja pero también puede ser una ventaja en virtud de que pone sobre los hombros del programador todo el peso de la responsabilidad de asegurarse que no se efectuarán operaciones incorrectas al mezclar datos de tipos diversos.

En C, cuando las constantes y las variables de tipos distintos aparecen mezcladas en una misma expresión, son convertidas a un mismo tipo de dato. El compilador C promoverá a los operandos “hacia arriba” hasta el tipo de dato más grande que se encuentre en la expresión, o sea siempre y cuando el espacio de almacenamiento asignado en bits a un cierto tipo de dato sea igual o mayor que el espacio de almacenamiento asignado en bits al tipo de dato que está siendo promovido. Se puede promover un número de punto flotante float que ocupa 32 bits a un número de doble precisión double que ocupa 64 bits, pero no se puede convertir un número de 64 bits a un número de 16 bits en virtud de que ello requería un truncamiento ilegal en la representación binaria del número.

Las reglas para la conversión de tipos son las siguientes:

(1) Los datos de tipo char así como los de tipo short int son convertidos a int, mientras que todos los float son convertidos a double.

(2) Para todos los pares de operandos, si uno de los operandos es un long double entonces el otro operando es convertido a long double. Si uno de los operandos es double el otro operando es convertido a double. Si uno de los operandos es long el otro operando es convertido a long. Y si uno de los operandos es unsigned, el otro es convertido a unsigned.

Una vez que se han aplicado las reglas de conversión, cada par de operandos será del mismo tipo y el resultado de la operación producirá un resultado que es del mismo tipo de ambos operandos. Las promociones de tipo involucran varias condiciones que tienen que ser aplicadas secuencialmente en forma ordenada. A modo de ejemplo, supóngase que en un programa se han declarado cuatro variables de tipos distintos:

   char car;
   int i;
   float f;
   double d;

y que tales variables serán usadas en la evaluación de la expresión:

resultado = car/i + f*d - (f+i);

De acuerdo con las reglas de precedencia para operadores aritméticos, la evaluación de la expresión se lleva a cabo de una manera que puede ser resaltada metiendo paréntesis:

resultado = (car/i) + (f*d) - (f+i);

Las conversiones de tipo ocurren en la manera que se muestra a continuación:




Como puede verse, primero el caracter car es convertido a un número entero y el número de punto flotante f es convertido a double. Tras esto, el resultado de car tendrá que ser convertido a double porque f*d es double. El resultado final será double porque esta vez ambos operandos son double.

Además de que C ya de por sí y en forma predeterminada lleva a cabo promociones con la finalidad de resolver el problema de la evaluación de una expresión cuando aparecen mezclados datos de tipos distintos, también es posible forzar que cierta expresión sea de cierto tipo utilizando una técnica conocida como cast, cuya forma general es:

(tipo)  expresion

en donde el tipo es uno de los tipos de datos permisibles en C. Supóngase por ejemplo que la variable a es un entero y queremos asegurarnos de que la expresión a/5 sea evaluada a un resultado del tipo float, para asegurarnos de que haya un componente fraccional (después del punto decimal). Para ello escribimos lo siguiente:

(float) a/5

Aquí el cast (float) está asociado con la a, lo cual ocasiona a su vez que el 5 sea promocionado al tipo float y que el resultado final de la división sea del tipo float. Sin embargo, hay que ser muy cuidadoso; ya que si la promoción de cast se lleva a cabo de esta manera:

(float) (a/5)

entonces no se evaluará ninguna componente fraccional, ya que en tal caso se lleva a cabo una división de enteros que produce un entero, y el resultado que es elevado a float sigue siendo un entero.

Los casts son considerados operadores, y como tales un cast es un operador unario (actúa sobre un solo operando), teniendo la misma precedencia que cualquier otro operador unario. Hay situaciones en las cuales un cast puede ser muy útil. Supóngase por ejemplo que se quiere usar un entero para el control de un bucle, pero al mismo tiempo llevar a cabo una operación que requiere la parte fraccionaria, como en el caso del siguiente programa:


   #include <stdio.h>

   main()
   {
     int i;

     for(i=1; i<=10; ++i )
        printf("%d / 2 es: %f\n", i, (float) i/3);
   }


La ejecución del programa anterior produce el siguiente resultado en la ventana DOS simulada del entorno Borland C:




Sin el cast (float), únicamente se lleva a cabo una división entre dos enteros y no se obtienen los resultados deseados. El cast asegura que la parte fraccionaria será parte de la respuesta que queremos ver en la pantalla.

Se vuelve a recalcar que en un programa C podemos poner espacios en blanco a nuestro antojo en cualquier lugar, los cuales son ignorados por el compilador C pero pueden hacer un programa más legible. Las siguientes expresiones son la misma cosa:

   Q = 3.1416/(arco)-R*(450/diametro);

   Q = 3.1416 / (arco) - R * (450 / diametro);

El uso de paréntesis redundantes o adicionales no producirán errores ni disminuirán en nada la rapidez de ejecución de un programa en virtud de que son removidos por el compilador al momento de fijarse la precedencia de las operaciones a ser llevadas a cabo. Es buena práctica en C (y en cualquier otro lenguaje de programación) usar paréntesis para dejar en claro el orden exacto en el cual se llevará a cabo una evaluación, tanto para nosotros mismos como para otros que tengan que leer y comprender el programa en un tiempo posterior. En las siguientes dos expresiones que son la misma cosa, la segunda parece ser más fácil de leer:

   H=p/7-42*velocidad-8;

   H = (p/7) - (42*velocidad) - 8;

Veremos ahora en mayor detalle un tema que es la esencia de cualquier programa de cómputo en virtud de que gobierna el flujo de la ejecución de un programa: los enunciados de control del programa. Las muy diversas maneras en las cuales se pueden implementar es lo que le dá potencia y versatilidad al lenguaje C. Los enunciados de control pueden ser separados en tres categorías. La primera categoría consiste de instrucciones condicionales if y switch. La segunda categoría consiste de los enunciados usados en los bucles de control while, for, y do-while. La tercera categoría le concierne al salto incondicional goto, cuyo uso indiscriminado no es muy recomendable en una programación bien estructurada aunque tal vez habrá situaciones esporádicas en las cuales su uso resulta conveniente.

Recuérdese que un enunciado en C puede consistir de un enunciado sencillo (terminado en un semicolon), un bloque de enunciados (frecuentemente encapsulados entre corchetes), o nada, lo cual es conocido como un enunciado vacío. Al hablar de un enunciado, estamos considerando todas estas posibilidades.

Aunque ya vimos con anterioridad una breve introducción al enunciado if, aquí repasaremos en mayor detalle dicho enunciado. La sintaxis de la forma general de un enunciado if es:

   if(condicion)  enunciado;
   else enunciado;

La cláusula else es optativa dependiendo de lo que se requiera en el programa. Si la condición se evalúa a un resultado verdadero (lo cual en C puede ser cualquier cosa que no sea cero), el enunciado o bloque que forma el objetivo del enunciado if es ejecutado. De lo contrario, y si acaso existe, el enunciado o bloque que es el objetivo de else es ejecutado. Unicamente el código asociado con el if o el código asociado con el else será ejecutado, pero nunca ambos. Los objetivos de ataque de if y else pueden ser enunciados sencillos o bloques de enunciados. Obsérvese que en C se prescinde por completo de la palabra THEN usada en el tipo de enunciados IF-THEN-ELSE empleados en lenguajes como el que usa Visual Basic, por no ser absolutamente necesaria aunque la omisión ciertamente resta algo de claridad a los programas.

A continuación veremos un programa C que nos muestra al if en acción. Se trata de un programa que lleva a cabo conversiones de bases numéricas, el cual puede convertir números decimales a hexadecimales, números hexadecimales a decimales, números decimales a octales, y números octales a decimales. El programa nos permite escoger de un menú el tipo de conversión que queremos llevar a cabo, y el resultado será entregado en la pantalla. Este tipo de programas en otros tiempos eran parte de las utilerías usadas por los programadores antes del advenimiento de los sistemas operativos con interfaces visuales.

La clave de las habilidades del programa de conversión radican en dos comandos de formato especiales que son usados en las funciones scanf() y printf(), el formato %x y el formato %o. Cuando usamos el formato %x con printf(), hacemos que un entero sea puesto en la pantalla en formato hexadecimal. Y si usamos el código de formato %x con scanf(), haremos que el número proporcionado por el usuario sea interpretado como un número en formato hexadecimal. De la misma manera el código de formato %o ocasiona que printf() imprima un entero en sistema octal y que scanf() interprete el número proporcionado por el usuario como un número en formato octal. La conversión utiliza una serie de enunciados if para determinar qué tipo de conversión quiere llevar a cabo el usuario. Puesto que la operación de igualdad puede estar aparejada con solo una de las selecciones del menú, solo se puede ejecutar una conversión cada vez que el programa se corre. En este programa el objetivo de cada if es un bloque de código.


   /* Programa de conversion de bases numericas */

   #include <stdio.h>

   main()
   {
     int seleccion;
     int valor;

     printf("Convertir:\n");
     printf("  1: decimal a hexadecimal\n");
     printf("  2: hexadecimal a decimal\n");
     printf("  3: decimal a octal\n");
     printf("  4: octal a decimal\n");
     printf("dame tu seleccion: ");
     scanf("%d", &seleccion);

     if(seleccion == 1) {
       printf("dame un valor decimal: ");
       scanf("%d", &valor);
       printf("%d en hexadecimal es: %x", valor, valor);
     }

     if(seleccion == 2) {
       printf("dame un valor hexadecimal: ");
       scanf("%x", &valor);
       printf("%x en decimal es: %d", valor, valor);
     }

     if(seleccion == 3) {
       printf("dame un valor decimal: ");
       scanf("%d", &valor);
       printf("%d en octal es: %o", valor, valor);
     }

     if(seleccion == 4) {
       printf("dame un valor octal: ");
       scanf("%o", &valor);
       printf("%o en decimal es: %d", valor, valor);
     }
   }


La ejecución del programa bajo la ventana DOS simulada por el entorno C produce algo como lo que se muestra:




Siempre podemos asociar un else a cualquier if. Si la expresión condicional asociada con la parte if es verdadera, su blanco (objetivo) será ejecutado. Si es falsa, entonces la parte else es la que será ejecutada. El siguiente programa muestra al enunciado else en acción:


   #include <stdio.h>

   main()
   {
     int i;

     printf("dame un numero: ");
     scanf("%d", &i);

     if(i<0) printf("el numero es negativo");
     else printf("el numero es positivo o cero");
   }


Una construcción usada con mucha frecuencia es la escalera if-else-if, la cual tiene la siguiente estructura:

   if(condicion)
      enunciado;
   else if (condicion)
      enunciado;
   else if(condicion)
      enunciado;
   .
   .
   .
   else (enunciado);

Las expresiones condicionales son evaluadas desde arriba hacia abajo. En cuanto se encuentra una condición que sea verdadera, el enunciado asociado con dicha condición es ejecutado. y el resto de la escalera es ignorado. Si ninguna de las condiciones resulta verdadera, entonces el else al final será ejecutado. El enunciado else usado solo actúa como una condición de default, si fallan todas las demás pruebas condicionales entonces el enunciado final else siempre será ejecutado. Si no hay ningún else puesto al final y todas las condiciones resultan falsas, entonces no ocurrirá acción alguna como resultado del procesamiento del código.

Podemos usar una escalera if-then-else para mejorar el programa de conversiones de base dado arriba, En la versión original, cada enunciado if en sucesión es evaluado aún si hubo éxito en uno de los enunciados anteriores. Aunque no es de la mayor importancia en esta situación, la evaluación redundante de todos los if no es algo muy eficiente o muy elegante, y denota una práctica pobre de los principios de programación. El programa que sigue resuelve esta situación, ya que en la versión de escalera if-else-if en cuanto un enunciado if es exitoso todos los demás enunciados del programa son ignorados por el compilador:


   #include <stdio.h>

   main()
   {
     int seleccion;
     int valor;

     printf("Convertir:\n");
     printf("  1: decimal a hexadecimal\n");
     printf("  2: hexadecimal a decimal\n");
     printf("  3: decimal a octal\n");
     printf("  4: octal a decimal\n");
     printf("dame tu seleccion: ");
     scanf("%d", &seleccion);

     if(seleccion == 1) {
       printf("dame un valor decimal: ");
       scanf("%d", &valor);
       printf("%d en hexadecimal es: %x", valor, valor);
     }

     else if(seleccion == 2) {
       printf("dame un valor hexadecimal: ");
       scanf("%x", &valor);
       printf("%x en decimal es: %d", valor, valor);
     }

     else if(seleccion == 3) {
       printf("dame un valor decimal: ");
       scanf("%d", &valor);
       printf("%d en octal es: %o", valor, valor);
     }

     else if(seleccion == 4) {
       printf("dame un valor octal: ");
       scanf("%o", &valor);
       printf("%o en decimal es: %d", valor, valor);
     }
   }


Una diferencia entre el lenguaje C con respecto a otros lenguajes de programación como BASIC es el hecho de que cualquier expresión válida puede ser usada para controlar el enunciado if, esto es, el tipo de expresión de prueba no tiene que estar restringido a aquellos que involucran únicamente operadores relacionales y lógicos. Todo lo que se requiere es que la expresión de prueba sea evaluada a un valor cero o diferente de cero. En el siguiente programa, se leen dos enteros introducidos desde el teclado y se muestra el cociente, y para evitar un error en caso de que el usuario introduzca como divisor un cero, se utiliza un enunciado if que es controlado por el segundo número:


   #include <stdio.h>

   main()
   {
     int x, y;

     printf("dame dos numeros: ");
     scanf("%d%d", &x, &y);

     if(y) printf("%d\n", x/y);
     else printf("esta prohibida la division entre cero!\n");
   }


Este código funciona porque si y es cero entonces la condición que controla a if es falsa y se ejecuta el enunciado else. En caso contrario la condición se lleva a cabo.

Quizá una de las cosas que ocasionan más confusión en el uso de los enunciados if en cualquier lenguaje de programación es el uso de los if anidados. Un if anidado es un enunciado if que a su vez es el objetivo de otro enunciado if o de un enunciado else. La razón por la cual los if anidados suelen ser problemáticos es que puede ser difícil al leer el código de un programa cuál enunciado else está asociado con cuál enunciado if. Considérese el siguiente ejemplo:

   if(a)
      if(b) printf("prueba X");
      else printf("prueba y");

La pregunta aquí, desde luego, es: ¿a cuál de los dos if se refiere el enunciado else? Esta es una de las razones por las cuales en cierto momento se vuelve casi una necesidad el uso de la indentación al escribir programas, recorriendo porciones lógicas de código varios espacios a la derecha. En ausencia de dicha práctica, y sobre todo en programas C muy grandes que consten de varias miles de líneas de código, puede ser una tarea casi imposible leer y entender un programa inclusive para el que lo escribió. Obviamente, el siguiente código en el cual el else va aparejado con el segundo if:

   if ( numero > 6 )
      if ( numero < 12)
         printf("Te estas acercando a la respuesta\n");
      else
         printf("Perdiste tu turno\n");

es más fácil de leer que la versión del mismo no-indentada aunque ambos se ejecuten exactamente de la misma manera:

   if ( numero > 6 )
   if ( numero < 12)
   printf("Te estas acercando a la respuesta\n");
   else
   printf("Perdiste tu turno\n");

Y si la indentación se usa en forma incorrecta, puede confundir al propio programador haciéndole creer que el else va aparejado al primer if:

   if ( numero > 6 )
      if ( numero < 12)
         printf("Te estas acercando a la respuesta\n");
   else
      printf("Perdiste tu turno\n");

De cualquier manera, hay una regla muy sencilla para responder a la pregunta sobre a cuál de varios if le corresponde un else posterior. En C, else siempre va aparejado al if más cercano dentro del mismo bloque de código que no tenga ya un enunciado else asociado al mismo, o a a menos de que se pongan corchetes que indiquen lo contrario. Aplicando la regla al ejemplo:

   if(a)
      if(b) printf("prueba X");
      else printf("prueba y");

podemos responder que el else va aparejado al segundo if. Si queremos que el else esté asociado al primer if(a) y no al segundo, hay que poner corchetes de la manera en que se muestra para hacer de lado la asociación normal:

   if(a) {
      if(b) printf("prueba X");
   }
      else printf("prueba y");

De este modo, el else está asociado ahora con el if(a) porque ya no forma parte del bloque de código if(b).

En C existe una forma abreviada de implementar una forma del enunciado if-else, la cual usa el operador condicional:

?:

Se trata de un operador curioso que requiere de tres operandos. He aquí un ejemplo de cómo se emplea dicho operador:

x = ( A < 0) ? - A : A;

Todo lo que hay entre el signo de asignación (igualdad) y el semicolon es la expresión condicional. El significado de la expresión es éste: Si A es menor que cero, entonces invertir el signo de A haciendo con ello x.=.A, y si A es mayor que cero entonces hacer x.=.A (el efecto es asignar el valor absoluto de A a x removiendo el signo menos en caso de que lo tenga). En términos de if-else:

   if (A < 0)
      x = -A;
   else
      x = A;

La forma general de la expresión condicional es:

expresion 1  ?  expresión 2  :  expresion 3

Si la expresion 1 es verdadera, entonces toda la expresión condicional tiene el mismo valor que la expresion 2. Y si la expresion 1 es falsa, entonces toda la expresión condicional tiene el mismo valor que la expresion 3. De este modo, en las siguientes expresiones:

( 5 > 3 ) ? 1 : 2

( 3 > 5 ) ? 1 : 2

la primera tomará el valor 1 y la segunda tomará el valor 2.

Podemos usar la expresión condicional cuando tenemos una variable a la cual se le pueden asignar dos valores posibles. Además de usarse para obtener el valor absoluto de un número, otro ejemplo consiste en hacer que una variable tome el máximo de dos valores posibles:

max = (A > B) ? A : B;

Estas peculiaridades y detalles adicionales como el uso de operador ++ usado para operaciones de incremento son precisamente lo que puede hacer que un programa elaborado en C pueda resultar difícil de leer.

Aunque la escalera if-else-if puede llevar a cabo tareas múltiples, no se trata de algo muy elegante. El código puede ser difícil de seguir y puede confundir incluso al mismo autor en un tiempo posterior. Por estas razones encontramos en C otro enunciado usado en una decisión de ramificación múltiple llamado switch. Con este enunciado, una variable es probada sucesivamente contra una lista de enteros o constantes de caracter. Cuando se encuentra un apareamiento, se ejecuta el enunciado o secuencia asociado con esa constante. Las constantes no tienen que estar puestas en ningún orden en particular. La sintaxis de la forma general del enunciado switch es:

   switch(variable) {
      case constante 1:
         instrucciones
         break;
      case constante 2:
         instrucciones
         break;
      case constante 3:
         instrucciones
         break;
      .
      .
      .
      default:
         instrucciones
      }

en donde el enunciado default es ejecutado si no se encuentra apareamiento alguno. El enunciado default es optativo, y si no está presente entonces no ocurre acción alguna si no se encuentra apareamiento alguno. Cuando se encuentra un apareamiento, el código asociado con ese case es ejecutado hasta que se llega al enunciado break o, en el caso de default (o el último case si no hay un enunciado default presente) se llega al final del enunciado switch. El enunciado switch es comparable al enunciado ON-GOTO de BASIC o el enunciado CASE de Pascal.

El enunciado switch difiere del enunciado if en que switch únicamente puede probar condiciones de igualdad  mientras que la expresión condicional de un if puede ser de cualquier tipo. No puede haber dos constantes case en el mismo bloque switch con valores idénticos.

Con el enunciado switch podemos implementar la siguiente mejora en el programa conversor de bases numéricas que se ha dado arriba (el resultado final, desde el punto de vista del usuario, es exactamente el mismo y no podrá distinguir diferencia alguna):


   #include <stdio.h>

   main()
   {
     int seleccion;
     int valor;

     printf("Convertir:\n");
     printf("  1: decimal a hexadecimal\n");
     printf("  2: hexadecimal a decimal\n");
     printf("  3: decimal a octal\n");
     printf("  4: octal a decimal\n");
     printf("dame tu seleccion: ");
     scanf("%d", &seleccion);

     switch(seleccion) {
        case 1:
           printf("dame un valor decimal: ");
          scanf("%d", &valor);
          printf("%d en hexadecimal es: %x", valor, valor);
          break;
        case 2:
           printf("dame un valor hexadecimal: ");
           scanf("%x", &valor);
           printf("%x en decimal es: %d", valor, valor);
           break;
        case 3:
           printf("dame un valor decimal: ");
           scanf("%d", &valor);
           printf("%d en octal es: %o", valor, valor);
           break;
        case 4:
           printf("dame un valor octal: ");
           scanf("%o", &valor);
           printf("%o en decimal es: %d", valor, valor);
           break;
      }
   }


El programa anterior no incluye un enunciado default. Se lo podemos agregar para manejar el caso en el cual el usuario no haga una selección válida de las cuatro opciones del menú:


   #include <stdio.h>

   main()
   {
     int seleccion;
     int valor;

     printf("Convertir:\n");
     printf("  1: decimal a hexadecimal\n");
     printf("  2: hexadecimal a decimal\n");
     printf("  3: decimal a octal\n");
     printf("  4: octal a decimal\n");
     printf("dame tu seleccion: ");
     scanf("%d", &seleccion);

     switch(seleccion) {
        case 1:
           printf("dame un valor decimal: ");
          scanf("%d", &valor);
          printf("%d en hexadecimal es: %x", valor, valor);
          break;
        case 2:
           printf("dame un valor hexadecimal: ");
           scanf("%x", &valor);
           printf("%x en decimal es: %d", valor, valor);
           break;
        case 3:
           printf("dame un valor decimal: ");
           scanf("%d", &valor);
           printf("%d en octal es: %o", valor, valor);
           break;
        case 4:
           printf("dame un valor octal: ");
           scanf("%o", &valor);
           printf("%o en decimal es: %d", valor, valor);
           break;
        default:
          printf("seleccion no valida, intenta de nuevo\n");
          break;  
      }
   }


Aunque por sintaxis los enunciados break son generalmente requeridos dentro de un switch, son optativos. Generalmente son usados para dar por terminada la secuencia de enunciados asociada con cada constante. Sin embargo, si se omite el enunciado break la ejecución continuará hacia los siguientes enunciados case hasta encontrar un break o el final del bloque switch. Podemos visualizar cada case como si fuera una etiqueta; la ejecución empezará en la etiqueta y continuará hasta que se encuentre un enunciado break o termine el bloque switch. Esto lo podemos ver en el siguiente programa:


   #include <stdio.h>

   main()
   {
     int t;

     for(t=0; t<10; t++)
       switch(t) {
         case 1:
  printf("La ociosidad");
  break;
         case 2:
  printf(" es ");
         case 3:
  printf("la madre");
  printf(" de todos los vicios\n");
  break;
         case 5:
         case 6:
  printf("si ");
  break;
         case 7:
         case 8:
         case 9:
  printf(".");
     }

   }


Al ejecutarse el programa, se imprime lo siguiente:

   La ociosidad es la madre de todos los vicios
   la madre de todos los vicios
   si si ...

El programa nos muestra que podemos tener enunciados case vacíos (sin contener código alguno). Esto puede ser útil cuando varias condiciones utilizan la misma secuencia de enunciados. La posibilidad de tener varios case que corran juntos permite escribir programas eficienes evitando una duplicidad de código.

Así como podemos llevar a cabo anidamiento de enunciados if, también podemos llevar a cabo anidamiento de enunciados switch, como se muestra a continuación:

   switch(a) {
      case 1:
         switch(b) {
            case 0:
               printf("se ha elevado al cubo");
               break;
            case 1:
               mul(f,g);
         }
         break;
      case 2:
      .
      .
      .

El siguiente programa nos muestra un ejemplo de un enunciado switch anidado, el cual pudiera ser una pequeña base de datos construída por un químico nuclear para su uso personal:


   #include <stdio.h>
   #include <conio.h>
   #include <ctype.h>

   main()
   {
     char elemento, isotopo;

     printf("Los elementos son: cesio, uranio, y plutonio\n");
     printf("Pon la primera letra del elemento: ");
     elemento = getche();
     elemento = toupper(elemento); /* convertir a mayuscula */
     printf("\n");

     switch(elemento) {
       case 'C':
         printf("Los isotopos son: a, b y c\n");
         printf("Pon la letra del isotopo: ");
         isotopo = toupper(getche());
         printf("\n");

         switch(isotopo) {
            case 'A': printf("Reactividad: %f\n", 10.5);
               break;
            case 'B': printf("Reactividad: %f\n", 12.8);
               break;
            case 'C': printf("Reactividad: %f\n", 14.3);
               break;
         }
         break;

       case 'U':
         printf("Los isotopos son: a, b y c\n");
         printf("Pon la letra del isotopo: ");
         isotopo = toupper(getche());
         printf("\n");

         switch(isotopo) {
   case 'A': printf("Reactividad: %f\n", 10.9);
            break;
            case 'B': printf("Reactividad: %f\n", 9.6);
            break;
            case 'C': printf("Reactividad: %f\n", 12.8);
            break;
       }
       break;

       case 'P':
         printf("Los isotopos son: a, b y c\n");
         printf("Pon la primera letra del isotopo: ");
         isotopo = toupper(getche());
         printf("\n");

         switch(isotopo) {
            case 'A': printf("Reactividad: %f\n", 6.7);
            break;
            case 'B': printf("Reactividad: %f\n", 8.4);
            break;
            case 'C': printf("Reactividad: %f\n", 11.5);
            break;
         }
         break;
     }
   }


Obsérvese cómo el uso de las indentaciones facilita la visualización de los bloques de código permitieneo una mejor interpretación del propósito del programa que es en realidad una pequeña base de datos. En el programa hemos introducido la función de biblioteca toupper() para convertir un caracter en minúscula a mayúscula en caso de que sea una minúscula, y que permite que el usuario pueda escribir la letra que se le pide ya sea en minúscula o mayúscula, para cuyo uso se requiere incluír al principio del programa el archivo de cabecera CTYPE.H; agregándose también que en dicho archivo de cabecera hay otra función de biblioteca usada para convertir de mayúscula a minúscula, la función tolower(). Se requiere también incluír el archivo de cabecera CONIO.H (Console Input Output, en alusión a que anteriormente las terminales con una pantalla y un teclado eran conocidas también como consolas) requerido por usarse la función de biblioteca getche().

Habiendo cubierto el tema de las estructuras de decisión, ahora cubriremos el tema de los bucles en el lenguaje C. Los bucles son un conjunto de instrucciones que son repetidas hasta que se cumple cierta condición, y C dá soporte al mismo tipo de bucles usados en otros lenguajes estructurados. Los bucles en C son for, while y do-while.

La sintaxis de la forma general del bucle for es:

for(inicializacion; condicion; incremento)  enunciado;

La inicializacion es un enunciado de asignación usado para fijar un valor de inicio para la variable de control del bucle. La condicion es por lo general una expresión relacional que determinará efectuando una prueba sobre la varible de control cuándo dejará de ejecutarse el bucle. Y el incremento es lo que define cómo cambiará la variable de control cada vez que se repite el bucle. Obsérvese que estas tres secciones del bucle for tienen que estar separadas por semicolons. El bucle for se continuará ejecutando entanto que la condición sea verdadera; una vez que la condición se vuelve falsa el bucle se dá por terminado y la ejecución del programa continúa con el enunciado que sigue al for.

Como un ejemplo sencillo del bucle for, tenemos el siguiente programa que imprime los números enteros del 1 al 20:


   #include <stdio.h>

   main()
   {
     int x;

     for(x=1; x<=20; x++) printf("%d ", x);
   }


En el programa, x es puesta inicialmente a 1. Puesto que x es menor que 20, la función printf() será invocada. Después de que se ejecuta la impresión, x es incrementada en una unidad con el operador de incremento. Este proceso continúa repitiéndose hasta que x es mayor que 20, y al ocurrir esto la ejecución del bucle llega a su fin. En este ejemplo, x es la variable de control del bucle que es cambiada y checada cada vez que se repite el bucle.

No es requisito indispensable que un bucle for vaya siempre en una dirección ascendente. Podemos crear un bucle que va en una dirección descendente usando una operación de decremento en lugar de incrementar la variable de control del bucle. Un ejemplo de ello es el siguiente programa que imprime en la pantalla los enteros desde el 20 hasta el 1:


   #include <stdio.h>

   main()
   {
     int x;

     for(x=20; x>0; x--) printf("%d ", x);
   }


Tampoco es requisito indispensable incrementar cada vez en una  unidad la variable de control del bucle. Podemos cambiar la variable de control del bucle de la manera que queramos. Como ejemplo se presenta el siguiente programa que imprime los enteros que van desde el 0 hasta el 40, aumentando de cinco en cinco:


   #include <stdio.h>

   main()
   {
     int x;

     for(x=0; x<=40; x=x+5) printf("%d ", x);
   }


Usando un bloque de código con la ayuda de los corchetes, podemos hacer que con el bucle for se ejecuten varios enunciados en lugar de uno solo, como en el siguiente programa que imprime en la pantalla los enteros del 0 al 19 así como el cuadrado de ellos:


   #include <stdio.h>

   main()
   {
     int i;

     for(i=0; i<20; i++) {
       printf("para el numero %d", i);
       printf(" su cuadrado es: %d\n", i*i);
     }
   }


Algo a tener en cuenta en el uso de los bucles for es que la prueba condicional siempre es ejecutada en el tope del bucle. Esto implica que el código al interior del bucle jamás será ejecutado si desde un inicio la condición es falsa; por ejemplo:

   a = 5;

   for(x=5; x!=a; ++x) print("%d", x);

El bucle jamás se ejecutará porque x y a de hecho son iguales al ingresar al bucle. Esto ocasiona que la expresión condicional sea evaluada a un valor falso, y por lo tanto ni el cuerpo de bucle ni la parte de incremento a la variable de control serán ejecutados, y por lo tanto no se imprimirá nada en la pantalla.

En los ejemplos dados arriba, se ha supuesto que solo hay una variable de control. Sin embargo, C permite que haya no solo una sino dos o más variables de control. He aquí un ejemplo de un programa que usa dos variables de control que llamaremos P y Q:


   #include <stdio.h>

   main()
   {
      int P, Q;

     for(P=0, Q=0; P+Q<50; ++P, Q++)
     printf("%d ", P+Q);

   }


El programa imprime los números cero al 48 yendo de dos en dos. Obsérvese que se han usado dos comas para separar los enunciados de inicialización e incremento. La coma en C es de hecho un operador que dice “haz esto y esto otro”. Cada vez que se repite el bucle, tanto P como Q son incrementadas y ambas (sumadas) tienen que tener el valor correcto para que la ejecución del bucle concluya.

Otra muestra de la versatilidad de C es que la expresión condicional usada en los bucles no necesariamente tiene que involucrar el probar la variable de control contra cierto valor en particular (por ejemplo, 10); de hecho la condición puede ser cualquier expresión que sea válida en C, lo cual permite probar por varias condiciones posibles de terminación del bucle. En el siguiente programa que puede ser usado por los niños para el aprendizaje de las operaciones de suma, se van presentando en grupos de diez en diez varias sumas ascendentes, y al final de cada grupo la computadora le pregunta al niño si quiere continuar; si el niño quiere continuar entonces simplemente oprime la tecla [Enter], y si el niño está cansado y quiere detenerse entonces solo tiene que teclear “n” (en minúscula) cuando se le pregunta al final de cada grupo de diez preguntas si quiere más prácticas. Póngase atención especial en la parte condicional del bucle for. La parte condicional hace que el bucle for sea ejecutado hasta 99 veces o hasta que el niño responde que no.


   #include <stdio.h>
   #include <conio.h>

   main()
   {
     int i, j, respuesta;
     char terminado = ' ';

     for(i=1; i<100 && terminado!='n'; i++) {
        for(j=1; j<10; j++) {
           printf("Cuantos son %d + %d? ", i, j);
           scanf("%d", &respuesta);
           if(respuesta != i+j) printf("equivocado\n");
           else printf("correcto\n");
         }
         printf("quieres practicar mas? ");
         terminado = getche();
         printf("\n");
     }
   }


De hecho, cada una de las tres secciones del enunciado for pueden consistir de expresiones C válidas, lo cual permite ampliar enormemente las posibilidades, las cuales no necesariamente tienen algo que ver con el propósito con el que sea utilizada cada sección. El siguiente programa proporciona una muestra de ello (en el programa se definen tres funciones que serán usadas por la función principal main(), y para que el código sea compilable en todos los compiladores C se ha puesto la función principal no al comienzo del programa sino al final, con lo cual las tres funciones que usa main() quedan declaradas y definidas antes de llegar a la función principal):


   #include <stdio.h>

   inquirir()
   {
     printf("dame un entero: ");
   }

   leernumero()
   {
     int t;

     scanf("%d", &t);
     return t;
   }

   cuadrado(int num)
   {
     printf("%d\n", num*num);
   }

   main()
   {
     int t;

     for(inquirir(); t=leernumero(); inquirir())
        cuadrado(t);
   }


El programa primero pone en la pantalla una interrogación y espera a que el usuario escriba un número. Cuando el número es proporcionado, se muestra su cuadrado y nuevamente se le pide al usuario que proporcione otro número. Esto continúa hasta se introduce un cero. Si examinamos de cerca los argumentos usados por el bucle for invocados en main(), veremos que cada parte del bucle for consta de llamadas de función que le piden un dato al usuario y leen un número del teclado. Si el número proporcionado por el usuario es cero, el bucle termina porque la expresión condicional será falsa; de lo contrario el número es elevado al cuadrado. De este modo, en este bucle for las partes que corresponden a la inicialización son utilizadas de una manera nada tradicional pero perfectamente válida.

Otra característica sorprendente e inusual en un bucle for es que no es necesario que todas las partes usadas en la definición del bucle tengan que estar allí. El siguiente bucle se estará ejecutando hasta que se le de entrada desde el teclado a un número 8:

for(A=0; A!=8;  ) scanf("%d", &A);

Obsérvese que la parte del incremento en la definición de for está en blanco. Esto significa que cada vez que el bucle se repite, A es probada para ver si es igual a 8, pero A no es cambiada en lo absoluto. Sin embargo, si se introduce 8 desde el teclado, la condición del bucle se vuelve falsa y la ejecución del bucle termina.

De hecho (y esto puede resultar más sorprendente) no es necesario que haya una expresión en ninguna de las tres secciones, con lo cual se vuelve posible la creación de un bucle perpetuo al dejar vacía la expresión condicional, como se muestra en el siguiente ejemplo:

for(;;) printf("Este es un bucle perpetuo\n")

¿Hay alguna manera de poder escapar de un bucle infinito como éste? Si la hay, y para este tipo de situaciones se utiliza el enunciado break. Cuando se encuentra en cualquier lugar dentro de un programa, break ocasiona una terminación inmediata en la ejecución del bucle infinito, y el programa continúa a partir del código que sigue inmediatamente al bucle infinito, como se muestra en este ejemplo:

   for(;;) {
      car = getche();  /* obtener un caracter del teclado */
      if(x=='M')  break   /* romper el bucle infinito */
   }
   printf("tecleaste una M");

Otra cosa que puede parecer sorprendente es que, de acuerdo a la sintaxis de C, el cuerpo de un bucle for puede estar vacío, lo cual puede ser usado para crear retardos de tiempo así como para mejorar la eficiencia de algunos algoritmos. Esta es la manera en la cual creamos un retardo de tiempo usando un bucle for:

for(t=0; t<LAPSO; t++)   ;

Habiendo cubierto los detalles principales del bucle for, pasamos al estudio del segundo tipo de bucle, el bucle while cuya sintaxis es:

while(condición)  enunciado;

en donde enunciado puede ser un enunciado vacío, un enunciado sencillo, o un bloque de enunciados que serán repetidos. La condición puede ser cualquier expresión válida. El bucle se repite mientras que (while) la condicion sea verdadera; cuando la condición se vuelve falsa, se sale del bucle y se pasa a la ejecución de la siguiente línea de código que sigue al bucle.

En el siguiente ejemplo el bucle se ejecuta indefinidamente hasta que el usuario oprime una tecla M en el teclado:

   esperar_tecla()
   {
      char caracter;

      caracter = '\0';  /* inicializar caracter */
      while(caracter!='M')  caracter = getche();
   }

Primero caracter es inicializado a un valor nulo. Como una variable local, su valor no es conocido cuando esperar_tecla() se ejecuta. El bucle while entonces empieza a checar si caracter no es igual a la tecla M. Puesto que caracter fue previamente inicializado a un valor nulo, la prueba es verdadera y comienza la ejecución del bucle. Cada vez que se oprime una tecla en el teclado, la prueba se lleva a cabo nuevamente. Cuando se oprime la tecla M la condición se vuelve falsa porque caracter es igual a M, y el bucle concluye.

Al igual que como ocurre con el bucle for, while checa la condición de prueba a partir del tope del bucle, lo cual implica que es posible que el bucle no se llege a ejecutar jamás. Esta es la razón por la cual en el ejemplo que se acaba de dar caracter tuvo que ser inicializado para evitar que pueda contener accidentalmente el caracter M (la palabra binaria en bytes que representa una variable en la memoria RAM y que es una variable no inicializada puede contener cualquier valor). Puesto que la condición de prueba es ejecutada al principio (en el tope del bucle), el bucle while es útil en situaciones en las que no queremos que el bucle se ejecute, lo cual elimina la necesidad de una prueba condicional separada antes de que se ejecute el bucle.

En el siguiente ejemplo, la función centrar() utiliza un bucle while para imprimir el número correcto de espacios en blanco necesarios para centrar una línea de texto en una pantalla monocromática con capacidad de 80 columnas. Si longitud es igual a cero, como lo sería en el caso de una línea a ser centrada que consta de 80 caracteres, el bucle no se ejecutará. Este programa utiliza la función de biblioteca strlen() proporcionada en el archivo de cabecera para el manejo de hileras STRING.H:


   #include <stdio.h>
   #include <string.h>

   main()
   {
     char hilera[255];
     int longitud;

     printf("dame una hilera: ");
     gets(hilera);

     centrar(strlen(hilera));
     printf(hilera);
   }

   /* Calcular y poner el numero apropiado
      de espacios en blanco para centrar
      una hilera de cierta longitud */

   centrar(int longitud)
   {
     longitud = (80-longitud)/2;

     while(longitud>0) {
       printf(" ");
       longitud--;
     }
   }


En donde haya varias condiciones separadas para terminar un bucle while, es común usar una sola variable como la expresión condicional, con el valor de la variable siendo fijado en varios puntos a lo largo del bucle, de lo cual se dá el siguiente ejemplo:

   funcion1 ()
   {
      int prueba;

      prueba = 1;   /* valor verdadero */

      while(prueba) {
         prueba = proceso1();
         if(prueba)
            prueba = proceso2();
         if(prueba)
            prueba = proceso3();
      }
   }

Aquí cualquiera de las tres rutinas puede regresar un valor falso causando con ello la terminación del bucle.

Al igual que como ocurre en el bucle for, no es necesario que haya enunciado alguno en el cuerpo de un bucle while, como en el siguiente caso:

while( (caracter = getche() ) != 'X')   ;

en donde el bucle se repetirá indefinidamente hasta que se oprima la tecla X (mayúscula).

A diferencia de los bucles for y while en los cuales la condición de prueba es checada desde el principio (en el tope del bucle), el bucle do-while checa la condición de prueba al final (al fondo) del bucle. Esto significa que un bucle do-while siempre se ejecutará por lo menos una vez. La sintaxis para la forma general de este bucle es:

   do {

      código

   } while(condicion)

Aunque los corchetes no son necesarios cuando únicamente hay un solo enunciado presente, las buenas prácticas de programación recomiendan su uso para aumentar la legibilidad de un programa y disminuír la confusión que se pueda causar al programador,

Como ejemplo de este tipo de bucle, se tiene el siguiente programa que lee los números introducidos desde el teclado hasta que uno de ellos sea menor o igual a 80:


   #include <stdio.h>

   main()
   {
     int numero;

      do {
       scanf("%d", &numero);
     } while(numero>80);
   }


En el programa dado anteriormente (arriba) para convertir números de una base a otra, una deficiencia de dicho programa es que si el usuario comete una equivocación en su selección del menú de cuatro opciones como escoger el número 0 o el número 5 como opción, el programa termina sin darle al usuario oportunidad para intentar escoger nuevamente una opción válida. Esto lo podemos corregir cambiando el código con la adición del bucle mostrado:

   do {
     printf("Convertir:\n");
     printf("  1: decimal a hexadecimal\n");
     printf("  2: hexadecimal a decimal\n");
     printf("  3: decimal a octal\n");
     printf("  4: octal a decimal\n");
     printf("dame tu seleccion: ");
     scanf("%d", &seleccion);
   } while (seleccion<1 || seleccion >4);

Algo que puede ocasionar confusiones si no se usa una redacción adecuada al elaborar un programa son los bucles anidados. Hablamos de un bucle anidado cuando dicho bucle se encuentra situado dentro de otro bucle. Los bucles anidados ofrecen una manera elegante de implementar algoritmos y de resolver algunos problemas interesantes de programación. Con la ayuda de bucles anidados for, el siguiente programa produce las primeras cuatro potencias de los números 1 al 8:


   #include <stdio.h>

   main()
   {
     int i, j, k, temp;

     printf("        i      i^2      i^3      i^4\n");
     for(i=1; i<=8; i++) {   /* bucle externo */
       for(j=1; j<5; j++) {
         temp = 1;
         for(k=0; k<j; k++)    /* bucle interno */
            temp = temp*i;
            printf("%9d", temp);
       }
       printf("\n");
     }
   }


Este programa produce la siguiente salida:




El uso de bucles anidados do-while nos permite mejorar el programa conversor de bases numéricas de manera tal que un bucle externo haga que el programa se continúe ejecutando hasta que el usuario le diga que pare, mientras que un bucle interno asegura que el usuario intoduzca una opción válida:


   #include <stdio.h>

   main()
   {
     int seleccion;
     int valor;

     do {

      do {
        printf("Convertir:\n");
        printf("  1: decimal a hexadecimal\n");
        printf("  2: hexadecimal a decimal\n");
        printf("  3: decimal a octal\n");
        printf("  4: octal a decimal\n");
        printf("  5: salir del conversor\n");
        printf("dame tu seleccion: ");
        scanf("%d", &seleccion);
      } while (seleccion<1 || seleccion >5);

      switch(seleccion) {
        case 1:
           printf("dame un valor decimal: ");
          scanf("%d", &valor);
          printf("%d en hexadecimal es: %x", valor, valor);
          break;
        case 2:
           printf("dame un valor hexadecimal: ");
           scanf("%x", &valor);
           printf("%x en decimal es: %d", valor, valor);
           break;
        case 3:
           printf("dame un valor decimal: ");
           scanf("%d", &valor);
           printf("%d en octal es: %o", valor, valor);
           break;
        case 4:
           printf("dame un valor octal: ");
           scanf("%o", &valor);
           printf("%o en decimal es: %d", valor, valor);
           break;  
      }

      printf("\n");

     } while(seleccion !=5);

   }


Además de poder usar el enunciado break para terminar un case en un enunciado switch como vimos arriba, podemos usarlo también para romper la ejecución de un bucle, pasando por encima de la prueba condicional del bucle. Cuando esto ocurre, al ser encontrado un break el flujo del programa continúa con la instrucción que sigue a la orden de salida, como en el siguiente programa que imprime los enteros del 1 al 10 hasta ser terminado por break:


   #include <stdio.h>

   main()
   {
     int t;

     for(t=0; t<30; t++) {
       printf("%d ", t);
       if(t==10) break;
     }
   }


Como puede verse, pese a que t<30 es la condición de terminación del bucle, el break se impone a la condición de terminación del bucle.

En el siguiente programa usado para evaluar el sentido del tiempo del usuario, se usará una función llamada time() que se encuentra en el archivo de cabecera TIME.H, además de la función kbhit() (keyboard hit) que se encuentra en el archivo de cabecera CONIO.H.


   #include <stdio.h>
   #include <time.h>
   #include <conio.h>

   main()
   {
     long tiempo;

     printf("Programa para probar el sentido del tiempo.\n");
     printf("Al estar listo, oprimir [Enter], contar 5 segundos\n");
     printf("y tocar cualquier tecla: ");
     getche();
     printf("\n");

     tiempo = time(0);
     for(;;)
       if(kbhit()) break;
     if(time(0)-tiempo==5) printf("Ganaste");
     else printf("Estas fuera de tiempo");
   }


Si al echar a andar el programa, después de oprimir la tecla [Enter] el usuario golpea otra tecla en un tiempo menor o mayor a los diez segundos, el programa le indicará al usuario que está fuera de tiempo, pero si la golpea en un tiempo de cinco segundos el programa le dirá que ganó. En el uso de time(), se le proporciona un 0 como argumento para hacer que la función regrese el tiempo transcurrido. ¿Y cómo sabemos tal cosa? Consultando los manuales, o el archivo de cabecera TIME.H.

Hay que tomar en cuenta que break ocasionará una salida únicamente del bucle más interior. En el siguiente fragmento que imprime los números 1 al 20 en la pantalla treinta veces, cada vez que se encuentre a break el control del programa será regresado al bucle for más externo:

   for(a=100; a<30; ++a) {
      conteo = 1;
      for(;;;) {
         printf("%d ", conteo);
         conteo++;
         if(conteo==20) break;
      }
   }

El enunciado break tiene una contraparte que es el enunciado continue, al cual es similar excepto que éste último en lugar de forzar una terminación del programa lo que hace es forzar que ocurra la siguiente iteración del bucle, pasando por alto cualquier código intermedio. El siguiente programa imprime los números pares de; 0 al 100:


   #include <stdio.h>

   main()
   {
     int a;

     for(a=0; a<=100; a++) {
       if(a%2) continue;
       printf("%d ", a);
     }
   }


En este programa, cada vez que se genera un número impar el enunciado if se ejecuta porque un número impar módulo 2 (obsérvese que se lleva a cabo la división modular) siempre es igual a 1 que es verdadero, de modo tal que un número impar hace que continue ejecute la siguiente iteración pasando por encima del enunciado printf().

Mientras que en el caso de los bucles while y do-while un enunciado continue hace que el control regrese directamente a la prueba condicional del bucle para que se continúe el proceso de ciclaje, en el caso del bucle for primero se ejecuta la parte de incremento del bucle, tras lo cual se lleva a cabo la prueba condicional con lo cual se continúa con el proceso del bucle.

A continuación veremos un programa sencillo de encriptación con el cual conforme se va escribiendo texto dentro de la ventana los caracteres del alfabeto son desplazados en una posición (la “a” es convertida en una “b”, la “e” es convertida en una “f”, y así sucesivamente), pudiéndose terminar el programa en cualquier momento oprimiendo la tecla de numeral “#” (también conocida como gato). El programa nos muestra cómo se puede usar continue para expeditar la terminación de un bucle forzando que la condición de prueba se lleve a cabo en cuanto se encuentra una condición para la terminación del programa:


   #include <stdio.h>
   #include <conio.h>

   main()
   {
     printf("Entra las teclas que quieras codificar.\n");
     printf("Teclea un # cuando hayas terminado.\n");

     codificar();
   }

   codificar()
   {
     char hecho, ch;

     hecho = 0;
     while(!hecho) {
        ch = getch();
        if(ch=='#') {
         hecho = 1;
         continue;
       }
       printf("%c", ch+1);

     }
   }


Uno de los grandes triunfos del concepto de la programación estructurada introducida por Nicklaus Wirth - wikipedia (implementado en el lenguaje estructurado que inventó, el lenguaje Pascal) es que el enunciado GOTO usado (y abusado) en lenguajes como FORTRAN y BASIC no solo no es indispensable en un lenguaje estructurado; ni siquiera es necesario, y hasta la fecha nadie ha propuesto un algoritmo ni ha podido escribir un programa en C en donde el enunciado <b>goto</b> no pueda ser eliminado por completo. Elevaremos este hecho a un postulado que tendrá que ser aceptado sin demostración.

De cualquier manera, C pone a disposición de los programadores el enunciado goto, el cual para poder ser usado requiere usar una etiqueta que se define en C como cualquier identificador que es seguido por un colon (:). El nombre que se le dá a la etiqueta sigue las mismas convenciones que las que se usan para darle un nombre a una variable. Un ejemplo de ello es:

goto manejador_error;

Para que esto pueda funcionar, se requiere de otro enunciado que tenga la etiqueta manejador_error. Esto se logra empezando el enunciado con el nombre de la etiqueta seguido de un colon.

Veamos algunas maneras en las cuales es posible eliminar el uso de goto en un programa C.

(1) Una situación es aquella en la cual tenemos una situación if que requiere más de un enunciado:

   if ( size > 98 )
      goto a;
   goto b;

   a: cost = cost * 1.07;
      flag = 2;
   b: bill = cost * flag;

El enunciado goto se puede eliminar de la siguiente manera:

   if (size > 98)
   {
      cost = cost * 1.07;
      flag = 2;
   }
   bill = cost * flag

(2) En el caso en que se use un goto para seleccionar entre dos alternativas:

   if ( ibex > 14 )
      goto a;
   sheds = 2;
   goto b;

   a: sheds = 3;
   b: help = 2*sheds;

el enunciado goto se puede eliminar usando una estructura if-else:

   if ( ibex > 14 )
   {
      sheds = 3;
   else
      sheds = 2;
   help = 2 * sheds;

(3) Para el montaje de un bucle perpetuo:

   readin: scanf("%d",&score);
   if ( score < 0 )
      goto stage2;
   enunciados;
   goto readin;
   stage2: masenunciados;

se puede usar un bucle while:

   scanf("%d",&score);
   while( score >= 0 )
      {
      <i>enunciados</i>;
      scanf("%d",&score);
      }
   <i>masenunciados</i>;

(4) Para saltar hacia el final de un bucle, podemos usar un enunciado continue.

(5) Para salir de un bucle: usar break en lugar de goto. De hecho, break y continue son una forma especializada de goto. Sus ventajas son que sus nombres dicen lo que debe ocurrir, y puesto que no usan etiquetas, no hay riesgo alguno de poner una etiqueta en el lugar equivocado.

Un buen ejemplo del uso esporádico de goto es para proporcionar una vía de escape de una rutina profundamente anidada. Considérese el siguiente ejemplo:

   for(...) {
      for(...) {
         while(...) {
            if(...) goto alto;
            .
            .
            .
         }
      }
   }

   alto:
      printf("Ocurrio un error en el programa");

Sin el goto tendríamos que efectuar un número adicional de pruebas, y aquí no funciona un simple enunciado break porque solo nos hace salir del bucle más interno. El código tomaría la siguiente forma:

   terminado = 0;

   for(...) {
      for(...) {
         while(...) {
              if(...) {
                terminado = 1;
                break;
                }
                .
                .
                .
             }
             if (terminado)  break;
         }
         if (terminado) break;    
      }
   }
   }

La regla general es que goto debe ser usado solo en casos extremos que realmente ameriten su uso, sólo cuando en su ausencia el código se vuelva más difícil de leer o si la velocidad de ejecución del programa es un asunto crítico.