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:
-
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.
-
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.
-
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:
-
Se ejecutan las sentencias del bloque,
-
Se ejecutan la sentencias de actualización,
-
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
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.