"Para cada elemento, faça isto"
O clássico laço for com contador é ótimo quando você tem um índice para controlar. Mas, na maioria das vezes, você não se importa de fato com o índice: só quer tocar cada elemento de um contêiner. Escrever for (int i = 0; i < v.size(); i++) para isso é ruidoso, e i está a um erro de off-by-one de ler fora dos limites.
O C++11 adicionou o laço for baseado em intervalo exatamente para isto. Você nomeia uma variável, aponta para um contêiner e o laço percorre cada elemento por você:
Sem índice, sem .size(), sem limites para errar. Leia como "para cada s em scores." Funciona com arrays nativos, std::vector, std::string, std::map e qualquer outra coisa que exponha begin() e end().
Deixe o auto escolher o tipo
Escrever o tipo do elemento à mão funciona, mas é frágil: mude o tipo do contêiner e todos os laços precisam mudar também. Combine o for baseado em intervalo com auto e o compilador deduzirá o tipo do elemento por você:
Ainda assim, há um custo escondido aqui. O simples auto name deduz string e copia cada elemento para name a cada passagem. Para um int isso é de graça; para um string ou uma struct grande é uma alocação desperdiçada a cada iteração. A solução são as referências, que é a próxima coisa a entender.
Modificar no lugar com auto&
Se você escrever auto x, recebe uma cópia, então atribuir a x muda a cópia, não o contêiner. Veja esta armadilha:
A multiplicação silenciosamente não faz nada porque n é uma cópia descartável. Para realmente editar os elementos, pegue-os por referência com auto&:
O único & é toda a diferença entre "olhar mas não tocar" e "editar no lugar." Se você algum dia se perguntar por que suas mudanças somem, quase sempre é por isto.
Ler sem copiar: const auto&
Quando você só precisa ler elementos, mas eles são caros de copiar, use const auto&. A referência evita a cópia, e const documenta (e impõe) que você não vai modificar nada:
Uma boa regra prática:
for (auto x : c) // cópia - tipos baratos (int, char, ponteiros)
for (auto& x : c) // editar - você quer mudar os elementos
for (const auto& x : c) // ler - tipos pesados que você só inspeciona
Use por padrão const auto& ao ler e auto& ao escrever. Recorra ao simples auto apenas para tipos genuinamente pequenos e baratos de copiar.
Percorrendo maps e pairs
Um for baseado em intervalo sobre um std::map entrega um std::pair para cada entrada, com .first (a chave) e .second (o valor). Desde o C++17, os structured bindings permitem desempacotar esse pair em duas variáveis nomeadas direto no cabeçalho do laço:
[name, age] é muito mais claro do que repetir entry.first e entry.second em todo lugar. Mantenha o const auto& aqui também: a chave de uma entrada de map é um string, então copiar cada pair seria desperdício.
A armadilha: não redimensione enquanto faz o laço
A maior armadilha é mudar o tamanho do contêiner enquanto um for baseado em intervalo o percorre. Chamar push_back, erase, insert ou clear pode realocar o armazenamento subjacente e invalidar os iteradores internos do laço: o resultado é comportamento indefinido, ou seja, travamentos ou lixo, não um erro amigável:
vector<int> v = {1, 2, 3};
for (int x : v) {
v.push_back(x); // COMPORTAMENTO INDEFINIDO - a realocação invalida o intervalo
}
Se você precisar adicionar ou remover elementos durante o processamento, troque para um laço for baseado em índice ou em iterador e gerencie os limites você mesmo, ou construa um contêiner de resultado separado e troque-o depois. Duas armadilhas menores da mesma família: nunca vincule um for baseado em intervalo a um temporário que morre imediatamente (for (auto x : makeVector()) tudo bem, mas for (auto& x : someObj.getTempVector()) pode ficar pendurado), e lembre-se de que for (auto& c : myString) permite alterar caracteres individuais no lugar.
Próximo: funções
O laço for baseado em intervalo organiza a iteração, e as escolhas entre auto / auto& / const auto& que você acabou de aprender passam direto para uma das ferramentas mais importantes do C++. A seguir, vamos empacotar lógica em funções reutilizáveis: dando ao código um nome, parâmetros e um valor de retorno para que você possa chamá-lo de qualquer lugar em vez de se repetir.
Perguntas frequentes
O que é um laço for baseado em intervalo em C++?
Um laço for baseado em intervalo percorre cada elemento de um contêiner (array, vector, string, map, etc.) sem que você precise gerenciar um índice ou iterador. A sintaxe é for (auto x : container) { ... }. Foi adicionado no C++11 e é a forma mais limpa de dizer "faça isto para cada elemento".
Quando devo usar auto& em vez de auto em um laço for baseado em intervalo?
Use auto& x quando quiser modificar os elementos no lugar, e const auto& x quando apenas os ler mas quiser evitar a cópia (importante para string, vector ou objetos grandes). O simples auto x faz uma cópia a cada iteração: tudo bem para tipos baratos como int, mas desperdício caso contrário.
É possível mudar o tamanho de um vector dentro de um laço for baseado em intervalo em C++?
Não. Chamar push_back, erase, insert ou clear no contêiner que você está iterando invalida os iteradores internos do laço e é comportamento indefinido: pode travar ou corromper os dados silenciosamente. Se precisar adicionar ou remover elementos durante o laço, use um laço for baseado em índice ou em iterador.