Menu

Laço for baseado em intervalo em C++: sintaxe, auto e referências

O laço for baseado em intervalo do C++ explicado: iteração limpa sobre arrays, vectors, strings e maps, por que você deve usar auto& e const auto&, e as armadilhas de cópia e invalidação de iteradores a evitar.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

"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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR