C++ for de Intervalo C++11

Muitas vezes, repetir significa passar por todos os itens de uma lista: todas as notas da turma, todos os produtos do carrinho, todas as letras de um nome. Com o for clássico você precisaria de um contador, do tamanho da lista e de acesso por posição. O C++11 trouxe um atalho para isso, o for de intervalo (em inglês, range-based for, ou só range-for): "para cada item desta lista, faça…".

Você vai aprender
  • Como escrever for (int n : numeros) e ler em voz alta "para cada n em numeros".
  • Como somar, achar o maior e mostrar os itens de uma lista.
  • Como percorrer uma std::string letra por letra (e o cuidado com acentos).
  • Como deixar o compilador escolher o tipo com auto.
  • Por que for (auto p : precos) p = p * 2; não muda nada, e como auto& resolve.
  • Por que const auto& é a forma recomendada para só ler.

Os programas foram compilados com o g++ 15 na máquina fictícia da Ana, na pasta ~/cpp, com g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa.


A ideia: "para cada pessoa da fila, entregue um panfleto"

Você está entregando panfletos numa fila. Ninguém lhe diz "entregue para a pessoa número 1, depois para a número 2…". A instrução é simplesmente: "para cada pessoa da fila, entregue um panfleto". Você começa pela primeira, vai até a última e para. Não precisa contar quantas são nem saber a posição de cada uma.

O for de intervalo é isso: você diz qual é a lista e que nome dar a cada item; o C++ cuida do resto (começar do primeiro, ir até o último, parar).

Uma lista para trabalhar: std::vector

Para percorrer uma lista, primeiro precisamos de uma. Nesta aula, vamos usar o std::vector, a lista padrão do C++ (vector = vetor, uma lista que pode crescer). Ele tem aula própria, std::vector; por enquanto, basta saber criar um:

#include <vector>
Traz o std::vector para o programa, como o <iostream> traz o std::cout.
std::vector<int>
O tipo: "uma lista de int". Entre < > vai o tipo de cada item: std::vector<double> é uma lista de números com vírgula; std::vector<std::string>, uma lista de textos.
numeros
O nome da variável (a lista inteira).
= {4, 8, 15, 16, 23, 42};
Os itens iniciais, entre chaves, separados por vírgula.

1. O primeiro for de intervalo

#include <iostream>
#include <vector>

int main() {
    std::vector<int> numeros = {4, 8, 15, 16, 23, 42};
    for (int n : numeros) {
        std::cout << n << '\n';
    }
}
for
A mesma palavra-chave do for clássico. O que muda é o que vai dentro dos parênteses.
int n
A variável que, em cada volta, recebe um item da lista. Na 1ª volta vale 4; na 2ª, 8; e assim por diante. O tipo (int) deve combinar com o tipo dos itens.
:
Os dois-pontos separam a variável da lista. Leia como "em": "para cada n em numeros".
numeros
A lista que será percorrida, do primeiro ao último item.
{ … }
O corpo, que roda uma vez para cada item.
$ g++ -std=c++23 -Wall -Wextra primeiro.cpp -o primeiro
$ ./primeiro
4
8
15
16
23
42

Seis itens, seis voltas. Repare no que não está no código: não há contador, não há condição de parada, não há passo, não há o número 6 em lugar nenhum. Se você acrescentar um sétimo número na lista, o laço dá sete voltas sem mudar nada no for.

for clássicofor de intervalo
O que você escreveInício, condição e passoSó a variável e a lista
Sabe a posição do item?Sim (i)Não
Pode errar o tamanho ("um a mais")?SimNão
Bom paraContar, pular de 2 em 2, andar para trás, precisar da posiçãoPassar por todos os itens, do primeiro ao último

2. Fazendo contas com os itens

As técnicas da aula while (o acumulador que nasce fora e cresce dentro) funcionam do mesmo jeito. A Ana quer a soma, a média e a maior das notas:

#include <iostream>
#include <vector>

int main() {
    std::vector<double> notas = {7.5, 9.0, 6.0, 8.5};
    double soma = 0.0;
    double maior = 0.0;
    for (double nota : notas) {
        soma += nota;
        if (nota > maior) {
            maior = nota;
        }
    }
    std::cout << "Soma: " << soma << '\n';
    std::cout << "Média: " << soma / 4 << '\n';
    std::cout << "Maior nota: " << maior << '\n';
}
$ g++ -std=c++23 -Wall -Wextra notas.cpp -o notas
$ ./notas
Soma: 31
Média: 7.75
Maior nota: 9
  • Os itens são double, então a variável do laço também é: double nota.
  • maior começa em 0 e, a cada nota maior que ela, é substituída. No fim, guarda a maior de todas. (Se as notas pudessem ser negativas, seria melhor começar com a primeira nota; a aula std::vector mostra como.)
  • O 4 de soma / 4 está "chumbado" no código. Com notas.size(), que dá o tamanho da lista, a média continuaria certa mesmo que a lista mudasse (veja std::vector).

Missão: some os preços do carrinho com um for de intervalo e mostre Total: 47.5. A variável total já foi criada; falta o laço.

#include <iostream>
#include <vector>

int main() {
    std::vector<double> precos = {12.5, 8.0, 27.0};
    double total = 0.0;
    // percorra precos aqui

    std::cout << "Total: " << total << '\n';
}
#include <iostream>
#include <vector>

int main() {
    std::vector<double> precos = {12.5, 8.0, 27.0};
    double total = 0.0;
    for (double p : precos) {
        total += p;
    }
    std::cout << "Total: " << total << '\n';
}

3. Percorrer uma std::string

Um texto (std::string) também é uma lista: uma lista de caracteres (char). Então o for de intervalo funciona com ele, entregando uma letra por volta:

#include <iostream>
#include <string>

int main() {
    std::string nome = "Ana Maria";
    for (char c : nome) {
        std::cout << '[' << c << ']';
    }
    std::cout << '\n';

    int quantos_a = 0;
    for (char c : nome) {
        if (c == 'a' || c == 'A') {
            quantos_a++;
        }
    }
    std::cout << "Letras 'a': " << quantos_a << '\n';
}
$ g++ -std=c++23 -Wall -Wextra letras.cpp -o letras
$ ./letras
[A][n][a][ ][M][a][r][i][a]
Letras 'a': 4

Os colchetes mostram cada volta: 9 caracteres, contando o espaço. O segundo laço usa um if para contar só as letras a, maiúsculas ou minúsculas.

Letras com acento ocupam mais de um char

Num texto em UTF-8 (o padrão do Linux), letras como ã e é ocupam 2 bytes, e cada char é um byte. Veja o que acontece com "pão":

#include <iostream>
#include <string>

int main() {
    std::string palavra = "pão";
    std::cout << "Tamanho: " << palavra.size() << '\n';
    for (char c : palavra) {
        std::cout << '[' << c << ']';
    }
    std::cout << '\n';
}
$ g++ -std=c++23 -Wall -Wextra acento.cpp -o acento
$ ./acento
Tamanho: 4
[p][�][�][o]

"pão" tem 3 letras, mas 4 chars: o ã virou dois pedaços, e cada pedaço sozinho não é um caractere válido; o terminal mostra � no lugar. Para textos sem acento, letra por letra funciona bem. Para lidar com acentos, veja Acentos e UTF-8.


4. Deixe o compilador escolher o tipo: auto

Em vez de escrever o tipo do item, você pode usar auto: o compilador olha a lista e descobre sozinho. Com std::vector<double>, auto vira double; com std::string, vira char:

#include <iostream>
#include <vector>

int main() {
    std::vector<double> precos = {12.5, 8.0, 27.0};
    for (auto p : precos) {
        std::cout << "R$ " << p << '\n';
    }
}
$ g++ -std=c++23 -Wall -Wextra auto.cpp -o auto
$ ./auto
R$ 12.5
R$ 8
R$ 27

Com auto, o tipo da variável sempre combina com o da lista. Isso evita um erro silencioso: escrever int para uma lista de double (veja Quando dá errado). Por isso, no for de intervalo, auto é a escolha mais comum no C++ moderno.


5. Modificar os itens: auto&

A Ana quer dobrar todos os preços da lista. A primeira tentativa parece óbvia, mas não funciona. Rode e confira:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> precos = {10, 20, 50};

    for (auto p : precos) {
        p = p * 2;
    }
    std::cout << "Sem &: ";
    for (auto p : precos) {
        std::cout << p << ' ';
    }
    std::cout << '\n';

    for (auto& p : precos) {
        p = p * 2;
    }
    std::cout << "Com &: ";
    for (auto p : precos) {
        std::cout << p << ' ';
    }
    std::cout << '\n';
}
$ g++ -std=c++23 -Wall -Wextra copia.cpp -o copia
$ ./copia
Sem &: 10 20 50
Com &: 20 40 100 

O primeiro laço rodou, fez as contas e… a lista continuou 10 20 50. O compilador nem avisou. Por quê?

  • Sem & (auto p): em cada volta, p é uma cópia do item. Você dobrou a cópia, e a cópia foi jogada fora no fim da volta. É como tirar uma fotocópia de um documento e escrever na fotocópia: o original não muda.
  • Com & (auto& p): p é uma referência, um outro nome para o próprio item da lista. Mexer em p é mexer no item. É escrever direto no documento original.
auto
O tipo do item, descoberto pelo compilador (aqui, int).
&
"Referência": não copie, me dê acesso ao item verdadeiro. Referências têm aula própria: Referências.
p
O nome do item na volta atual.
: precos
A lista cujos itens serão alterados.

A regra prática: quer mudar os itens da lista? Use auto&. O mesmo vale para uma std::string: for (auto& c : nome) c = '*'; troca todas as letras por asteriscos.


6. Só ler, sem copiar: const auto&

Copiar um int não custa nada. Mas copiar um texto custa: o computador precisa reservar memória e copiar letra por letra. Num laço que só mostra os itens de uma lista de textos, cada volta faria uma cópia inútil. A forma recomendada junta as duas ideias:

#include <iostream>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> lista = {"arroz", "feijão", "café", "leite"};
    for (const auto& item : lista) {
        std::cout << "- " << item << '\n';
    }
}
$ g++ -std=c++23 -Wall -Wextra compras.cpp -o compras
$ ./compras
- arroz
- feijão
- café
- leite
  • &: não copia; item é o próprio texto da lista.
  • const: promete que item não será alterado (veja Constantes). Se você tentar, o compilador recusa, e isso protege a lista de mudanças acidentais.
#include <iostream>
#include <vector>

int main() {
    std::vector<int> precos = {10, 20, 50};
    for (const auto& p : precos) {
        p = p * 2;
    }
}
$ g++ -std=c++23 -Wall -Wextra constref.cpp -o constref
constref.cpp: In function ‘int main()’:
constref.cpp:7:11: error: assignment of read-only reference ‘p’
    7 |         p = p * 2;
      |         ~~^~~~~~~

"Atribuição a uma referência somente leitura p". O const fez seu trabalho. No editor do guia, o Clang diz error: cannot assign to variable 'p' with const-qualified type 'int const &'.

Qual usar?

FormaO que é xPode mudar a lista?Quando usar
for (auto x : lista)Uma cópia do itemNão (muda só a cópia)Itens pequenos (int, double, char) que você só lê
for (auto& x : lista)O próprio itemSimQuando quer alterar os itens
for (const auto& x : lista)O próprio item, só leituraNão (o compilador impede)Só ler itens "grandes" (textos, structs). Na dúvida, use este.
for (int x : lista)Uma cópia convertida para intNãoQuando você quer escolher o tipo; cuidado para ele combinar

Formas diferentes de escrever

Uma lista escrita direto no laço

Para poucos valores fixos, a lista pode ir entre chaves, direto no for:

#include <iostream>

int main() {
    for (int n : {10, 20, 30}) {
        std::cout << n << ' ';
    }
    std::cout << '\n';
}
$ g++ -std=c++23 -Wall -Wextra chaves.cpp -o chaves
$ ./chaves
10 20 30 

Quando você precisa da posição

O for de intervalo não diz em que posição está o item. Se você precisa numerar, crie um contador antes do laço e some 1 a cada volta:

#include <iostream>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> lista = {"arroz", "feijão", "café"};
    int pos = 1;
    for (const auto& item : lista) {
        std::cout << pos << ". " << item << '\n';
        pos++;
    }
}
$ g++ -std=c++23 -Wall -Wextra posicao.cpp -o posicao
$ ./posicao
1. arroz
2. feijão
3. café

Desde o C++20, o contador pode ser criado dentro do próprio for, antes de um ;: for (int pos = 1; const auto& item : lista). Assim ele só existe dentro do laço, como o i do for clássico. Com -std=c++17, o g++ avisa: warning: range-based ‘for’ loops with initializer only available with ‘-std=c++20’ or ‘-std=gnu++20’. Se precisar da posição para outras coisas (comparar com o item anterior, pular itens), o for clássico com índice pode ser mais simples.


Quando dá errado

A lista não mudou (faltou o &)
$ ./copia
Sem &: 10 20 50
Com &: 20 40 100 

O que aconteceu: sem &, a variável do laço é uma cópia; alterar a cópia não altera a lista. O compilador não avisa, porque mudar uma cópia é permitido.

Como consertar: for (auto& p : precos).

As notas perderam a parte decimal (tipo errado na variável)
#include <iostream>
#include <vector>

int main() {
    std::vector<double> notas = {7.5, 9.0, 6.5};
    for (int nota : notas) {
        std::cout << nota << '\n';
    }
}
$ g++ -std=c++23 -Wall -Wextra trunca.cpp -o trunca
$ ./trunca
7
9
6

O que aconteceu: os itens são double, mas a variável é int. Em cada volta, o item foi convertido para inteiro, e a parte depois do ponto foi cortada (7.5 virou 7). Nem o -Wall -Wextra avisa (só a opção -Wconversion, veja Conversão de Tipos).

Como consertar: use o tipo certo (double nota) ou, melhor, auto.

"assignment of read-only reference"

O que aconteceu: você tentou alterar o item num laço com const auto&, que é só de leitura.

Como consertar: se a ideia é mesmo alterar a lista, troque por auto&. Se não era, o erro acabou de salvar sua lista: revise a linha.

Escreveu in (de outras linguagens) no lugar de :
#include <iostream>
#include <vector>

int main() {
    std::vector<int> numeros = {1, 2, 3};
    for (int n in numeros) {
        std::cout << n << '\n';
    }
}
$ g++ -std=c++23 -Wall -Wextra in.cpp -o in
in.cpp: In function ‘int main()’:
in.cpp:6:15: error: expected ‘;’ before ‘in’
    6 |     for (int n in numeros) {
      |               ^~~
      |               ;
in.cpp:6:16: error: ‘in’ was not declared in this scope; did you mean ‘n’?
    6 |     for (int n in numeros) {
      |                ^~
      |                n
in.cpp:6:18: error: expected ‘;’ before ‘numeros’
    6 |     for (int n in numeros) {
      |                  ^~~~~~~~
      |                  ;
in.cpp:6:19: warning: for increment expression has no effect [-Wunused-value]
    6 |     for (int n in numeros) {
      |                   ^~~~~~~

O que significa: Python e JavaScript usam for … in …; o C++ usa dois-pontos. Sem os :, o compilador achou que era um for clássico e passou a procurar os ;. Ele ainda tentou entender in como uma variável ("did you mean ‘n’?").

Como consertar: for (int n : numeros).

Acentos aparecem como �

O que aconteceu: o laço percorreu a std::string byte a byte, e uma letra acentuada ocupa dois bytes em UTF-8. Cada metade, sozinha, não é um caractere válido.

Como consertar: se só vai mostrar o texto, mostre-o inteiro (std::cout << palavra). Para trabalhar letra por letra com acentos, veja Acentos e UTF-8.


Referência

FormaO que faz
for (int n : numeros) { … }Uma volta por item; n é uma cópia de cada um.
for (char c : texto)Uma volta por char (byte) de uma std::string.
for (auto x : lista)O compilador escolhe o tipo; x é cópia.
for (auto& x : lista)x é o próprio item: alterar x altera a lista.
for (const auto& x : lista)O próprio item, só leitura, sem cópia. O padrão para ler.
for (int n : {1, 2, 3})Lista escrita direto no laço.
for (int i = 0; const auto& x : lista)C++20 Com uma variável de apoio criada no próprio for.

Exercícios

1. O separador

Complete com o símbolo que separa a variável da lista:

for (int n numeros)

2. Para alterar

Complete para que o laço zere todos os itens da lista v:

for ( x : v) { x = 0; }

3. Só ler textos

Complete com a forma recomendada para só mostrar uma lista de std::string:

for ( nome : nomes)

4. Depois de std::vector<int> v = {1, 2, 3}; for (auto x : v) { x = 9; }, quanto vale v?

5. Quantas voltas dá for (char c : std::string("Oi, tudo"))?

6. Você precisa mostrar 1. arroz, 2. feijão… Qual é a limitação do for de intervalo aqui?

Missão final: aumento de 10%

Missão: a lojinha da Ana vai aumentar todos os preços em 10%. Escreva um for de intervalo com auto& que multiplica cada preço por 1.10. O segundo laço, que já está pronto, deve mostrar 11 22 55.

#include <iostream>
#include <vector>

int main() {
    std::vector<double> precos = {10.0, 20.0, 50.0};
    // aumente os preços aqui

    for (const auto& p : precos) {
        std::cout << p << ' ';
    }
    std::cout << '\n';
}
#include <iostream>
#include <vector>

int main() {
    std::vector<double> precos = {10.0, 20.0, 50.0};
    for (auto& p : precos) {
        p = p * 1.10;
    }
    for (const auto& p : precos) {
        std::cout << p << ' ';
    }
    std::cout << '\n';
}

Resumo

  • for (tipo x : lista) { … } passa por todos os itens, do primeiro ao último, sem contador e sem risco de "um a mais".
  • Funciona com std::vector, com std::string (um char por volta), com arrays e com listas entre chaves.
  • auto deixa o tipo sempre certo; um tipo errado (int para double) converte em silêncio.
  • Sem &, x é uma cópia: alterá-la não muda a lista. Para alterar, use auto&.
  • Para só ler, especialmente textos, use const auto&: sem cópia e protegido contra mudanças.
  • Precisa da posição? Contador à parte (ou o for clássico).