Sentencias de control.


En lenguaje C, existen un conjunto de sentencias que se pueden usar para afectar el control o flujo de un programa. La mayoría de estas sentencias, se basan en una prueba condicional que determinará la acción que se llevará a cabo. Una prueba condicional produce un valor de cierto ( se puede usar la palabra clave de C 'TRUE' ) o de falso ( la cual es 'FALSE' ). En lenguaje C, el falso es representado exclusivamente por el valor numérico cero; cualquier otro valor representa un cierto. Este enfoque de lo que es cierto y lo que es falso permite codificar eficientemente muchos tipos de rutina.


Las sentencias de control se dividen en:


Sentencias Condicionales.


En lenguaje C son soportados dos tipos de sentencias condicionales: if y switch.


if


Se usa cuando se requiere que una instrucción o un bloque de ellas sea ejecutado si y sólo si se cumple una condición particular. Su forma de uso es:


if (condición) sentencia;


ó


if (condición) {

sentencia1;

sentencia2;

...

última sentencia;

}


En el primer caso, sentencia se ejecutará si condición es cierto. En el segundo caso se ejecutarán todas las sentencias dentro del bloque. “condición” es una expresión con operadores relacionales que es evaluada y retorna cero si es falsa o cualquier otro valor es cierta. P ej: Al evaluar la siguiente expresión: (2<3), se retornará un número diferente de cero para indicar que es cierta ( cualquier número diferente de cero pero, aunque no importa, suele ser uno ). Por el contrario el evaluar la expresión (3<2), retornará un cero indicando que es falsa. En lenguaje C también se pueden usar las macros definidas 'TRUE' o 'FALSE'.



if-else


Podría requerirse en un programa, y de hecho suele ser así, que se ejecute una sentencia si se cumple una condición y que se ejecute otra si no. Para eso tenemos el compañero de la sentencia if denominado else. Su forma de uso es:


if (condición) sentencia;

else sentencia;


ó


if (condición) {

sentencia1;

sentencia2;

} else {

sentencia3;

sentencia4;

}


Como intuirán, sentencia1 y sentencia2 se ejecutarán si y solo si condición es cierta. De lo contrario se ejecutarán sentencia3 y sentencia4.


ifs anidados


Uno de los aspectos más confusos de las sentencias if se encuentra en los ifs anidados. Un if anidado es un if que es obejeto de otro if o de un else. La razón por la que suelen ser tan problemáticos es que puede ser difícil saber que else se asocia con que if. Por ejemplo:


if(cond1)

if(cond2) sentencia1;

else sentencia2;


Por suerte C nos da una regla muy sencilla para saber a que if pertenece un else y ésta es que una sentencia else pertenecerá al if precedente más próximo que no tenga ya asociada una sentencia else. En este caso, el else esta asociado a la sentencia if(cond2) ( si el programador se guía por la estructura del programa podría pensar que esta asociado con el if(cond1) y no es así ). Si lo que se quiere es que el else sea asociado con el if(cond1) entonces se debe usar llaves para saltarse la asociación normal, por lo que sería:


if(cond1) {

if(cond2) sentencia1;

} else sentencia2;


Ahora el else sí está asociado a if(cond1).


if-else-if


La escala if-else-if es una construcción muy común en programación. Su forma de uso es:


if (cond1) sentencia1;

else if (cond2) sentencia2;

else if (cond3) sentencia3;

...

else ultimaSentencia;


Todas estas sentencias se evalúan en orden y tan pronto como se encuentra una condición cierta se ejecuta la sentencia asociada con ella y se pasa por alto el resto de la escala. Si ninguna de las condiciones es cierta se ejecuta el else final. Esta sería la sentencia “por defecto” a ejecutarse pero no necesariamente debe existir. Si no está, y ninguna de las condiciones se cumple, entonces no se hace nada.


La alternativa a if-else con el operador ?


Se puede usar el operador ? Para reemplazar las sentencias if-else con la forma general:


if(cond)

expresión;

else

otraExpresión;


La restricción es que el objetivo del if y el else deben ser expresiones simples que retornen valores y no otras sentencias de C.


El ? Es un operador ternario ya que necesita tres opereandos. Su forma es:


Exp1 ? Exp2 : Exp3


Observe los dos puntos. Que hace ésto? Evalua Exp1. Si es cierta se evalúa Exp2 y se convierte en el valor de la expresión completa. Si Exp1 es falsa entonces se evalúa Exp3 y su valor se convierte en el valor de la expresión completa. Que quiere decir que se convierte en el valor de la expresión completa? Veamos el siguiente caso:


i = 3;

x = i>5 ? 50 : 100;


Ésto es lo mismo que decir:

i = 3;

if (i > 5) x = 50;

else x = 100;


o sea, que la expresión “i > 5 ? 50 :100” se convierte en 50 si se cumple la condición i > 5 y se convierte en 100 si no. Luego este resultado se puede asignar a una variable. A eso me refiero con que “su valor se convierte en el valor de la expresión completa”. Ok, ésto es así... pero no necesariamente ustedes necesitarán usarlo de esta manera. Por ejemplo:


char ch;

ch = getch();

ch == 'x' ? printf(“Presiono x”) : printf(“No presiono x”);


evalúa si ch es igual a 'x'. Si es así imprime “Presiono x” y si no imprimirá el mensaje “No presiono x”. No me interesa asignar el valor de la expresión completa a ninguna variable.


Otro ejemplo:


int i;

scanf(“%i”,&i);

i ? printf(“Introdujo un número distinto de cero”) : printf(“Introdujo cero”);


evalúa i. Si es cero ( lo que indica falso ) imprimirá el mensaje “Introdujo cero”. Para cualquier otro valor de i imprimirá “Introdujo un número distinto de cero”. Al igual que en el caso anterior no me importa que valor toma la expresión completa.


ESO SÍ: Lo que deben tomar en cuenta es que todo lo que usen en esta expresión debe retornar un valor. Esto quiere decir que no pueden usar funciones que retornen void. La mayoría de las funciones en C retornan un valor. Por ejemplo printf() retorna el número de caracteres impresos y es por eso que puede ser usada ( si, ese valor no nos interesa aquí. Por eso no lo asigné a una variable como en el primer ejemplo. No nos interesa, pero si no lo retornara no podríamos usar esta alternativa ).


switch


Aunque la escala if-else-if puede realizar pruebas multicaminos, es poco elegante. El código puede ser bastante difícil de seguir y puede confundir incluso a su autor al pasar el tiempo. Por esta razón, C incorpora una sentencia de decisión de ramificación múltiple llamada switch. Esta sentencia compara sucesivamente una variable con una lista de opciones representadas por constantes enteras o de caracteres y cuando encuentra una que corresponda, se ejecuta la sentencia o bloque de sentencias que tiene asociada la opción. Su forma de uso es:


switch (variable) {

case const1:

secuencia de sentencias1;

break;

case const2:

secuencia de sentencias2;

break;

.

.

.

default:

secuencia de sentencias;

} // Las sentencias break redirigen el flujo del programa directamente

// hasta aquí y continúa la ejecución del programa ejecutando todo

// lo que siga.


La sentencia switch evaluará “variable” y si ésta contiene el valor const1 se ejecutará la secuencia de sentencias1 hasta conseguir un break. Si es const2 la secuencia de sentencias2 hasta conseguir el break y así sucesivamente. Si ninguna de las constantes en los case corresponde al valor de la “variable” entonces el switch ejecutará la secuencia de comandos representados por default. La parte default es opcional si ustedes no quieren que se lleve a cabo ninguna operación al fallar todas las pruebas entonces no agregan un bloque default y listo. OJO: switch sólo puede comprobar “IGUALDAD” y no puede evaluar expresiones relacionales. Otra cosa: switch NO puede contener dos case con el mismo valor.


El break lo veremos más adelante pero lo que hace es indicarle al programa que ya uno de los casos se ejecutó y que se vaya al final del bloque y finalice el mismo ( no revisará nada mas ). Es por eso que la última sentencia no requiere el break. El break es opcional. Si no lo agregan revisará el resto de los casos. El break lo que hace es enviar el flujo del programa al final el bloque ( VER break ).


Un buen ejemplo para un switch sería un menú. Veamos:


char c;

printf(“Que opción ( 1, 2 o 3) del menú requiere ejecutar: “);

c = getche();


switch ( c ) {

case '1':

printf(“Opción 1);

break;

case '2':

printf(“Opción 2”);

break;

case '3':

printf(“Opción 3”);

break;

default:

printf(“No introdujo una opción válida\n”);

}


Si por ejemplo tenemos un caso en que queremos que un mismo bloque de sentencias se realice para varias de las opciones del switch, podemos hacer:


switch ( variable ) {

case 1:

case 2:

case 3:

sentenciasComunes;

break;

case 4:

sentencia4;

case 5:

sentencia5;

break;

default:

sentenciaDefault;

}



Aquí sentenciasComunes se ejecutarán si se cumplen case 1, 2 o 3. Otra cosa que se refleja en este ejemplo y que puede ser de gran utilidad es que si se cumple el caso 4, se ejecutará la sentencia4 y como no consigue el break, también se ejecutará la sentencia5.


Los “case” sólo pueden contener secuencia de sentencias por lo que, por ejemplo, no se podrán realizar declaración de variables dentro de los case. Los case NO son bloques de código sino que tienen asociadas sentencias. Sin embargo, si necesitasen definir una variable dentro de un bloque switch ( el switch, por supuesto, si es un bloque ) podrían hacerlo pero fuera de todos los case. Ejemplo:


switch ( c ) {

int i;

case 1: // etc.

}


Bucles.


En C al igual que en todos los lenguajes de programación modernos, las sentencias de iteración ( o bucles ) permiten que una instrucción o un bloque de ellas sea ejecutado hasta que se alcance una cierta condición. Los bucles se logran en C a través de: for y while.


Bucle for y variaciones del mismo. Bucle infinito.


La forma general de la sentencia for es:


for(inicialización;condición;incremento) sentencia;


ó


for(inicialización;condición;incremento) {

sentencia1;

sentencia2;

sentencia3;

}


donde:

inicialización: es normalmente una sentencia de asignación que se utiliza para inicializar la variable de control del bucle.

condición: es una expresión relacional que determina cuándo finaliza el bucle

incremento: define como cambia la variable de control cada vez que se repite el bucle. Puede ser algo como i++, ++i, i-- ó incluso x-=5, por ejemplo.


Como se observa, estas tres secciones principales deben estar separadas por punto y coma.


Por ejemplo en el siguiente caso:


int i;

for ( i = 0; i < 256; i++ ) printf(“(%i, %c) ”, i, i );


al entrar al bloque inicializamos la variable i con el valor cero. Se realiza la evaluación de la condición y si ésta es menor que 256, la sentencia printf se ejecutará. Al finalizar la ejecución de dicha sentencia se llega al final del bloque y el valor de la variable i será incrementada y así empezara la verificación de nuevo. La primera vez que se ejecuta el bucle la variable i contiene el valor 0, la segunda vez 1, la tercera 2 y así seguirá incrementando su valor y ejecutando el bucle mientras se cumpla la condición. Por cierto, este código imprime todos los pares (valor entero, caracter) que se encuentran en la tabla ASCII.


Otro ejemplo:


for( x = 100; x != 65; x-=5 ) {

z = sqrt(x);

printf(“La raíz cuadrada de %d es %f”, x, z);

}


En este caso el bucle inicializa la variable x con el valor 100 y se ejecutará mientras x sea distinto de 65. El valor de la variable x será decrementado en 5 en cada iteración. Por lo tanto este buble imprimirá el par número y raíz cuadrada del mismo para los números: 100, 95, 90, 85 , 80, 75 y 70. Al hacerse la variable x igual a 65, finaliza la ejecución del bucle porque no se cumple la condición.


Una de las variaciones más comunes del bucle for utiliza el operador coma para permitir dos o más variables para el control de un bucle. Por ejemplo:


for( x = 0, y = 0; x + y < 10; x++, y++ ) {

bloque de sentencias;

}


inicializa las variables x y y a cero y luego ejecutará el “bloque de sentencias” mientras que la suma x +y sea menor que diez. En este ejemplo, en cada nueva iteración los valores valor de x y y serán incrementados en uno.


OJO: Cabe destacar que los valores de las variables de control del bucle pueden variar dentro del bloque de sentencias y no necesariamente deben ser alteradas sólo en la declaración del bucle. Por ejemplo:


for( x = 0; x < 10; ) {

x++;

sentencias;

}


es completamente válido. Observarán en este ejemplo también que de hecho no es necesario que el bucle for contenga todas las secciones de definición. En este caso omitimos la parte del incrementador.


Otra variación común es por ejemplo:


for(;;) {

sentencias;

}


lo que es conocido como bucle infinito. Aquí las sentencias se ejecutarán eternamente a menos que una de las sentencias detenga la ejecución del bloque. Ejemplo:


for(;;) {

c = getch();

if (c == 'x') break;

}


se ejecutará pidiendo caracteres por teclado mientras que el caracter sea distinto de 'x' en cuyo caso se saldrá del bloque gracias a la sentencia break ( ver más adelante ).


Cabe destacar que cada una de las secciones del bucle for puede consistir en cualquier expresión válida de C pero no ahondaremos en ese punto.


while


Otro bucle de control en C. Su forma de uso es:


while ( condición ) sentencia;


ó


while ( condición ) {

sentencia1;

sentencia2;

...

}


La condición puede ser cualquier expresión y cualquier valor distinto de cero se considera cierta.


Por ejemplo:


char ch = '\0';

while(ch != 'x') ch = getch();


En este ejemplo, el programa declara una variable de tipo char llamada ch y le asigna la constante de barra invertida que representa el Nulo ( \0 ). A continuación entra en el bucle while y evalúa la condición. Como la primera vez ch es distinto de 'x' entonces se ejecuta la sentencia que en este caso es pedir un caracter por teclado. Este bucle se repetirá mientras el usuario no introduzca el caracter x.


No es necesario tener algunas sentencias en el cuerpo del bucle while. Por ejemplo:

char cu = '\0';

while ( (ch = getch()) != 'x' );


es perfectamente válido y hace exactamente lo mismo que el ejemplo anterior.


do-while


A diferencia de los bucles for y while que analizan las condiciones al principio del bucle, el bucle do-while la analiza al final del mismo. Por lo tanto, si ustedes requieren que un bloque de sentencias sea ejecutado “AL MENOS UNA VEZ” sin importar la condición, entonces este es el bucle que necesitan. Su forma de uso es:

do {

secuencia de sentencias;

} while ( condición ); // OJO con este punto y coma.


aunque las llaves, por supuesto, no son necesarias si sólo existe una sentencia. De todas formas siempre es bueno usarlas para evitar confusiones con el while ( por supuesto confusiones del programador no del compilador ).


Un ejemplo:


int i = 50;

do {

scanf(“%i”,&i);

} while ( i > 100 );


En este caso, el programa pedirá datos por teclado y se los asignará a la variable i mientras se cumpla que i sea mayor que 100. Cuando el usuario introduzca un valor menor o igual que 100, al evaluar la condición se cumplirá ésta y no se ejecutará más el bloque. Obsérvese también que premeditadamente asigne el valor inicial de i a 50. Esto es para mostrar que aunque la condición no se cumplía al entrar al bloque, de igual forma el bloque se ejecutó. Por eso comenté anteriormente que este bloque siempre se ejecuta “al menos una vez”.


Otras sentencias importantes.


Existen tres sentencias especialmente usadas para alterar el control o flujo normal con el que se ejecutaría un programa. Estas son: break, exit() y continue.


break


La sentencia break tiene dos usos. Se puede usar para finalizar un case en una sentencia switch como ya vimos o para forzar la terminación inmediata de un bucle saltando la ejecución normal del ciclo. Por ejemplo:


int i;

for( i = 0 ; i < 50; i++ ) {

printf(“%i “,i);

if ( i == 10 ) break;

}

// El flujo del programa viene hasta aquí cuando se ejecuta el break anterior.



En este caso tenemos un bucle for que comienza con i = 0 y será incrementado en uno en cada ciclo. El bucle imprimirá el valor de i mientras i sea menor que 50. Pero... que pasa? Que este bucle no imprimirá los números entre 0 y 100 como se esperaría al ver la definición del bucle “for” sino que imprimiría sólo los números entre 0 y 10 y luego, como se cumple la condición i == 10, saltará al final del bucle no imprimiendo nada más. El bucle sencillamente finaliza su ejecución cuando i se hace igual a diez todo ésto debido a la sentencia break.


exit()


Su prototipo es: void exit(int estado);


Esta función es ANSI C por lo que su prototipo está definido dentro de la biblioteca estándar stdlib.h – SI, dije stdlib.h no stdio.h. No es una función estándar de entrada o salida.

Esta función es usada para salir anticipadamente de un programa . Dicho de otra forma, ella da lugar a la terminación inmediata del programa forzando la vuelta al sistema operativo. El valor del estado es devuelto y si éste es cero, quiere decir que ocurrió una terminación normal del programa ( ésto es una convención internacional ).


Esta función es usada frecuentemente para expresar que una condición estrictamente necesaria para la ejecución del programa no se cumple. Supongamos que requerimos verificar que cierta “cond1” se cumpla para poder ejecutar nuestro programa y que en caso contrario nos lo indique, podríamos hacer algo como:


if ( cond1 ) exit(0); // Todo Ok. Se cumple la condición requerida. Retorna cero.

else exit (cualquierValorDistintoDeCero); // No se cumplio así que retorna un código de error.


Por lo tanto si al ejecutar este programa nos retorna cero sabemos que tenemos lo que necesitamos para que dicho programa se desarrolle con normalidad. Si nos devuelve el valor “cualquierValorDistintoDeCero”, entonces verificamos la “documentación del programa” para verificar que significa este error. En todo caso sería que no se cumple la condición necesaria en nuestro programa “cond1”. Ejemplos: si necesitamos abrir un archivo para leer datos en él y que si no existe nos envíe un mensaje de error, entonces haríamos algo como:


if (!( fopen(“NombreArchivo”, “r”) )) exit(1);


Lo que quiere decir que: _si_ _no_ _puedo abrir el archivo NombreArchivo para leerlo_ entonces _sal del programa y envía el mensaje de error 1_. El valor de retorno “1” debe estar documentado en alguna parte indicando el tipo de error que representa... o sea, no existe el archivo NomberArchivo.


continue


Esta sentencia funciona de forma similar al break. Sin embargo, en vez de forzar la terminación del bucle, “continue” fuerza ejecutar una nueva iteración del bucle saltando cualquier instrucción entre ella y el fin del bucle. Por ejemplo:


int i;

for( i = 0; i <= 100; i++) {

if (( i % 2 ) != 1) continue;

printf(“%i ”,i);

} // continue lo manda hasta aquí y verifica el próximo caso.


imprimirá sólo los números impares entre 0 y 100, o sea, mostrará por pantalla algo como: 1 3 5 7 9 ... 95 97 99. Por qué? Cuando i=0, i%2=0 lo que es distinto de 1 y forzará una nueva iteración. Luego i=1, i%2=1 lo que no es distinto de 1 así que ejecutará el printf, luego i=2, i%2=0 lo que es distinto de 1 y ejecutará el continue, luego i=3, i%2=1, no se hace el continue, hace el printf y así sucesivamente.


El continue puede ser usado en cualquier bucle ya sea for, while o do-while.