"Para cada elemento, haz esto"
El clásico bucle for con contador es estupendo cuando tienes un índice que controlar. Pero la mayoría de las veces el índice no te importa en realidad: solo quieres tocar cada elemento de un contenedor. Escribir for (int i = 0; i < v.size(); i++) para eso es ruidoso, y i está a un error de desfase por uno de leer fuera de los límites.
C++11 añadió el bucle for basado en rango justo para esto. Nombras una variable, apuntas a un contenedor y el bucle recorre cada elemento por ti:
Sin índice, sin .size(), sin límites que equivocar. Léelo como "para cada s en scores." Funciona con arrays nativos, std::vector, std::string, std::map y cualquier otra cosa que exponga begin() y end().
Deja que auto elija el tipo
Escribir el tipo del elemento a mano funciona, pero es frágil: cambia el tipo del contenedor y todos los bucles tienen que cambiar también. Combina el for basado en rango con auto y el compilador deducirá el tipo del elemento por ti:
Aun así, aquí hay un coste oculto. El simple auto name deduce string y copia cada elemento en name en cada pasada. Para un int eso es gratis; para un string o una estructura grande es una asignación desperdiciada en cada iteración. La solución son las referencias, que es lo siguiente que hay que entender.
Modificar en el sitio con auto&
Si escribes auto x, obtienes una copia, así que asignar a x cambia la copia, no el contenedor. Observa esta trampa:
La multiplicación no hace nada en silencio porque n es una copia desechable. Para editar de verdad los elementos, tómalos por referencia con auto&:
El único & marca toda la diferencia entre "mirar pero no tocar" y "editar en el sitio." Si alguna vez te preguntas por qué desaparecen tus cambios, casi siempre es por esto.
Leer sin copiar: const auto&
Cuando solo necesitas leer elementos pero son costosos de copiar, usa const auto&. La referencia evita la copia, y const documenta (y obliga) que no vas a modificar nada:
Una buena regla práctica:
for (auto x : c) // copia - tipos baratos (int, char, punteros)
for (auto& x : c) // editar - quieres cambiar los elementos
for (const auto& x : c) // leer - tipos pesados que solo inspeccionas
Usa por defecto const auto& al leer y auto& al escribir. Recurre al simple auto solo para tipos realmente pequeños y baratos de copiar.
Recorrer maps y pairs
Un for basado en rango sobre un std::map te entrega un std::pair por cada entrada, con .first (la clave) y .second (el valor). Desde C++17, los structured bindings permiten desempaquetar ese pair en dos variables con nombre directamente en la cabecera del bucle:
[name, age] es mucho más claro que repetir entry.first y entry.second por todas partes. Mantén también aquí el const auto&: la clave de una entrada de map es un string, así que copiar cada pair sería un desperdicio.
La trampa: no redimensiones mientras recorres
El mayor problema es cambiar el tamaño del contenedor mientras un for basado en rango lo recorre. Llamar a push_back, erase, insert o clear puede reasignar el almacenamiento subyacente e invalidar los iteradores internos del bucle: el resultado es comportamiento indefinido, lo que significa fallos o basura, no un error amable:
vector<int> v = {1, 2, 3};
for (int x : v) {
v.push_back(x); // COMPORTAMIENTO INDEFINIDO - la reasignación invalida el rango
}
Si necesitas añadir o eliminar elementos mientras procesas, cambia a un bucle for basado en índices o iteradores y gestiona los límites tú mismo, o construye un contenedor de resultados aparte e intercámbialo después. Dos trampas más pequeñas de la misma familia: nunca enlaces un for basado en rango a un temporal que muere de inmediato (for (auto x : makeVector()) está bien, pero for (auto& x : someObj.getTempVector()) puede quedar colgante), y recuerda que for (auto& c : myString) te permite mutar caracteres individuales en el sitio.
Siguiente: funciones
El bucle for basado en rango ordena la iteración, y las decisiones entre auto / auto& / const auto& que acabas de aprender se trasladan directamente a una de las herramientas más importantes de C++. A continuación empaquetaremos lógica en funciones reutilizables: dándole al código un nombre, parámetros y un valor de retorno para que puedas llamarlo desde cualquier sitio en lugar de repetirte.
Preguntas frecuentes
¿Qué es un bucle for basado en rango en C++?
Un bucle for basado en rango recorre cada elemento de un contenedor (array, vector, string, map, etc.) sin que tengas que gestionar un índice o un iterador. La sintaxis es for (auto x : container) { ... }. Se añadió en C++11 y es la forma más limpia de decir "haz esto para cada elemento".
¿Cuándo debería usar auto& en lugar de auto en un bucle for basado en rango?
Usa auto& x cuando quieras modificar los elementos en el sitio, y const auto& x cuando solo los leas pero quieras evitar copiarlos (importante para string, vector u objetos grandes). El simple auto x hace una copia en cada iteración: está bien para tipos baratos como int, pero es un desperdicio en otros casos.
¿Se puede cambiar el tamaño de un vector dentro de un bucle for basado en rango en C++?
No. Llamar a push_back, erase, insert o clear sobre el contenedor que estás iterando invalida los iteradores internos del bucle y es comportamiento indefinido: puede provocar un fallo o corromper los datos en silencio. Si necesitas añadir o eliminar elementos mientras recorres, usa en su lugar un bucle for basado en índices o en iteradores.