ESTRUCTURAS DE CONTROL







Sentencia for()
Esta es la sentencia de control repetitivo más utilizada en el lenguaje C. Su sintaxis es como sigue:

for(cl_iniciación; ex_control; cl_actualización)
 {
  /*
    Bloque de sentencias
    repetidas iterativamente
    por la sentencia for();
  */
 }

 /* Ejemplo */

 for(i=0; i<10;i++)
  {
    printf("%d\n", i);
  }
 


La sentencia for() consta de tres cláusulas que describiremos a continuación:
  1. La cláusula_de_iniciación está formada por una o más expresiones, separadas mediante comas, que se ejecutarán una sola vez al entrar en el bucle. Estas expresiones para dar valores iniciales a las variables que participen en el bloque, tanto si intervienen en la expresión_de_control como en caso contrario.
  2. La expresión_de_control es una expresión cuyo resultado se interpretará de forma lógica. Mientras el resultado de evaluar esta expresión sea verdero (distinto de cero), el bucle seguirá repitiendo su contenido (iterando). Cuidado con los bucles infinitos: una sentencia for con una cláusula de control vacía se repetirá indefinidamente.
  3. La cláusula_de_actualización consta de una o más expresiones separadas por comas, que se ejecutarán una vez por iteración; el momento de la ejecución estas sentencias es precisamente el siguiente a la ejecución a la última sentencia del bloque, e inmediatamente antes de verificarse el valor de la expresión_de_control .
Se dice entonces que el bucle for() es una sentencia de comprobación anterior. Cuando el control de programa llega a la sentencia for() , se evalúan las expresiones de iniciación (si las hay) y la condición de iteración. Si esta última no produce un valor verdadero, se sale del bucle sin haber ejecutado las sentencias del bloque ni siquiera una vez. Si la cláusula de control produce un valor verdadero, entonces:
  1. Se ejecutan las sentencias del bloque,
  2. Se ejecutan la sentencias de actualización,
  3. y se evalúa de nuevo la cláusula_de_control .
(Obsérvese que ya no se ejecuta las cláusula_de_iniciación ). Si el resultado de evaluar la condición de control es verdadero, se produce una nueva iteración; en caso contrario se sale del bucle y se ejecuta la sentencia siguiente a este.

La sentencia for(; ;) es sumamente flexible. Todas sus cláusulas son opcionales; en caso de prescindir de todas ellas, se tiene por omisión un bucle infinito. Las sentencias del bloque pueden ser, a su vez, sentencias for() , produciéndose así un anidamiento de sentencias. En este caso será preciso verificar cuidadosamente la correcta utilización de las llaves; si se produjera un cruce en las llaves de los bloques anidados, el compilador no señalaría un error, y el programa sería incorrecto. Véase un ejemplo inicial de la sentencia for() .

Casos incompletos
La sentencia for(; ;) es sumamente flexible. Todas sus cláusulas son opcionales; en caso de prescindir de todas ellas, se tiene por omisión un bucle infinito. Si se prescinde de las cláusulas de iniciación o de actualización, será responsabilidad del programador escribir las sentencias que desempeñen esas funciones. Es posible hallar circunstancias en que las cláusulas de iniciación no existan realmente, y también casos en que sean tan numerosas que el encabezado de for() resulte deformado. Otro tanto se puede decir de las sentencias de actualización. En tales ocasiones, procede utilizar las sentencias while() y do-while() .

Anidamiento
No se impone restricción alguna sobre las sentencias que aparecerán en el bloque de la sentencia for() . Consiguientemente, se admiten las sentencias de control, y en particular la sentencia for() . Esto se denominará anidamiento. El compilador no impone restricciones sobre el nivel de anidamiento efectuado. Es muy conveniente respetar en todo momento las convenciones de formato de programa; C no es un compilador posicional pero la lectura e inteligibilidad de los programas se incrementan notablemente al seguir estas convenciones. Como ejemplo se propone un programa que muestra en pantalla la tabla de multiplicar.

break .
Es relativamente frecuente el caso en que se tiene que abandonar un bucle for(;;) "por la tremenda". Esto se hace posiblemente mediante una sentencia if() y con el auxilio de la sentencia break , entre cuyas habilidades se cuenta la de transferir el control fuera del bloque en cuyo interior se encuentre. Esto sirve evidentemente para salir del bloque de un bucle for que se quiera abandonar. El lector interesado hallará un ejemplo en el siguiente ejercicio .

++ y --
Una de las operaciones más frecuentes es el incremento o decremento de índices. Las expresiones del tipo i = i + 1; o bien j = j - 1; son extraordinariamente abundantes. El lenguaje C ofrece los operadores ++ y -- , destinados a incrementar o decrementar respectivamente sus operandos. De esta manera se tienen las siguientes equivalencias:

La expresión... equivale a...
i = i + 1; i++;
j = j - 1; j--;
Los operadores ++ y -- pueden anteponerse o posponerse a sus operandos. En ambos casos, el resultado final es el incremento o decremento de sus operandos. Sin embargo, cuando se utilizan estos operadores en el interior de una expresión, hay diferencias en su significado, dependiendo de que vayan antepuestos o pospuestos. En efecto, si el operador va antepuesto entonces primero se incrementa o decrementa el operando, y después se utiliza el valor resultante en la expresión. Si el operador va pospuesto, entonces primero se utiliza el valor del operando en la expresión, y después se incrementa. Evidentemente, cuando se trate de expresiones formadas únicamente por el operador y el operando, este orden de evaluación no tendrá importancia. La utilización de ++ y -- puede observarse en el siguiente Ejercicio .

+=, *=, -=, /=
Es frecuente en C hallar asignaciones en que el lvalue aparece también dentro del rvalue , y más aún dentro de una expresión muy sencilla con solo dos operandos de los cuales el primero es el lvalue . En tales ocasiones, C admite una nueva notación compacta, que posee la misma funcionalidad con un código más breve. En su forma general se tiene
lvalue = lvalue operador expresión;

y la forma compacta es

lvalue operador= expresión;

La tabla siguiente describe la forma de utilizar las expresiones compactas descritas más arriba:

La asignación... equivale a...
i = i + expresión; i += expresión;
i = i - expresión; i -= expresion;
i = i * expresión; i *= expresión;
i = i / expresión; i /= expresión

Como ejemplo , véase un remake del Ejercicio 1, implementado con operadores de incremento y con el esquema compacto de asignación. Desde un punto de vista puramente funcional, ambos programas son totalmente equivalente. Sin embargo, el segundo, al hacer uso de operadores compactos, producirá un código más breve y rápido. Es raro ver expresiones que puedan hacer uso de ellos y no lo hagan.









Ejercicio 1 .- Construir un programa que pida diez números al usuario. El programa debe sumar los números introducidos, generando finalmente la media.
/*
 Este programa permite calcular la media de una serie de números
 introducidos a través del teclado.
 Sirve como ejemplo de la sentencia for().
*/

#include <stdio.h>

#define MAX_NUMEROS 10


int main(void)
{
  float temp, /* Variable temporal para leer números */
  acumulador, /* De la suma de números */
  media_aritmetica;
  int i; /* Contador de números */
  printf("Programa de cálculo de medias aritméticas.");
  printf("\n\nPor favor, pulse <INTRO> después de cada número.\n\n");

 acumulador = 0.0; /* Iniciación IMPRESCINDIBLE */

 for(i=0; i < MAX_NUMEROS; i = i + 1)
 {
 printf("Escriba a[%d] : ", i);
 scanf("%f", &temp);
 acumulador = acumulador + temp;
 };

 media_aritmetica = acumulador / MAX_NUMEROS;

 printf("\n\nLa media aritmética es %f.", media_aritmetica);

 printf("\n\nTerminación normal del programa.\n\n");
 return 0;
}
/*
 RESULTADO
Programa de cálculo de medias aritméticas.

Por favor, pulse <INTRO> después de cada número.

Escriba a[0] : 1
Escriba a[1] : 2
Escriba a[2] : 3
Escriba a[3] : 4
Escriba a[4] : 5
Escriba a[5] : 6
Escriba a[6] : 7
Escriba a[7] : 8
Escriba a[8] : 9
Escriba a[9] : 10


La media aritmética es 5.500000.

Terminación normal del programa.
*/

Comentarios .- Hay que dar valor inicial a la variable acumulador. ¿Qué ocurre si no se hace esto?

Ejercicio 2 .- Construir un programa que muestre en pantalla la tabla de multiplicar.

/*
 Este programa muestra en pantalla la tabla de multiplicar.
 Sirve como ejemplo de la sentencia for()
*/

#include <stdio.h>

/*
 OJO : TABLA es la dimensión de la tabla + 1.
 Si TABLA es 11, la tabla llegará hasta el 10.
*/

#define TABLA 11



int main(void)
{
  int i, j; /* Índices para crear la tabla */
  printf("Tabla de multiplicar\n\n");

  printf("    |");
  for (i=1; i < TABLA; i = i + 1)
    printf("%4d",i);
   printf("\n");

   printf("----|");
   for (i=1; i < TABLA; i = i + 1)
    printf("----");
   printf("\n");

   for(i=1; i < TABLA; i = i + 1)
   {
     printf("%4d|", i);
     for (j = 1; j < TABLA; j = j + 1)
      printf("%4d", i*j);
     printf("\n");
   }

   printf("\n\nTerminación normal del programa.\n\n");

   return 0;
}

/*
 RESULTADO
Tabla de multiplicar

    |   1   2   3   4   5   6   7   8   9  10
----|----------------------------------------
   1|   1   2   3   4   5   6   7   8   9  10
   2|   2   4   6   8  10  12  14  16  18  20
   3|   3   6   9  12  15  18  21  24  27  30
   4|   4   8  12  16  20  24  28  32  36  40
   5|   5  10  15  20  25  30  35  40  45  50
   6|   6  12  18  24  30  36  42  48  54  60
   7|   7  14  21  28  35  42  49  56  63  70
   8|   8  16  24  32  40  48  56  64  72  80
   9|   9  18  27  36  45  54  63  72  81  90
  10|  10  20  30  40  50  60  70  80  90 100


Terminación normal del programa.

Comentarios .- En una pantalla 800x600 se llega hasta la tabla del 30; más allá la cosa se complica (letra de tamaño 9). Por otra parte, la raíz cuadrada de 999 es 31.606961, que es una segunda limitación por cuanto se ha construido la tabla con %4d. Se deja para el lector la curiosa tarea de construir una tabla de multiplicar que se visualice SIEMPRE en correctas columnas (al menos en un fichero).

Nota.- Resulta interesante utilizar el operador de redirección para enviar el resultado del programa a un archivo, en lugar de dejarlo en pantalla. Esto se hace mediante una expresión como la siguiente:
./a.out > tabla.txt


Ejercicio 2 .- Construir un programa que implemente el conocido juego de adivinación de números. Se comunica al usuario el límite mínimo y máximo de un número que ha pensado el ordenador (por ejemplo, entre 1 y 512) y se ofrece un número de intentos para adivinar el número. El usuario va haciendo suposiciones, y si halla el número antes del número máximo de intentos el programa concluye. En caso contrario, el programa admite el número máximo de intentos y finaliza igualmente.

/*
 Una implementación sencillita de MASTERMIND.
 Este ejercicio muestra el uso de break en bucles for(;;).
*/

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

// Esto lo voy a utilizar para generar una semilla verdaderamente aleatoria.
// Para ser exactos, empleo la función clock(), que me indica el tiempo
// transcurrido desde el arranque el sistema.

#include<math.h>

// Esto lo necesitamos para disponer de logaritmos, y calcular
// el número máximo de intentos. La función ceil() proporciona,
// por cierto, el menor entero que es mayor que su argumento.
// La lógica de la búsqueda binaria tiene aquí su aplicación

#define maximo 512


void main(void)
{

 int numero_buscado = clock() % maximo,numero_dado = 0;
 int max_preguntas = (int)(ceil(log(maximo)/log(2)));
 int numero_pregunta;

 printf("M A S T E R M I N D\n\n");

 printf("He pensado un numero entre 1 y %d.¿Sabrás cual es en %d intentos?\n\n",
 maximo,max_preguntas);

 for(numero_pregunta=1; numero_pregunta<=max_preguntas;numero_pregunta++)
 {
 printf("Intento número %d : ",numero_pregunta);
 scanf("%d",&numero_dado);

 // Si el pringaíllo lo ha encontrado, salimos del bucle!

 if (numero_dado == numero_buscado) break;

 // En caso contrario, damos la pista correspondiente y seguimos

 if (numero_dado < numero_buscado)
 printf("\nNo, %d es menor que el número que he pensado.\n", numero_dado);
 else
 printf("\nNo, %d es mayor que el número que he pensado.\n", numero_dado);

 } // Aquí acaba el bucle for

 if (numero_dado != numero_buscado)
 printf("\n\nJa! Había pensado el %d\n\n", numero_buscado);
 else
 printf("\n\nEnhorabuena! Era el %d!\n\n>Fin<\n\n",numero_buscado);
}

/*
 RESULTADO
M A S T E R M I N D

He pensado un numero entre 1 y 2048.¿Sabrás cual es en 11 intentos?

Intento número 1 : 1024

No, 1024 es mayor que el número que he pensado.
Intento número 2 : 512

No, 512 es menor que el número que he pensado.
Intento número 3 : 768

No, 768 es mayor que el número que he pensado.
Intento número 4 : 620

No, 620 es mayor que el número que he pensado.
Intento número 5 : 576

No, 576 es mayor que el número que he pensado.
Intento número 6 : 544

No, 544 es menor que el número que he pensado.
Intento número 7 : 560

No, 560 es menor que el número que he pensado.
Intento número 8 : 568

No, 568 es menor que el número que he pensado.
Intento número 9 : 572

No, 572 es mayor que el número que he pensado.
Intento número 10 : 570


Enhorabuena! Era el 570!

>Fin<

 COMENTARIOS

 El uso de clock() permite asegurar que en cada ejecución salga un
 número distinto. No es realmente un número aleatorio, porque los
 ordenadores suelen encenderse aproximadamente a la misma hora todos
 los días. Sin embargo, es una aproximación razonable, y funciona bien.
 Obsérvese que si el jugador encuentra el número (casualmente) antes
 de efectuar los intentos reglamentarios, entonces sale del bucle
 mediante una sentencia break.
*/



Ejercicio 3.-
Construir un programa que escriba en pantalla los números del 1 al 5, y del 5 al 1. El programa debe hacer uso de los operadores ++ y --, estudiando posibles errores lógicos (y no sintácticos) en su utilización.

/*
 Este programa muestra un ejemplo de utilización de los operadores ++ y --.
*/

#include <stdio.h>

#define MAX_NUM 6


int main(void)
{
  int i;
  printf("Escribir los números del 1 al %d (empleando i++) : ",MAX_NUM-1);

  for(i=1; i < MAX_NUM ; i++)
    printf("%d", i);

  printf("\n\nEscribir los números del 1 al %d (empleando ++i) : ",MAX_NUM-1);

  for(i=1; i < MAX_NUM ; ++i)
    printf("%d", i);

  printf(" \n\nUn ejemplo curioso:");

  printf("\n\nEscribir los números del 1 al %d (bien, i++) : ",MAX_NUM-1);

 /* Bien. Primero se usa i, luego se incrementa */
  for (i=1; i < MAX_NUM; printf("%d", i++));

  printf("\n\nEscribir los números del 1 al %d (mal, ++i) : ",MAX_NUM-1);

 /* MAL. Primero se incrementa i, y luego se usa! */
  for (i=1; i < MAX_NUM; printf("%d", ++i));

 /*
 Ahora con --
 */
  printf("\n\nY ahora con --");

  printf("\n\nEscribir los números del %d al 1 (empleando i--) : ",MAX_NUM-1);

  for(i=MAX_NUM-1; i > 0 ; i--)
    printf("%d", i);

  printf("\n\nEscribir los números del %d al 1 (empleando --i) : ",MAX_NUM-1);

  for(i=MAX_NUM-1; i > 0 ; --i)
    printf("%d", i);

  printf(" \n\nUn ejemplo curioso:");

  printf("\n\nEscribir los números del %d al 1 (bien, i--) : ",MAX_NUM-1);

 /* Bien. Primero se usa i, luego se decrementa */
  for(i=MAX_NUM-1; i > 0 ; printf("%d", i--)) ;

  printf("\n\nEscribir los números del %d al 1 (mal, --i) : ",MAX_NUM-1);

 /* MAL. Primero se incrementa i, y luego se usa! */
  for(i=MAX_NUM-1; i > 0 ; printf("%d", --i)) ;

  printf("\n\nTerminación normal del programa.\n");

  return 0;
}

/*
 RESULTADO
 Escribir los números del 1 al 5 (empleando i++) : 12345

 Escribir los números del 1 al 5 (empleando ++i) : 12345

 Un ejemplo curioso:

 Escribir los números del 1 al 5 (bien, i++) : 12345

 Escribir los números del 1 al 5 (mal, ++i) : 23456

 Y ahora con --

 Escribir los números del 5 al 1 (empleando i--) : 54321

 Escribir los números del 5 al 1 (empleando --i) : 54321

 Un ejemplo curioso:

 Escribir los números del 5 al 1 (bien, i--) : 54321

 Escribir los números del 5 al 1 (mal, --i) : 43210

 Terminación normal del programa.

 COMENTARIOS

 El compilador protesta; solo son Warnings:

 Warning : possible unwanted ';'
 HelloWorld.c line 26 for (i=1; i < MAX_NUM; printf("%d", i++));

 ...

 Warning : possible unwanted ';'
 HelloWorld.c line 56 for (i=1; i < MAX_NUM; printf("%d", ++i));

 El uso de ++ y -- aplicado al índice de una expresión
 permite construir códigos bastante compactos. En este caso
 concreto, se puede emplear también un código de la forma:

 for (i=1; i < MAX_NUM; ) printf("%d", i++);

 En este caso no hay cláusula de actualización; la sentencia
 printf() se encarga de actualizar el contador. Se verán
 más ejemplos de esta aplicación posteriormente.

 Un último comentario: la indicación de "bien" y "mal" alude únicamente
 al resultado final, que no es el buscado. Se puede obtener el resultado
 deseado empleando operadores antepuestos o pospuestos; sólo habrá que
 adecuar la cláusula de iniciación.
*/

Ejercicio 3 .- Repetir el Ejercicio 1 (cálculo de una media aritmética) emplando el operador de incremento, ++, y la notación compacta de asignación, operador= .

/*
 Este programa calcula medias aritméticas.
 Es un ejemplo de utilización de los operadores de incremento
 y de las formas compactas de asignación.
*/

#include <stdio.h>

#define MAX_NUMEROS 10

float temp, /* Variable temporal para leer números */
 acumulador, /* De la suma de números */
 media_aritmetica;
int i; /* Contador de números */

int main(void)
{
 printf("Programa de cálculo de medias aritméticas.");
 printf("\n\nPor favor, pulse <INTRO> después de cada número.\n\n");

 acumulador = 0.0; /* Iniciación IMPRESCINDIBLE */

 for(i=0; i < MAX_NUMEROS; i++)
 {
  printf("Escriba a[%d] : ", i);
  scanf("%f", &temp);
  acumulador += temp; /* Asignación compacta */
 };

 media_aritmetica = acumulador / MAX_NUMEROS;

 printf("\n\nLa media aritmética es %f.", media_aritmetica);

 printf("\n\nTerminación normal del programa.\n\n");
 return 0;
}

Ejercicios propuestos

  1. Ejercicio 0402r01.- Escribir un programa que lea de teclado varias frases, hasta que el usuario inserte una cadena vacía. El programa debe mostrar la media de letras por palabra y la media de palabras por frase.

  2. Ejercicio 0402r02.- Adaptar el ejercicio anterior al caso de disponer de información almacenada en un archivo de texto. El programa debe leer el archivo y mostrar en pantalla los resultados obtenidos.

  3. Ejercicio 0402r03.- Considérese una línea de texto escrita con un formato encolumnado conocido. Se pide separar y traducir los campos, insertándolos en los campos correspondientes de una estructura de datos.

  4. Ejercicio 0402r04.- Considérese una línea de texto escrita con un formato delimitado conocido. Se pide separar y traducir los campos, insertándolos en los campos correspondientes de una estructura de datos de características análogas a la línea de texto.

  5. Ejercicio 0402r05.- Considérese una cierta estructura formada por campos numéricos y alfanuméricos. Se pide construir un programa capaz de traducir el contenido de esa estructura a una línea de texto, de formato encolumnado.

  6. Ejercicio 0402r06.- Considérese una cierta estructura formada por campos numéricos y alfanuméricos. Se pide construir un programa capaz de traducir el contenido de esa estructura a una línea de texto, de formato delimitado.

  7. Ejercicio 0402r07.- Considérese una línea de texto codificada en un formato en columnado. Se pide construir un programa capaz de traducir esa línea al formato delimitado.

  8. Ejercicio 0402r08.- Considérese una línea de texto codificada en formato delimitado. Se pide construir un programa capaz de traducir esa línea a un formato encolumnado.