Saltar al contenido
Topos Uranos

Resumen

Un programa que solo encadena sentencias hace siempre lo mismo, cualesquiera que sean sus datos; para que responda a ellos necesita decidir. Esta clase estudia las decisiones de C++: if y else, que eligen entre dos caminos según una condición; las cadenas de else if, que eligen entre varios, y en las que el orden de las condiciones decide qué caso atiende cada rama; las decisiones anidadas y la trampa del else colgante; y switch con enum class, que eligen entre valores discretos con nombre, con un compilador que avisa si falta un caso o si uno cae en el siguiente. El concepto que unifica todo es el análisis por casos: una decisión correcta parte el conjunto de las entradas en casos disjuntos que lo cubren por completo, y cada caso se comprueba con una prueba en su interior y otra en cada uno de sus bordes.

Objetivos de aprendizaje

  1. Escribir decisiones simples, encadenadas y anidadas que atiendan todos los casos de una especificación.
  2. Verificar que los casos de una cadena de if y else if son disjuntos y exhaustivos, y diseñar una entrada de prueba por caso.
  3. Usar switch con enum class para valores discretos, y explicar qué advierte el compilador si falta un caso.
  4. Detectar el else colgante y la caída entre casos de un switch, y corregirlos.

if y else

La forma de una decisión

La clase Del problema al algoritmo presentó tres formas con las que se construye todo algoritmo: la secuencia, la decisión y la repetición. Hasta aquí los programas del curso han sido secuencias; esta clase escribe en C++ la segunda forma. Una decisión evalúa una condición, es decir, una expresión de tipo bool como las de la clase Relaciones y lógica, y ejecuta un bloque si la condición es verdadera y otro si es falsa:

Código C++
if (condición) {    // se ejecuta si la condición es verdadera} else {    // se ejecuta si la condición es falsa}

La condición va siempre entre paréntesis. La parte else es opcional: sin ella, si la condición es falsa no se hace nada y el programa sigue con la sentencia posterior a la decisión. En ambos casos los dos caminos se reúnen al final, de modo que la decisión, vista desde fuera, es una sola sentencia con una entrada y una salida, como exigía la clase 1.1.

EjemploEl resto euclídeo

La clase Aritmética mostró que a % b tiene el signo del dividendo, de modo que con a=−7a = -7 y b=2b = 2 vale −1-1, mientras que el resto euclídeo, que nunca es negativo, vale 1; y anunció que la manera segura de corregirlo es sumar bb solo cuando el resto es negativo. El programa supone b>0b > 0.

Programa en C++
#include <iostream>#include <print>​int main(){    int a{};    int b{};    std::cin >> a >> b;    int r{a % b};    if (r < 0) {        r = r + b;    }    std::println("Resto euclídeo de {} entre {}: {}", a, b, r);}
Entrada del programa
-7 2
Salida
Resto euclídeo de -7 entre 2: 1

La variable r ya no es constante, porque la decisión puede cambiarla. La figura sigue los dos caminos posibles.

Demostración

  1. La ejecución llega a la decisión con r ya calculado, y lo primero que hace es evaluar la condición r < 0: el rombo es el único punto del programa donde los caminos se separan.

  2. Si la condición es verdadera, como con −7-7 y 2, se ejecuta el bloque del if: r pasa de −1-1 a 1.

  3. Si es falsa, como con 7 y 2, el bloque se salta entero, y r conserva su valor.

  4. Ambos caminos se reúnen en el mismo punto, y desde allí el programa sigue igual en los dos casos: la sentencia que escribe el resultado se ejecuta siempre.

Con else, la decisión elige entre dos acciones que se excluyen. La condición de año bisiesto de la clase Relaciones y lógica produce un bool; una decisión lo convierte en una conducta del programa.

Programa en C++
#include <iostream>#include <print>​int main(){    int year{};    std::cin >> year;    const bool isLeap{(year % 4 == 0 && year % 100 != 0) || year % 400 == 0};    if (isLeap) {        std::println("{} es bisiesto: febrero tiene 29 días.", year);    } else {        std::println("{} no es bisiesto: febrero tiene 28 días.", year);    }}
Entrada del programa
1900
Salida
1900 no es bisiesto: febrero tiene 28 días.

Dar nombre a la condición, como isLeap, separa qué se decide de qué se hace con la decisión.

Llaves siempre

La gramática de C++ permite omitir las llaves cuando el bloque tiene una sola sentencia: if (r < 0) r = r + b; es válido. Ahora bien, sin llaves la decisión gobierna solo la sentencia siguiente, y lo que el lector cree gobernado depende de la sangría, que el compilador ignora. Si esto es así, toda modificación posterior (añadir una segunda sentencia, comentar la primera) puede cambiar el significado sin que el texto lo muestre. De ello se sigue la regla del curso: el bloque de un if o de un else lleva siempre llaves, aunque contenga una sola sentencia.

El error más corto de todos confirma la regla. Un punto y coma después de los paréntesis es una sentencia vacía, y la decisión gobierna esa sentencia vacía; el bloque que sigue ya no pertenece al if y se ejecuta siempre.

Programa en C++
#include <print>​int main(){    const int stock{12};    if (stock == 0);    {        std::println("Producto agotado");    }    std::println("Unidades disponibles: {}", stock);}
Avisos del compilador
main.cpp: In function ‘int main()’:main.cpp:6:20: warning: suggest braces around empty body in an ‘if’ statement [-Wempty-body]    6 |     if (stock == 0);      |                    ^main.cpp:6:5: warning: this ‘if’ clause does not guard... [-Wmisleading-indentation]    6 |     if (stock == 0);      |     ^~main.cpp:7:5: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘if’    7 |     {      |     ^
Salida
Producto agotadoUnidades disponibles: 12

El programa anuncia que el producto está agotado aunque quedan 12 unidades. GCC da dos avisos: -Wempty-body sugiere llaves alrededor del cuerpo vacío, y -Wmisleading-indentation señala que el bloque está sangrado como si el if lo gobernara. El otro error clásico, if (x = 0) en lugar de if (x == 0), se vio en la clase Compilar, ejecutar y configurar: la asignación vale cero, que se convierte en false, y -Wparentheses avisa. En ambos casos el programa compila; sin los avisos, el error pasaría inadvertido.

Las llaves tienen una consecuencia más: una variable declarada dentro del bloque de un if existe solo allí. Si un valor calculado en una rama se usa después de la decisión, la variable se declara antes, como r en el ejemplo del resto; el alcance se estudiará en la clase Parámetros y alcance.

El operador condicional

Cuando las dos ramas de una decisión solo eligen un valor, C++ ofrece una forma más breve: condición ? a : b vale a si la condición es verdadera y b si es falsa, y solo evalúa el elemento elegido. A diferencia de if, que es una sentencia, el operador condicional es una expresión: tiene un valor y un tipo, y puede ir donde va cualquier expresión, por ejemplo en la inicialización de una constante, que un if no permitiría sin renunciar a const.

Programa en C++
#include <iostream>#include <print>​int main(){    int a{};    int b{};    std::cin >> a >> b;    const int larger{a > b ? a : b};    std::println("Mayor: {}", larger);    std::println("{} {}", b, b == 1 ? "unidad" : "unidades");    const auto mixed{a > b ? a : 0.5};    std::println("Tipo común: {:.1f}", mixed);    std::println("Sin paréntesis: {}", 10 + a > b ? 1 : 0);    std::println("Con paréntesis: {}", 10 + (a > b ? 1 : 0));}
Entrada del programa
7 3
Salida
Mayor: 73 unidadesTipo común: 7.0Sin paréntesis: 1Con paréntesis: 11

Las dos primeras líneas muestran el uso que aclara: elegir el mayor y concordar una palabra con el número, sin repetir la sentencia entera en dos ramas. La tercera muestra que la expresión tiene un solo tipo, cualquiera que sea la rama elegida: si los dos resultados son de tipos distintos, se convierten a un tipo común con las conversiones aritméticas habituales de la clase Aritmética; aquí, un int y un double dan double, y el 7 elegido se guarda como 7.0. Las dos últimas muestran la precedencia: el operador condicional está entre los de prioridad más baja, de modo que sin paréntesis la condición es 10 + a > b y el valor, 1, en lugar de 11. Cuando el condicional forma parte de una expresión mayor, va entre paréntesis.

El operador oscurece en cuanto se le pide más que elegir un valor. Anidado, como en a > b ? (a > c ? a : c) : (b > c ? b : c), obliga a reconstruir mentalmente una decisión anidada sin las llaves que la hacen legible; y usado por sus efectos, como en ok ? std::println("sí") : std::println("no"), es un if disfrazado. La regla del curso es usarlo solo para elegir entre dos valores breves, sin anidarlo; todo lo demás se escribe con if.