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.
- 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
- ¿Qué es una condición?
- ¿Para qué sirve?
- ¿Cómo funciona por dentro?
- Sintaxis
- Ejemplo básico: clasificar una nota
- Ejemplo intermedio: combinar condiciones
- Caso práctico: umbral de decisión
- Visualización: qué rama se ejecuta
- switch en la práctica
- Errores comunes
- Buenas prácticas
- Ejercicios
- Soluciones explicadas
- Resumen
- 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.
#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:
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.
nota = 15
if nota >= 18:
print("Excelente")
elif nota >= 14:
print("Bueno")
else:
print("En proceso")#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
#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
#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.
#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.
#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;}nota guarda 15.—
Avanza paso a paso: observa la línea resaltada, las variables y la salida.
switch en la práctica
#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
| Error | Causa | Solución |
|---|---|---|
if (nota = 18) | Asignación en lugar de comparación | Escribe ==; -Wall suele avisar |
error: switch quantity not an integer | Intentaste hacer switch sobre un std::string | Usa if/else con == para texto |
Dos case imprimen lo mismo | Falta break | Añádelo o marca [[fallthrough]]; |
if (0.1 + 0.2 == 0.3) nunca entra | Comparación exacta de reales | Compara con tolerancia: std::abs(a - b) < 1e-9 |
El else se ejecuta cuando no debería | Rama colgante por falta de llaves | Usa 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.
#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;
}#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
boolcon nombre. - Declara los umbrales como
constexpren un solo lugar. - No compares
doublecon==; usa una tolerancia. - En
switch, cubre siempredefaulty termina cadacase. - Prefiere
ifpara rangos yswitchpara valores discretos.
Ejercicios
Clasificador de temperatura
>= 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.
Año bisiesto
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.
#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
trueofalse; en C++,0es falso y el resto verdadero. - La cadena
if / else if / elsese 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.
switchfunciona con enteros,chary enums; cadacasenecesitabreak.defaultcubre los valores no contemplados y evita fallos silenciosos.- Compara
doublecon tolerancia, nunca con==.
Evaluación
¿Qué ocurre con int x = 0; if (x = 5) { ... }?
¿Por qué se escribe if (i < v.size() && v[i] > 0) en ese orden?
¿Qué pasa si haces switch sobre una variable std::string?
¿Ya lo entendiste?
Márcala como completada para llevar tu progreso y desbloquear el siguiente paso de la ruta.