AulaML Python · C++
C++ principiante · 32 min · Estructuras de control

Condicionales y switch

Tomar decisiones con if, operadores lógicos, ternario y switch

Los condicionales permiten que un programa elija entre varios caminos. En esta lección escribes cadenas if/else if/else, combinas condiciones con && y ||, usas el ternario y switch con break, y aprendes por qué los reales se comparan con tolerancia.

Lección 1 de 2 del módulo
Al terminar esta lección podrás
  • Escribir cadenas de condiciones en el orden correcto
  • Combinar comparaciones con &&, || y ! sin ambigüedad
  • Usar el operador ternario para decisiones cortas
  • Elegir entre if y switch según el tipo de dato
  • Evitar los errores clásicos: asignación en la condición, fallthrough y comparación de reales

Antes necesitas: Variables, const, auto y operadores

En esta lección
  1. ¿Qué es una condición?
  2. ¿Para qué sirve?
  3. ¿Cómo funciona por dentro?
  4. Sintaxis
  5. Ejemplo básico: clasificar una nota
  6. Ejemplo intermedio: combinar condiciones
  7. Caso práctico: umbral de decisión
  8. Visualización: qué rama se ejecuta
  9. switch en la práctica
  10. Errores comunes
  11. Buenas prácticas
  12. Ejercicios
  13. Soluciones explicadas
  14. Resumen
  15. Evaluación

Un programa deja de ser una calculadora cuando puede elegir. Esa elección se escribe con if, con el operador ternario o con switch, y siempre se reduce a lo mismo: evaluar una condición booleana y decidir qué bloque se ejecuta.

¿Qué es una condición?

Una condición es una expresión que produce true o false. En C++ cualquier valor numérico se convierte a booleano: 0 es falso y todo lo demás es verdadero. Esa flexibilidad viene de C y es una fuente de errores si la usas sin querer.

  • Comparación: a > b, a == b, a != b, a <= b.
  • Lógicos: && (y), || (o), ! (no).
  • Ternario: cond ? valor_si_true : valor_si_false.
  • switch: elige entre varios casos de un valor entero o enumerado.

¿Para qué sirve?

  • Validar entradas antes de usarlas: edad positiva, nota dentro de rango, divisor distinto de cero.
  • Clasificar valores en categorías: aprobado o desaprobado, fiebre o normal.
  • Elegir una ruta en un menú de opciones.
  • Aplicar umbrales de decisión sobre probabilidades.
  • Evitar cálculos costosos con el cortocircuito de && y ||.

En un clasificador binario, la etiqueta final sale de comparar una probabilidad con un umbral. Cambiar el umbral cambia la decisión sin reentrenar el modelo: eso es un if con consecuencias reales.

¿Cómo funciona por dentro?

El compilador evalúa la condición y genera un salto condicional: si el resultado es falso, salta al bloque siguiente. El coste es una comparación y un salto, y por eso los condicionales son baratos.

&& y || cortocircuitan: en a && b, si a es falso, b no se evalúa. Eso permite escribir if (i < v.size() && v[i] > 0) sin salirte del rango. El orden importa: si inviertes los operandos, el acceso ocurre antes de la comprobación.

Los double no se comparan con == salvo que sepas exactamente cómo se construyeron. 0.1 + 0.2 no es 0.3 en binario; la diferencia es minúscula pero suficiente para que la igualdad falle.

CPP comparar_reales.cpp
#include <cmath>
#include <iomanip>
#include <iostream>

int main() {
    double a = 0.1 + 0.2;
    double b = 0.3;

    std::cout << std::setprecision(17);
    std::cout << "0.1 + 0.2 = " << a << '\n';
    std::cout << "a == b:    " << (a == b ? "true" : "false") << '\n';
    std::cout << "casi igual: " << (std::abs(a - b) < 1e-9 ? "true" : "false") << '\n';
    return 0;
}

La salida confirma el problema: 0.30000000000000004 frente a 0.3. La comparación con tolerancia std::abs(a - b) < 1e-9 es la forma correcta cuando trabajas con medidas o con resultados de un modelo.

Sintaxis

La forma canónica encadena condiciones y deja el caso general en el else:

CPP cadena de condiciones
if (condicion1) {
    // se ejecuta si condicion1 es true
} else if (condicion2) {
    // se ejecuta si condicion1 es false y condicion2 es true
} else {
    // ningún caso anterior
}

Las llaves son opcionales cuando el bloque tiene una sola sentencia, pero el curso las exige siempre: evitan el error de la rama colgante y facilitan añadir líneas después.

El ternario devuelve un valor, no ejecuta bloques: std::string estado = nota >= 11 ? "aprobado" : "desaprobado";. Úsalo para expresiones cortas; si anidas dos, pierdes legibilidad.

switch compara un valor entero o char contra etiquetas case constantes. Cada case termina en break (o en return) si no quieres que continúe; default cubre el resto.

La misma decisión en los dos lenguajes
Python
PYTHON nota.py
nota = 15

if nota >= 18:
    print("Excelente")
elif nota >= 14:
    print("Bueno")
else:
    print("En proceso")
C++
CPP nota.cpp
#include <iostream>

int main() {
    int nota = 15;
    if (nota >= 18) {
        std::cout << "Excelente\n";
    } else if (nota >= 14) {
        std::cout << "Bueno\n";
    } else {
        std::cout << "En proceso\n";
    }
    return 0;
}

Python usa `elif` y bloques por indentación; C++ usa `else if`, llaves y paréntesis en la condición. La condición de Python no necesita paréntesis; en C++ son parte de la sintaxis.

Ejemplo básico: clasificar una nota

CPP notas.cpp
#include <iostream>

int main() {
    int nota = 15;

    if (nota >= 18) {
        std::cout << "Excelente\n";
    } else if (nota >= 14) {
        std::cout << "Bueno\n";
    } else if (nota >= 11) {
        std::cout << "Aprobado\n";
    } else {
        std::cout << "Desaprobado\n";
    }
    return 0;
}

El orden de las condiciones importa. nota >= 18 se comprueba primero; si es falsa, se pasa a nota >= 14, y así. Si empezaras por nota >= 11, una nota de 20 caería en «Aprobado» porque la primera condición ya sería verdadera.

Con nota = 15 la salida es Bueno: 15 no llega a 18, pero sí a 14. Cambia el valor a 10 y comprueba que ahora entra por el else final.

Ejemplo intermedio: combinar condiciones

CPP acceso.cpp
#include <iostream>

int main() {
    int edad = 20;
    bool tiene_entrada = true;
    bool vetado = false;

    bool puede_entrar = edad >= 18 && tiene_entrada && !vetado;

    std::cout << "¿Puede entrar? " << (puede_entrar ? "sí" : "no") << '\n';
    return 0;
}

puede_entrar combina tres condiciones con && y una negación. El resultado es un bool que después se usa en el ternario. Evaluar por partes con nombres claros (tiene_entrada, vetado) hace que la condición se lea casi como una frase.

Añade || y la cosa se complica: edad >= 18 || tiene_permiso deja pasar a un menor con permiso. Los paréntesis no son obligatorios por precedencia (&& antes que ||), pero casi siempre aclaran la intención.

Caso práctico: umbral de decisión

Un modelo devuelve una probabilidad; el programa decide. Este esquema es el corazón de la inferencia binaria y permite comparar umbrales sin tocar el modelo.

CPP umbral.cpp
#include <iomanip>
#include <iostream>

int main() {
    double probabilidad = 0.73;
    constexpr double umbral = 0.5;

    std::cout << std::fixed << std::setprecision(2);
    std::cout << "P(clase 1) = " << probabilidad << '\n';

    if (probabilidad >= umbral) {
        std::cout << "Predicción: clase 1\n";
    } else {
        std::cout << "Predicción: clase 0\n";
    }

    // Un umbral más exigente cambia la decisión
    constexpr double exigente = 0.9;
    std::cout << "Con umbral 0.90: "
              << (probabilidad >= exigente ? "clase 1" : "clase 0") << '\n';
    return 0;
}

Con P = 0.73 y umbral 0.5, la predicción es clase 1. Con umbral 0.90, la misma probabilidad ya no alcanza y la predicción cambia a clase 0. El modelo no cambió; cambió la regla. En problemas con clases desbalanceadas, subir el umbral reduce falsos positivos a costa de más falsos negativos.

La comparación usa >=, no >. La diferencia solo importa cuando la probabilidad es exactamente el umbral, pero conviene decidirla y documentarla.

Visualización: qué rama se ejecuta

La traza muestra las dos comparaciones y el momento en que se elige una rama. Observa que la cadena se detiene en cuanto una condición es verdadera: las siguientes no se evalúan.

Ejecución visual ·Una cadena if / else if / else
1 / 6
1 #include <iostream>
2
3 int main() {
4 int nota = 15;
5 if (nota >= 18) {
6 std::cout << "Excelente\n";
7 } else if (nota >= 14) {
8 std::cout << "Bueno\n";
9 } else {
10 std::cout << "En proceso\n";
11 }
12 return 0;
13 }
nota guarda 15.
Variables
nota = 15
Salida
—

Avanza paso a paso: observa la línea resaltada, las variables y la salida.

switch en la práctica

CPP menu.cpp
#include <iostream>

int main() {
    int opcion = 2;

    switch (opcion) {
        case 1:
            std::cout << "Entrenar modelo\n";
            break;
        case 2:
            std::cout << "Evaluar modelo\n";
            break;
        case 3:
            std::cout << "Exportar métricas\n";
            break;
        default:
            std::cout << "Opción no válida\n";
            break;
    }
    return 0;
}

switch no admite std::string; para texto se usa if o se compara con ==. Sí admite char, enums y enteros. default no es obligatorio, pero omitirlo deja casos sin cubrir y el compilador no siempre avisa.

Si olvidas un break, la ejecución cae al siguiente case: es el fallthrough. A veces se busca a propósito (varios casos comparten código), y entonces se documenta con [[fallthrough]];. Si no es intencional, es un error silencioso.

Errores comunes

ErrorCausaSolución
if (nota = 18)Asignación en lugar de comparaciónEscribe ==; -Wall suele avisar
error: switch quantity not an integerIntentaste hacer switch sobre un std::stringUsa if/else con == para texto
Dos case imprimen lo mismoFalta breakAñádelo o marca [[fallthrough]];
if (0.1 + 0.2 == 0.3) nunca entraComparación exacta de realesCompara con tolerancia: std::abs(a - b) < 1e-9
El else se ejecuta cuando no deberíaRama colgante por falta de llavesUsa llaves siempre
expected primary-expression before 'else'Punto y coma después de if (...)Quita el ; antes del bloque

El primero merece énfasis: if (x = 0) asigna 0 a x y evalúa 0, así que la rama nunca se ejecuta. Es un error que compila sin quejarse en muchos casos.

El case que se cuela
Incorrecto
PYTHON Incorrecto
#include <iostream>

int main() {
    int opcion = 1;

    switch (opcion) {
        case 1:
            std::cout << "Uno\n";
        case 2:
            std::cout << "Dos\n";
            break;
        default:
            break;
    }
    return 0;
}
Correcto
PYTHON Correcto
#include <iostream>

int main() {
    int opcion = 1;

    switch (opcion) {
        case 1:
            std::cout << "Uno\n";
            break;
        case 2:
            std::cout << "Dos\n";
            break;
        default:
            break;
    }
    return 0;
}

Sin el `break`, la ejecución continúa en el `case 2` e imprime las dos líneas. El programa compila y no avisa: solo se detecta probando con la opción 1. Ese silencio es lo que hace peligroso el fallthrough accidental.

Buenas prácticas

  • Escribe llaves aunque el bloque sea de una línea.
  • Ordena las condiciones de lo más específico a lo más general.
  • Extrae condiciones complejas a variables bool con nombre.
  • Declara los umbrales como constexpr en un solo lugar.
  • No compares double con ==; usa una tolerancia.
  • En switch, cubre siempre default y termina cada case.
  • Prefiere if para rangos y switch para valores discretos.

Ejercicios

Ejercicio 1

Clasificador de temperatura

Escribe un programa que clasifique una temperatura corporal: >= 39.0 es fiebre alta, >= 37.5 es fiebre, >= 36.0 es normal y por debajo es hipotermia. Prueba con 38.5, 36.5 y 35.0, y comprueba que cada valor cae en la categoría correcta.
Pista

Ordena las comparaciones de mayor a menor. Si empiezas por la más baja, todos los valores altos caerán en la primera categoría.

Ejercicio 2

Año bisiesto

Un año es bisiesto si es divisible entre 4 y no entre 100, o si es divisible entre 400. Escribe la condición en una sola variable bool y muestra el resultado con un ternario. Verifica con 2024 (bisiesto), 1900 (no bisiesto) y 2000 (bisiesto).
Pista

Combina % con && y ||. Recuerda que && tiene más prioridad que ||; los paréntesis aclaran la regla.

Completar código Completa la decisión
#include <iostream>

int main() {
    int nota = 16;
    if (nota  14) {
        std::cout << "Aprobado\n";
    }  {
        std::cout << "Desaprobado\n";
    }
    return 0;
}

La segunda rama se ejecuta cuando la condición es falsa.

Soluciones explicadas

En el clasificador de temperatura, la decisión clave es el orden. Las condiciones se evalúan de arriba abajo y la primera verdadera gana, así que poner >= 36.0 antes que >= 39.0 haría que una fiebre alta se etiquetara como normal. Ese error no lo detecta el compilador: solo una prueba con valores de cada rango.

En el año bisiesto, los paréntesis traducen la regla del calendario a precedencia explícita. Sin ellos, && seguiría agrupando igual, pero el lector tendría que reconstruir la regla mentalmente. Probar 1900 y 2000 es lo que separa una condición correcta de una que solo funciona con años recientes.

En el ejercicio de completar, la diferencia entre = y >= es la misma que separa un programa correcto de uno que compila y miente. El else no lleva condición porque representa el complemento exacto de la anterior.

Resumen

  • Una condición produce true o false; en C++, 0 es falso y el resto verdadero.
  • La cadena if / else if / else se detiene en la primera condición verdadera.
  • && y || cortocircuitan: el orden de los operandos puede evitar accesos inválidos.
  • El ternario devuelve un valor y sirve para decisiones cortas.
  • switch funciona con enteros, char y enums; cada case necesita break.
  • default cubre los valores no contemplados y evita fallos silenciosos.
  • Compara double con tolerancia, nunca con ==.

Evaluación

Evaluación 1

¿Qué ocurre con int x = 0; if (x = 5) { ... }?

Evaluación 2

¿Por qué se escribe if (i < v.size() && v[i] > 0) en ese orden?

Evaluación 3

¿Qué pasa si haces switch sobre una variable std::string?

Tus notas

¿Ya lo entendiste?

Márcala como completada para llevar tu progreso y desbloquear el siguiente paso de la ruta.