Herramientas de usuario

Herramientas del sitio


curso-cpp:estructuras-selectivas

Estructuras de control selectivas

Hasta ahora, la lista de instrucciones de nuestro programa que se ejecutarán está fija: Esto es, siempre especificamos una lista de instrucciones, y cada una de ellas es ejecutada en orden, de arriba hacia abajo.

Hay veces en las que solamente queremos llevar a cabo una acción determinada a veces, cuando se cumple una cierta condición particular que hace que queramos llevar a cabo la tarea. Veremos en esta lección cómo se puede lograr esto en C++.

La instrucción if

La instrucción if sirve para instruir a la computadora a que lleve a cabo un determinado conjunto de instrucciones, únicamente cuando se cumpla una condición específica.

Versión común

La sintaxis (forma de escritura) de la instrucción if es la siguiente:

if (condicion)
{
    instruccion1;
    instruccion2;
    //...
    instruccionFinal;
}

Notar que los paréntesis alrededor de la condición son obligatorios. De manera similar a lo que ocurre en main, donde escribimos todas las instrucciones que queremos que se ejecuten, las llaves { y } delimitan un bloque de instrucciones, que únicamente se ejecutarán cuando se cumpla la condición. Si la condición no se cumple, se saltearán todas las instrucciones encerradas entre llaves.

Por ejemplo, el siguiente programa puede utilizarse para leer un número, y escribir en pantalla un mensaje de acuerdo a su signo:

#include <iostream>
 
using namespace std;
 
int main()
{
    int x;
    cin >> x;
    if (x > 0)
    {
        cout << "El numero " << x << " es positivo." << endl;
    }
    if (x < 0)
    {
        cout << "El numero " << x << " es negativo." << endl;
    }
    return 0;
}

Notar algo muy importante, que es que indentamos (colocamos espacios a la izquierda de) las instrucciones del if que encerramos entre llaves: Esto lo hacemos por claridad, para que sea mucho más fácil leer los programas. Cada vez que ponemos un bloque de instrucciones entre llaves, es conveniente que todas las instrucciones contenidas estén más a la derecha visualmente que las instrucciones que rodean al bloque, para que sea más fácil entender a simple vista dónde comienza y termina el bloque de instrucciones del if. Notar que al compilador no le importan los espacios, y únicamente se basa en las llaves para decidir hasta dónde llega el if. Pero es importante para poder leer y entender más fácil el código, utilizar prolijamente los espacios.

Vemos aquí nuestro primer ejemplo de condición: x > 0 es una condición que puede colocarse en un if (entre paréntesis), y que se cumple justamente cuando la expresión x es mayor que cero. Como x es directamente una variable con el valor que leímos, esto se cumplirá cuando el número ingresado por el usuario sea mayor que cero. En dicho caso (¡y solo en dicho caso!) el programa ejecutará las instrucciones entre llaves que siguen al if. En este caso, es una única instrucción que muestra un mensaje indicando que el número es positivo.

Similarmente, luego de verificar la primera condición y (quizás) ejecutar lo indicado entre llaves, se llega en el programa al segundo if: En este se verifica la condición x < 0, y por lo tanto las instrucciones entre llaves que siguen a este if únicamente serán ejecutadas cuando el número ingresado sea negativo.

Por ejemplo, si ingresamos el número 4 veremos en pantalla al ejecutar la siguiente interacción:

4
El numero 4 es positivo.


------------------
(program exited with code: 0)
Press return to continue

En cambio, si ingresamos un valor de -2, al ejecutar veremos en pantalla lo siguiente:

-2
El numero -2 es negativo.


------------------
(program exited with code: 0)
Press return to continue

Si ingresamos un valor 0, veremos lo siguiente:

0


------------------
(program exited with code: 0)
Press return to continue

¡Vemos en este caso que el programa no imprime ningún mensaje! ¿Por qué ocurre esto?

La computadora ejecuta las instrucciones indicadas una por una en orden. En particular, cuando llega a cada if, verifica la condición correspondiente para ver si se cumple, y solo ejecuta la instrucción entre llaves (de impresión del mensaje) cuando la condición se cumple. Por lo tanto si este programa no imprime nada, debería ser porque cuando se ingresa el valor 0, ninguna de las dos condiciones se cumple, y no se ejecuta ninguno de los ifs.

En efecto, cero es un número especial, el único entero que no es ni positivo ni negativo, y por lo tanto 0 < 0 y 0 > 0 son ambas falsas. Podemos entonces agregar este caso como un nuevo if en el programa:

#include <iostream>
 
using namespace std;
 
int main()
{
    int x;
    cin >> x;
    if (x > 0)
    {
        cout << "El numero " << x << " es positivo." << endl;
    }
    if (x < 0)
    {
        cout << "El numero " << x << " es negativo." << endl;
    }
    if (x == 0)
    {
        cout << "El numero ingresado es cero." << endl;
    }
    return 0;
}

Si ejecutamos ahora los mismos ejemplos de antes, obtendremos los mismos resultados en los primeros dos casos: pero al ingresar el valor cero, ahora observaremos lo siguiente:

0
El numero ingresado es cero.


------------------
(program exited with code: 0)
Press return to continue

Notar que en nuestra tercera condición, hemos utilizado por primera vez el operador ==: Este operador no tiene nada que ver con el =: Recordemos que el operador = se utiliza para la asignación de variables (“meter valores en cajas”). El == en cambio se utiliza para expresar condiciones, y funciona como la igualdad matemática.

El == es un ejemplo de los que se denominan operadores de comparación. Los veremos en más detalle muy pronto.

Veamos un segundo ejemplo de programa, que muestre un mensaje de acuerdo a la paridad del número ingresado:

int main()
{
    int x;
    cin >> x;
    if (x % 2 == 0)
    {
        cout << "El numero " << x << " es par." << endl;
    }
    if (x % 2 == 1)
    {
        cout << "El numero " << x << " es impar." << endl;
    }
    return 0;
}

Recordemos que % es el operador de “resto de la división”, también llamado módulo. Para saber si un número es par o impar, debemos considerar el resto de la división por 2. Cuando el resto sea 0, el número es par, y cuando sea 1, es impar. Esas son las condiciones que verificamos en los ifs de este programa, y en cada caso, imprimimos un mensaje apropiado para la condición que se cumple.

El else

En el último ejemplo, verificábamos si un número era par o impar, e imprimíamos un mensaje de acuerdo a la paridad. Notemos que en este caso tenemos dos opciones excluyentes: O bien el número es par, y entonces solamente se imprime el mensaje para el caso par, o bien esto no ocurre(porque el número es impar), y entonces solamente se imprime el mensaje para el caso impar.

Esta situación, en la cual hay una cierta condición, y se debe ejecutar un conjunto de instrucciones cuando se cumple la condición, y otro conjunto cuando no se cumple, es muy común. Para ello existe una parte opcional de la instrucción if, que hasta ahora no hemos utilizado, y es la sección else.

Es posible escribir lo siguiente:

if (condicion)
{
   instruccionA1;
   instruccionA2;
   instruccionA3;
   ...
}
else
{
   instruccionB1;
   instruccionB2;
   instruccionB3;
   ...
}

En este caso, el primer bloque de instrucciones (instruccionA1, instruccionA2, etc) corresponde al “if normal”, y se ejecutará cuando la condición indicada entre paréntesis ocurra. En cambio, el segundo bloque de instrucciones (instruccionB1, instruccionB2, etc) se pone a continuación de else, y se ejecuta cuando la condición del if no ocurre. De esta forma, uno solo de los bloques de instrucciones será ejecutado, dependiendo de si la condición del if vale o no.

A continuación mostramos el ejemplo anterior de par o impar, reescrito utilizando la instrucción else:

int main()
{
    int x;
    cin >> x;
    if (x % 2 == 0)
    {
        cout << "El numero " << x << " es par." << endl;
    }
    else
    {
        cout << "El numero " << x << " es impar." << endl;
    }
    return 0;
}

En este caso, en lugar de utilizar un segundo if con la condición (x % 2 == 1), como esta condición es la que ocurre exactamente cuando no ocurre la primera, podemos simplemente extender el primer if con un else, para indicar qué hacer cuando el número no es par (en cuyo caso, será impar).

Operadores de comparación

Para expresar las condiciones anteriores, hemos utilizado los operadores <, >, e ==. Estos operadores se utilizan para expresar condiciones, mediante la comparación de otros dos valores. Así, x > 10 expresa la condición de que el valor almacenado en x debe ser mayor que 10. Similarmente, x + y == z expresa la condición de que el valor almacenado en x, más el valor almacenado en y, debe ser igual al valor almacenado en z.

Estos operadores se llaman operadores de comparación. A continuación mostramos los más importantes operadores de comparación, junto a un texto que indica su significado:

== "Igual a"
!= "Distinto de"
<  "Menor a"
>  "Mayor a"
<= "Menor o igual a"
>= "Mayor o igual a"

Cada uno de estos operadores puede utilizarte para comparar los valores de dos expresiones, obteniéndose así una condición que puede utilizarse en un if. Recordar siempre que el operador de comparación “==”, y el operador de asignación “=” son completamente diferentes, y mezclarlos puede llevar a errores en el comportamiento del programa.

Ya hemos usado estos operadores con valores de tipo int: en dicho caso, la comparación se realiza por valor numérico. También es posible utilizar los operadores de comparación con variables string (textos): En este caso, las palabras se ordenan en el orden del diccionario. El nombre técnico para el orden del diccionario (Donde primero van las palabras con A, luego con B, luego con C, etc) es “orden lexicográfico”. Por ejemplo, tendremos que “vaca” > “sopa”, y “abcd” < “bcda”. Notar sin embargo que como ya mencionamos, a cada caracter (char) corresponde un valor numérico ASCII, y las mayúsculas y minúsculas tienen valores distintos. Como C++ ordena las variables de tipo string usando estos códigos, cadenas que mezclen mayúsculas y minúsculas no se ordenarán según el orden normal del diccionario, ya que en ASCII todas las mayúsculas vienen antes que todas las minúsculas. Así por ejemplo, si bien “burro” > “agua”, tenemos que “Burro” < “agua”, pues la 'B' viene antes que la 'a', que viene antes que la 'b'.

Versión con una única instrucción

Hemos visto que la sintaxis (escritura) completa del if tiene la siguiente forma:

if (condicion)
{
    instrucciones;
}
else
{
    instrucciones;
}

Además, ya hemos mencionado que la parte del else, para especificar qué hacer cuando no se cumple la condición, es opcional. Una opción adicional que existe en C++ en el caso del if (o el else), es la de no utilizar las llaves cuando el bloque de instrucciones que delimitan contiene una sola instrucción. En este caso, si no usamos llaves, C++ asumirá que la primera instrucción que sigue a continuación conforma el bloque completo.

Veamos algunos ejemplos:

// Escritura completa
if (x > 0)
{
    cout << "El numero es positivo" << endl;
}
cout << "Fin del programa" << endl;
 
// Escritura sin llaves
if (x > 0)
    cout << "El numero es positivo" << endl;
cout << "Fin del programa" << endl;

Los dos ejemplos anteriores son equivalentes, ya que hay una sola instrucción entre llaves. Notemos en cambio que los siguientes dos ejemplos no son equivalentes:

// Escritura completa
if (x > 0)
{
    cout << "El numero ";
    cout << "es positivo" << endl;
}
cout << "Fin del programa" << endl;
 
// Escritura sin llaves:
// CAMBIA EL SIGNIFICADO! 
// Las llaves anteriores NO SE PUEDEN OMITIR.
if (x > 0)
    cout << "El numero ";
    cout << "es positivo" << endl;
cout << "Fin del programa" << endl;
 
// La version anterior sin llaves equivale a esto:
if (x > 0)
{
    cout << "El numero ";
}
cout << "es positivo" << endl;
cout << "Fin del programa" << endl;

En el ejemplo anterior, vemos que omitir las llaves cuando el bloque de instrucciones que queremos ejecutar tiene más de una instrucción es un error. Solamente es posible omitir las llaves, cuando el bloque tiene una única instrucción. Ante la duda, o posibilidad de confusión, es mejor dejar las llaves aunque se utilice una única instrucción, para evitar problemas.

Existe un peligro más cuando omitimos las llaves, y este peligro es el resultado de que en C++ la parte else sea opcional. Supongamos un código como el siguiente:

// Ejemplo 1 (resulta correcto)
if (x > 0)
    if (x % 2 == 0)
        cout << "Positivo par" << endl;
    else
        cout << "Positivo impar" << endl;
 
// Ejemplo 2 (resulta incorrecto)
 
if (x > 0)
    if (x % 2 == 0)
        cout << "Positivo par" << endl;
else
    cout << "Negativo" << endl;

Notemos que, más allá de los mensajes que se van a mostrar, la única diferencia entre el primer y el segundo ejemplo es la indentación (cantidad de espacios a la izquierda) del else. Pero al compilador no le importa la cantidad de espacios. Por lo tanto, ambos casos son interpretados de idéntica manera. En este caso... ¿Corresponde para C++ el else al primer if, como querríamos en el ejemplo2, o corresponde al segundo, como querríamos en el ejemplo 1?

La regla que sigue C++, que determina cómo resolver esta confusión cuando no se usan llaves en el if, es que un else en el código corresponde al if inmediatamente anterior. Es decir, el compilador interpreta la situación como querríamos en el ejemplo 1, con lo cual el ejemplo 2 nos resulta incorrecto. Para escribir lo que querríamos en el ejemplo 2, es necesario sí o sí utilizar llaves.

Veamos a continuación entonces cómo escribir los ejemplos con la escritura completa con llaves:

// Ejemplo 1 (con llaves, correcto)
if (x > 0)
{
    if (x % 2 == 0)
    {
        cout << "Positivo par" << endl;
    }
    else
    {
        cout << "Positivo impar" << endl;
    }
}
 
// Ejemplo 2 (con llaves, correcto)
 
if (x > 0)
{
    if (x % 2 == 0)
    {
        cout << "Positivo par" << endl;
    }
}
else
{
    cout << "Negativo" << endl;
}

Notar que ahora en el ejemplo 2, al tener todas las llaves, el compilador no puede confundirse y emparejar el else con el if interno, ya que ese if se encuentra dentro del bloque de instrucciones entre llaves, y por lo tanto no puede corresponderse con un else que está por fuera de las llaves.

Ejercicios

Operadores lógicos

A veces, queremos expresar condiciones compuestas, en base a otras condiciones más simples. Los operadores lógicos sirven para obtener tales condiciones. A continuación mostramos los operadores lógicos más comunes, junto con su nombre:

&& "and"
|| "or"
!  "not"

El operador &&, denominado “and”, se utiliza para formar una condición compuesta en la cual se exige que otras dos condiciones se cumplan al mismo tiempo. Por ejemplo, x > 0 && x % 2 == 0 expresa la condición de que x sea un número par positivo, indicando que deben cumplirse tanto x > 0 como x % 2 == 0. x > 0 && x < 0, por ejemplo, denota una condición imposible de cumplir, ya que se pide que x sea positivo y negativo.

El operador ||, denominado “or”, se utiliza para formar una condición compuesta en la cual se exige que al menos una de otras dos condiciones se cumpla. Así por ejemplo, cero será el único número que no cumple la condición x > 0 || x < 0: En esta condición se pide que x sea positivo o negativo. El único número que no cumple ninguna de ellas es el cero. Otro ejemplo es x % 2 == 0 || x % 3 == 0, en el cual se pide que x sea múltiplo de 2 o de 3. Además, si x resulta ser múltiplo de ambos (como por ejemplo, 12), la condición igualmente se cumple, pues con una sola que se cumpla basta, y si se cumplen las dos “mejor”.

Finalmente, el operador ! se usa para invertir una condición dada, que generalmente deberá encerrarse entre paréntesis. Por ejemplo, !(x < 0) es completamente equivalente a (x >= 0). Por otro lado, !(x % 2 == 0 || x % 3 == 0), por ejemplo, estaría negando la condición anterior de que el número x sea múltiplo de 2 o de 3, y por lo tanto esta condición solamente será cierta cuando el número no sea múltiplo ni de 2 ni de 3.

Utilizando estos operadores lógicos, podemos muchas veces resumir largas cadenas de ifs, en una sola condición compuesta más clara. Veamos por ejemplo el siguiente ejemplo para decidir si un año es bisiesto:

#include <iostream>
 
using namespace std;
 
int main()
{
    int year;
    cin >> year;
    if (year % 400 == 0)
        cout << "Es bisiesto." << endl;
    else
    {
        // En este caso, no es multiplo de 400
        if (year % 100 == 0)
            cout << "No es bisiesto " << endl;
        else
        {
            // En este caso, no es multiplo de 100
            if (year % 4 == 0)
                cout << "Es bisiesto." << endl;
            else
                cout << "No es bisiesto." << endl;
        }
    }
    return 0;
}

Si bien es correcto, este código puede resumirse en el siguiente mucho más claro, utilizando expresiones lógicas:

#include <iostream>
 
using namespace std;
 
int main()
{
    int year;
    cin >> year;
    if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0))
        cout << "Es bisiesto." << endl;
    else
        cout << "No es bisiesto " << endl;
    return 0;
}

Notar que encerramos entre paréntesis la condición (year % 4 == 0 && year % 100 != 0). Siempre es conveniente utilizar paréntesis para indicar el orden de las operaciones lógicas, cuando encadenamos una mezcla de operaciones || y &&.

El tipo bool

Ya hemos visto los tipos int, string y char. Veremos ahora un tipo de datos adicional: El tipo bool.

Un bool representa el resultado de analizar si una condición es cierta o falsa. Por lo tanto, un valor bool representa un sí o un no. En C++, el sí se escribe true (del inglés “verdadero”) y el no se escribe false (del inglés, “falso”).

Ahora podemos entender que todos los operadores de comparación que vimos, así como todos los operadores lógicos, operan con expresiones y producen resultados de tipo bool: true cuando la condición analizada es cierta, y false cuando la condición analizada no lo es.

Por ejemplo, en el caso de los operadores de comparación, 1 < 2 da por resultado true, que es un valor de tipo bool. 1 == 2 es otro valor de tipo bool, que será false. Como con cualquier otro tipo de datos, podemos declarar variables bool:

bool cond1 = 1 == 2;
bool cond2 = 1 < 2;
if (cond1)
    cout << "Esto no se ejecuta" << endl;
if (cond2)
    cout << "Esto si se ejecuta" << endl;
if (cond1 || cond2)
    cout << "Esto si se ejecuta" << endl;
if (cond1 && cond2)
    cout << "Esto no se ejecuta" << endl;

En este ejemplo, vemos que las operaciones lógicas || y && operan con 2 valores de tipo bool: || da por resultado true cuando al menos uno de los operandos lo es, y && devuelve true solamente cuando ambos operandos lo son.

Además, en este ejemplo vemos que lo que hemos llamado condición, y que se debe colocar entre paréntesis en el if, en realidad puede ser cualquier expresión de tipo bool. Esto permite guardar valores bool intermedios en variables, y usarlos libremente en expresiones compuestas mediante operadores lógicos y de comparación.

Más ejercicios

curso-cpp/estructuras-selectivas.txt · Última modificación: 2017/10/29 19:28 por santo