Guión 2: Estructuras de control
 
Expresiones y sentencias.

Expresión: Combinación de operandos mediante operadores. La expresión más simple es un operando aislado (una constante o una variable.

La asignación también posee un valor, que es el que adquiere la variable a la izquierda del signo igual. Así se pueden realizar asignaciones múltiples.

Ejemplo 2.1. Realizar un programa que asigne el número diez a tres variables enteras y después imprima sus valores.

#include <stdio.h>

int main() {

  int a, b, c;

  a = b = c = 10; /* las tres variables valen lo mismo */

  printf("a: %d, b: %d, c: %d.\n", a, b, c);

  system("pause");
  return 0;
}

Ejercicio: Modificar el programa anterior para que lea por teclado el valor de una variable adicional y posteriormente asigne el valor de dicha variable adicional a las tres variables anteriores ("a", "b" y "c") mediante una asignación múltiple.

 Un programa es un conjunto de sentencias. En C cada sentencia debe finalizar con un punto y coma.

 Una sentencia compuesta (bloque) es un conjunto de sentencias encerrado entre llaves.


La sentencia if.
Permite elegir la acción o acciones a realizar en función del valor de verdad de una expresión.
if (<expresión>)
   <sentencia>
else
      <sentencia>
Ejemplo 2.2. Realizar un programa que imprima por pantalla el valor absoluto de un entero pedido por el teclado.

#include <stdio.h>

int main() {

  int x, y;

  printf("Escribe un entero: ");
  scanf("%d", &x);

  if (x >= 0)
     y = x;
  else
     y = -x;

  printf("\nValor absoluto: %d.\n", y);
 
  system("pause");
  return 0;
}

Ejercicio: Modificar el programa anterior para que lea por teclado dos enteros y posteriormente imprima en pantalla el máximo de ambos.

Pueden escribirse sentencias anidadas, donde cada sentencia else es, a su vez, otra sentencia if-else.

Ejemplo 2.3. Realizar un programa que lea un número a través del teclado e indique si dicho número es positivo, negativo o igual a cero.
#include <stdio.h>

int main(){

  int x;

  printf("Escribe un entero:");
  scanf("%d", &x);
  putchar('\n'); /* Imprime un carácter de salto de línea */

  if (x != 0) {
     if (x > 0)
        printf("El entero es positivo.\n");
     else
        printf("El entero es negativo.\n");
     } /* Estas llaves pueden omitirse */
  else
     printf("El entero es igual a cero.\n");

  system("pause");
  return 0;
}
Ejercicio: Modificar el programa anterior para que en primer lugar determine si el entero introducido por teclado es igual a cero, y en caso contrario discrimine si es positivo o negativo.

Si existen sentencias if-else anidadas, cada sentencia else corresponde siempre a la sentencia if más próxima.

Ejemplo 2.4. Ejecutar el siguiente programa y comprobar que la sentencia else se halla vinculada a la segunda condición, aunque por el estilo de escritura (sangrado) parezca que dicha sentencia depende de la primera condición.

#include <stdio.h>

int main() {

  int a;

  printf("Escribe un entero: ");
  scanf("%d", &a);

  if (a >= 0)
     if (a == 1)
        printf("La variable \"a\" vale uno.\n");
  else
     printf("El valor de \"a\" es negativo.\n");

  system("pause");
  return 0;
}

Ejercicio: Modificar el programa anterior para que la sentencia else quede ahora vinculada a la primera condición.


La sentencia switch.

Si un programa debe elegir una opción entre varias, resulta más legible si se usa una sentencia switch en vez de varias sentencias if-else anidadas.
switch (<expresión entera>) {                                
       case const_1: <sentencia/s>
                     break; /* opcional */
       case const_2: <sentencia/s>
                     break; /* opcional */
       ...
       case const_n: <sentencia/s>
                     break; /* opcional */
       default:      <sentencia/s>
       }
Ejemplo 2.5. Realizar un programa que imprima por pantalla un comentario a una calificación numérica introducida a través del teclado.
#include <stdio.h>

int main() {

  unsigned nota; /* No existen notas negativas */
 
  printf("Escribe una nota: ");

  scanf("%u", &nota);


  /* puts() imprime una cadena de caracteres */


  switch (nota) {
         case 0: puts("\nNi idea."); break;
         case 1:

         case 2:

         case 3: puts("\nCasi ni idea."); break;

         case 4: puts("\nCasi apruebas."); break
;
         case 5: puts("\nPor los pelos.");

                 puts("Hay que aplicarse."); break;

         case 6: puts("\nBien aprobado."); break;

         case 7:

         case 8: puts("\nNotable."); break;

         case 9:

         case 10: puts("\nSobresaliente"); break;

         default: puts("\nNota no permitida.");

         }

  system("pause");
  return 0;

}
Si se omite la sentencia break, el flujo de ejecución del programa continúa ejecutando el conjunto de sentencias encerradas entre llaves, aunque éstas se encuentren precedidas por otras etiquetas case.

Ejercicio: Suprimir las sentencias break del programa anterior, ejecutarlo y comprobar el resultado.

Limitaciones:
- No es posible comprobar si el valor de la expresión pertenece a un rango de valores, únicamente se puede comprobar que la expresión coincide con una de las etiquetas precedidas por la palabra reservada case.
- Ni la expresión ni las etiquetas pueden ser datos reales (de tipo float o double). Tan sólo pueden ser de tipo entero o carácter.
Ejercicio: Desarrollar un programa que lea por teclado dos reales (double) y el signo de uno de los siguientes operadores: +, -, *, /. El programa, según el carácter del operador, seleccionará la operación y mostrará el resultado en la pantalla. También presentará un mensaje de error en el caso de que el usuario escriba un carácter distinto de los permitidos.


El bucle while.

Pertenece a la categoría de bucles condicionales. La sentencia (simple o compuesta) se ejecuta de cero a n veces según el valor de verdad de la expresión (condición de entrada).

while (<expresión>)
      <sentencia>

Ejemplo 2.6. Escribir un programa que lea caracteres del teclado y los imprima por pantalla encriptados, a excepción del carácter de salto de línea ('\n'). La clave de encriptación vale diez, de modo que el carácter que se imprime es el que hace diez a partir del carácter original, según el código de entrada/salida usado (ASCII en los ordenadores personales).
Nota: La función getchar() lee un carácter de la entrada estándar, y la función putchar() lo escribe en la salida estándar.
/* programa sencillo de encriptación */

#include <stdio.h>

#define K 10 /* clave de encriptación */

int main() {

  char ch;

  while ((ch = getchar()) != EOF) {
        if (ch == '\n')
           putchar(ch); /* '\n' no se encripta */
        else
           putchar(ch + K); /* otro se encripta */
        } /* las llaves se pueden omitir */

  system("pause");
  return 0;
}
Ejemplo 2.7. Escribir un programa que imprima por pantalla la suma de los cien primeros números naturales.
Nota: Obsérvese como el programa lleva a cabo una suma iterada: En cada iteración del bucle se añade al acumulador (suma) el valor correspondiente del contador (n). Al principio suma vale cero, lo cual es lógico puesto que todavía no se le ha añadido ningún sumando.
#include <stdio.h>

#define TOPE 100

int main() {

  unsigned n = 1, sum = 0;

  while (n <= TOPE) {
        sum += n; /* sum = sum + n; */
        n++;
        }

  printf("\nLa suma es: %u.\n", sum);
  system("pause");
  return 0;
}
Ejercicio: Tomando como base el programa del ejemplo 2.7, escribir otro que imprima la suma de los cuadrados de los k primeros números naturales. El usuario deberá introducir por teclado un valor para k mayor o igual que la unidad.


El bucle do-while.

La sentencia (simple o compuesta) se ejecuta de una a n veces según el valor de verdad de la expresión (condición de salida), la cual se comprueba tras cada iteración.
do
      <sentencia>
while
(<expresión>);
Ejemplo 2.8. Escribir un programa para adivinar un número.
/* programa para adivinar un número */

#include <stdio.h>


#define NUM 1000 /*número a adivinar */

int main() {

  int elegido;

  do {
       printf("\nDame un entero: ");
       scanf("%d", &elegido);
     } while (elegido != NUM);

  puts("\nAcertaste!!!");
  system("pause");
  return 0;
}
Ejercicio: Rescribir el ejemplo 2.7 haciendo uso de la sentencia do-while.


El bucle for.

Permite aumentar la legibilidad de los bucles en algunos casos y escribirlos de forma más compacta. Al principio, y una sóla vez, se ejecuta la expresión de inicialización. Posteriormente se evalúa la expresión de test. Si ésta es cierta, se ejecuta la sentencia (simple o compuesta) y tras ella la expresión de actualización. El ciclo "test-sentencia-actualización" se repite mientras sea cierta la expresión de test.
for (<inicialización>; <test>; <actualización>)
    <sentencia>

El formato anterior puede reescribirse del siguiente modo:

<inicialización>;
while (<test>) {
      <sentencia>
      <actualización>;
      }

Ejemplo 2.9. Escribir un programa que imprima por pantalla el factorial de un entero mayor o igual a cero introducido por el usuario a través del teclado.
#include <stdio.h>

int main() {

  unsigned i, n;
  unsigned fact = 1;

  printf("\nEscribe un entero: ");
  scanf("%u", &n);

  for (i = 1; i <= n; i++)
      fact *= i; /* fact = fact * i; */
 
  printf("\nFactorial de %u: %u.\n", n, fact);
  system("pause");
  return 0;
}
Ejercicio: Rescribir el programa del ejemplo 2.7 mediante el uso de la sentencia for.

Ejemplo 2.10. Escribir un programa que cuente desde uno hasta diez e imprima por pantalla dicha cuenta.
#include <stdio.h>

int main() {

  unsigned i;

  for (i = 1; i <= 10; i++)
      printf("Cuenta: %d.\n", i);
 
  system("pause");
  return 0;
}
Ejercicio: Modificar el programa del ejemplo 2.10 para que imprima la cuenta, en orden inverso, desde diez hasta uno.

Ejemplo 2.11. Escribir un programa que imprima en orden las letras minúsculas del abecedario y sus códigos ASCII.
#include <stdio.h>

int main() {

  char ch;

  for (ch = 'a'; ch <= 'z'; ch++)
      printf("ASCII de %c: %d.\n", ch, ch);
 
  system("pause");
  return 0;
}
Ejercicio: Modificar el programa del ejemplo 2.11 para que imprima, en orden inverso, las letras mayúsculas del abecedario y sus códigos ASCII.

Ejemplo 2.12. Comprobar que el siguiente programa contiene un bucle infinito y, por lo tanto, la ejecución del programa se detiene en éste y para salir del mismo es necesario cerrar su ventana.
#include <stdio.h>
int main() {

  for ( ; ;) puts("Eternidad");

  puts("Esta sentencia no se ejecuta.");

  system("pause");
  return 0;
}
Ejemplo 2.13. Es posible omitir, cuando sea conveniente, algunas de las expresiones de la sentencia for:
#include <stdio.h>

int main() {

  unsigned i = 1;

  for (; i <= 10; ) {
      printf("Cuenta: %d.\n", i);
      i++;
      }
 
  system("pause");
  return 0;
}

Las sentencias break; y continue;.

La sentencia break; puede utilizarse con los tres bucles anteriores. Cuando el programa llega a esta sentencia dentro de un bucle, el flujo abandona este último y pasa a ejecutar la siguiente sentencia del programa. Normalmente se usa la sentencia break; cuando existe más de una razón para abandonar la estructura iterativa: La más importante se suele escribir como condición del bucle y las demás se codifican como sentencias if (...) break;.

Ejemplo 2.14. Escribir un programa que lea caracteres del teclado y los imprima por pantalla hasta que el usuario escriba el carácter de fin de fichero (^Z), el cual se encuentra codificado en el fichero de encabezamiento <stdio.h> como la constante EOF, o bien dicho usuario escriba en el teclado el carácter de salto de línea ('\n').
#include <stdio.h>

int main() {

  char ch;

  while ((ch = getchar()) != EOF) {
        if (ch == '\n') break;
        putchar(ch); /* no depende del "if" */;
        }

  system("pause");
  return 0;
}
La sentencia continue; dentro de un bucle evita el resto de la iteración y desvia el flujo del programa de nuevo hacia la expresión de test. Puede utilizarse en las tres sentencias iterativas anteriormente definidas, pero no en una sentencia switch.

Ejemplo 2.15. Modificar el programa del ejemplo 2.14 para que lea caracteres del teclado y los imprima por pantalla a excepción del carácter de salto de línea ('\n'), el cual se ignorará cada vez que el usuario lo escriba. Dicho programa finalizará su ejecución cuando el usuario escriba el carácter de fin de fichero (^Z), el cual se encuentra codificado en el fichero de encabezamiento <stdio.h> como la constante EOF.
#include <stdio.h>

int main() {

  char ch;

  while ((ch = getchar()) != EOF) {
        if (ch == '\n') continue;
        putchar(ch); /* no depende del "if" */;
        }

  system("pause");
  return 0;
}

Ejercicios de afianzamiento.

Escribir un programa que acepte tres enteros a través del teclado e imprima por pantalla el mayor y el menor de éstos.

Desarrollar un programa que resuelva la ecuación de segundo grado: a x2 + b x + c. Para ello, el programa pedirá en primer lugar los tres coeficientes reales. A continuación, según el signo del discriminante (b2 - 4 a c), presentará un mensaje para indicar al usuario si la ecuación posee dos soluciones reales, una solución doble o no tiene solución en el conjunto de los números reales. En los dos primeros casos el programa mostrará la solución: (-b ± SQRT(b2 - 4 a c)) / 2 a.

Codificar tres programas que sean equivalentes entre si y proporcionen el mismo resultado. El primero de ellos utilizara la sentencia while, el segundo la sentencia do-while y el tercero la sentencia for.

Desarrollar un programa que muestre por pantalla la suma de los cien enteros siguientes a un entero positivo suministrado a través del teclado.

Escribir un programa que lea del teclado temperaturas expresadas en grados Farenheit y las convierta a grados Celsius. El programa finalizará cuando lea un valor de temperatura igual a 999. La conversión de grados Farenheit (F) en Celsius (C) viene dada por la siguiente expresión: C = 5/9 * (F - 32).
Nota: Para que la división de 5 entre 9 dé como resultado un número real y no cero, el dividendo, el divisor o ambos deben ser de tipo real. Esto puede conseguirse mediante un moldeado (conversión de tipo) o escribiendo directamente las constantes como datos reales (double).

Codificar un programa que muestre los n primeros términos de la sucesión An = 1/2n. El primer término de la misma se corresponde con n igual a uno. El valor de n debe suministrarse a través del teclado.

Modificar el programa anterior para que muestre la suma de los n primeros términos de la sucesión An = 1/2n. El primer término de la misma se corresponde con n igual a uno. El valor de n debe suministrarse a través del teclado.

Desarrollar un programa para calcular el máximo común divisor de dos enteros positivos (unsigned) suministrados por teclado mediante el Algoritmo de Euclides, el cual se describe a continuación.
Sea T el máximo común divisor de X e Y (T = MCD (X, Y)). Entonces se cumple:
a) X > Y => T = MCD (X, Y) = MCD (X - Y, Y).
b) X < Y => T = MCD (X, Y) = MCD (X, Y - X).
c) X = Y => T = MCD (X, Y) = X = Y.
Ejemplo: Hallar el máximo común divisor de 1000 y 450.
MCD (1000, 450) = MCD (550, 450);
MCD (550, 450) = MCD (100, 450);
MCD (100, 450) = MCD (100, 350);
MCD (100, 350) = MCD (100, 250);
MCD (100, 250) = MCD (100, 150);
MCD (100, 150) = MCD (100, 50);
MCD (100, 50) = MCD (50, 50);
MCD (50, 50) = 50.