Los bucles son estructuras de control que permiten repetir un bloque de código múltiples veces. Cuando necesitas ejecutar instrucciones mientras se cumpla una condición (como esperar a que un sensor cambie su estado), usas un while. Si el código debe ejecutarse al menos una vez antes de evaluar la condición (como mostrar un menú de opciones), el do-while es la herramienta adecuada. Para iteraciones donde el número de repeticiones es conocido o depende de un contador, el for es el estándar; este encapsula la inicialización de la variable, la condición de parada y el incremento en una sola línea, manteniendo el alcance de la variable de control limitado al propio bucle.
Para recorrer colecciones como std::vector o std::map, el range-based for [C++11] ofrece una sintaxis limpia que, por debajo, utiliza las funciones begin() y end() del contenedor para gestionar el recorrido. Si trabajas con estructuras compuestas, como los pares de un mapa, los structured bindings [C++17] te permiten desestructurar esos elementos en variables individuales de forma inmediata. Si fallas al gestionar estas estructuras —por ejemplo, creando bucles infinitos por error o modificando la estructura de un contenedor mientras lo recorres—, te enfrentarás a bloqueos del programa o a comportamientos indefinidos que son un dolor de cabeza en producción.
#include <iostream>
#include <vector>
#include <map>
#include <string>
struct Tarea {
int id;
std::string nombre;
bool completada;
};
int main() {
// Escenario: Gestión de un sistema de tareas
std::vector<Tarea> tareas = {
{1, "Configurar entorno", true},
{2, "Escribir código", false},
{3, "Depurar errores", false},
{4, "Subir a producción", false}
};
// 1. while: Procesar hasta encontrar un estado específico
size_t idx = 0;
std::cout << "--- Buscando la tarea ID 3 (while) ---\n";
while (idx < tareas.size()) {
if (tareas[idx].id == 3) {
std::cout << "¡Tarea 3 localizada!\n";
break; // Sale inmediatamente del bucle actual
}
std::cout << "Revisando ID: " << tareas[idx].id << "...\n";
++idx;
}
// 2. for tradicional: Inicialización múltiple con la coma
std::cout << "\n--- Contador de progreso (for tradicional) ---\n";
for (int i = 3, j = 0; i > 0; --i, ++j) {
std::cout << "Paso " << j << " de " << i << " restantes\n";
}
// 3. range-based for con structured bindings [C++17] y continue
std::map<std::string, int> prioridades = {
{"Alta", 1}, {"Media", 2}, {"Baja", 3}
};
std::cout << "\n--- Reporte de niveles (range-based + structured bindings) ---\n";
for (const auto& [nivel, valor] : prioridades) {
if (valor == 2) continue; // Salta la iteración actual (Media)
std::cout << "Prioridad: " << nivel << " (ID: " << valor << ")\n";
}
// 4. range-based for con referencia para modificar elementos
std::cout << "\n--- Finalizando tareas pendientes (range-based con referencia) ---\n";
for (auto& tarea : tareas) {
if (!tarea.completada) {
tarea.completada = true; // Modifica el objeto original en el vector
}
}
return 0;
}
Análisis del código
En el primer bloque, el while utiliza una variable idx para recorrer el std::vector. Fíjate en que la condición idx < tareas.size() es vital para evitar acceder a memoria fuera de los límites del vector. El uso de break interrumpe el flujo y sale del bucle en cuanto la condición interna se cumple.
En el for tradicional, hemos aprovechado que la sección de inicialización permite declarar múltiples variables (i y j) separadas por una coma. Esto es útil para mantener contadores sincronizados. Además, el incremento --i, ++j permite actualizar ambas variables en cada iteración.
Para el reporte de prioridades, usamos un range-based for sobre un std::map. Gracias a los structured bindings [nivel, valor], el compilador descompone automáticamente el std::pair que contiene cada elemento del mapa en dos variables locales, haciendo el código mucho más legible que usar elem.first y elem.second. El uso de continue permite omitir la iteración cuando la prioridad es “Media”, saltando directamente a la siguiente.
Finalmente, en el último bucle, empleamos auto& tarea. El uso de la referencia & es crucial: si hubiéramos escrito auto tarea (por valor), estaríamos trabajando sobre una copia de la tarea y el cambio tarea.completada = true no afectaría al vector original. Para máxima eficiencia y generalidad, en plantillas o tipos desconocidos, lo ideal es usar auto&&, que funciona tanto para lvalues como para rvalues.
El error frecuente
Un error clásico es modificar un contenedor mientras lo recorres con un range-based for.
std::vector<int> numeros = {1, 2, 3};
for (int n : numeros) {
if (n == 2) {
numeros.push_back(4); // ¡PELIGRO!
}
}
Al hacer push_back, el std::vector puede necesitar reasignar su memoria interna si se queda sin capacidad. Esto invalida todos los iteradores internos que el range-based for está usando de forma implícita. El resultado es un comportamiento indefinido (Undefined Behavior) que suele manifestarse como un crash aleatorio o corrupción de memoria. Si necesitas modificar el tamaño de un contenedor mientras lo recorres, utiliza un iterador manual o un índice, o planifica las inserciones/borrados para realizarlos después del bucle.
N° 23