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…".
- Como escrever
for (int n : numeros)e ler em voz alta "para cadanemnumeros". - Como somar, achar o maior e mostrar os itens de uma lista.
- Como percorrer uma
std::stringletra 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 comoauto&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::vectorpara o programa, como o<iostream>traz ostd::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
forclá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
nemnumeros". - 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ássico | for de intervalo | |
|---|---|---|
| O que você escreve | Início, condição e passo | Só a variável e a lista |
| Sabe a posição do item? | Sim (i) | Não |
| Pode errar o tamanho ("um a mais")? | Sim | Não |
| Bom para | Contar, pular de 2 em 2, andar para trás, precisar da posição | Passar 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. maiorcomeç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
4desoma / 4está "chumbado" no código. Comnotas.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.
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 empé 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 queitemnã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?
| Forma | O que é x | Pode mudar a lista? | Quando usar |
|---|---|---|---|
for (auto x : lista) | Uma cópia do item | Não (muda só a cópia) | Itens pequenos (int, double, char) que você só lê |
for (auto& x : lista) | O próprio item | Sim | Quando quer alterar os itens |
for (const auto& x : lista) | O próprio item, só leitura | Não (o compilador impede) | Só ler itens "grandes" (textos, structs). Na dúvida, use este. |
for (int x : lista) | Uma cópia convertida para int | Não | Quando 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
&)
$ ./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).
#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.
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.
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).
�
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
| Forma | O 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:
2. Para alterar
Complete para que o laço zere todos os itens da lista v:
3. Só ler textos
Complete com a forma recomendada para só mostrar uma lista de std::string:
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, comstd::string(umcharpor volta), com arrays e com listas entre chaves. autodeixa o tipo sempre certo; um tipo errado (intparadouble) converte em silêncio.- Sem
&,xé uma cópia: alterá-la não muda a lista. Para alterar, useauto&. - 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).