C++: Iteradores STL
Nas lições 34-35 aprendemos sobre contêineres e algoritmos STL.
Agora, vamos mergulhar na "cola" da STL — iteradores.
Compreender iteradores é a chave para verdadeiramente entender a filosofia de design da STL.
1. Visão Geral de Iteradores
(1) 1.1 O que são Iteradores?
Iteradores são um conceito central da STL que conectam contêineres e algoritmos.
Analogia do mundo real:
- Contêiner = armazém
- Algoritmo = trabalhador
- Iterador = o recibo de retirada na mão do trabalhador (dizendo ao trabalhador qual prateleira ir)
(2) 1.2 Operações Básicas de Iteradores
Todos os iteradores suportam as seguintes operações:
| Operação | Descrição | Exemplo |
|---|---|---|
*it |
Desreferenciar | int x = *it; |
it++ |
Avançar para o próximo elemento | ++it; |
it-- |
Voltar para o elemento anterior | --it; |
it1 == it2 |
Comparar igualdade | if (it1 == it2) |
it1 != it2 |
Comparar desigualdade | while (it != end()) |
2. Categorias de Iteradores
(1) 2.1 Cinco Tipos de Iteradores
A STL define 5 tipos de iteradores, do mais fraco ao mais forte:
| Tipo de Iterador | Capacidades | Contêiner Representativo |
|---|---|---|
| Iterador de entrada | Somente leitura, unidirecional | istream_iterator |
| Iterador de saída | Somente escrita, unidirecional | ostream_iterator |
| Iterador de avanço | Leitura/escrita, unidirecional | forward_list |
| Iterador bidirecional | Leitura/escrita, bidirecional | list, set, map |
| Iterador de acesso aleatório | Leitura/escrita, acesso aleatório | vector, deque, array |
(2) 2.2 Comparação de Capacidades dos Iteradores
Input iterator ← Weakest
↓
Forward iterator
↓
Bidirectional iterator
↓
Random-access iterator ← Strongest
Quanto mais forte a capacidade, mais operações são suportadas:
| Operação | Entrada | Avanço | Bidirecional | Acesso Aleatório |
|---|---|---|---|---|
Desreferenciar * |
✅ | ✅ | ✅ | ✅ |
Avançar ++ |
✅ | ✅ | ✅ | ✅ |
Voltar -- |
❌ | ❌ | ✅ | ✅ |
| Acesso aleatório `` | ❌ | ❌ | ❌ | ✅ |
Aritmética + - |
❌ | ❌ | ❌ | ✅ |
(3) 2.3 Exemplo: Iteradores para Diferentes Contêineres
#include <iostream>
#include <vector>
#include <list>
#include <forward_list>
int main() {
std::vector<int> v = {1, 2, 3};
std::list<int> l = {1, 2, 3};
std::forward_list<int> fl = {1, 2, 3};
// vector: random-access iterator
auto it_v = v.begin();
std::cout << it_v[2] << std::endl; // Random access works
// list: bidirectional iterator
auto it_l = l.begin();
++it_l; // Can advance
--it_l; // Can move back
// it_l[2]; // ❌ Error! list doesn't support random access
return 0;
}
Saída:
3
3. Invalidação de Iteradores
(1) 3.1 O que é Invalidação de Iteradores?
Invalidação de iteradores ocorre quando uma operação no contêiner faz com que a posição de um iterador se torne inválida.
Causas comuns:
- Realocação de memória (
push_backdovector) - Exclusão de elementos (
erase)
(2) 3.2 Regras de Invalidação de Iteradores por Contêiner
| Contêiner | Operação | Invalidação |
|---|---|---|
vector |
push_back |
Pode invalidar (quando ocorre realocação) |
vector |
erase |
Iteradores no e após o elemento excluído são invalidados |
list |
push_back |
Não invalidado |
list |
erase |
Apenas o iterador para o elemento excluído é invalidado |
map/set |
erase |
Apenas o iterador para o elemento excluído é invalidado |
(3) 3.3 Exemplo: Invalidação de Iterador do vector (Dificuldade ⭐⭐)
▶ Exemplo 1: Uso de Contêiner STL (Dificuldade ⭐)
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin();
std::cout << "*it = " << *it << std::endl; // Output: 1
// push_back may cause reallocation, invalidating iterators
v.push_back(6);
// ❌ Dangerous! it may be invalidated
// std::cout << "*it = " << *it << std::endl; // Undefined behavior
// ✅ Correct approach: re-acquire the iterator
it = v.begin();
std::cout << "*it = " << *it << std::endl; // Output: 1
return 0;
}
Saída:
*it = 1
*it = 1
(4) 3.4 Removendo Elementos com Segurança
Abordagem errada:
for (auto it = v.begin(); it != v.end(); ++it) {
### ▶ Exemplo 2: Aplicação de Recurso do C++ Moderno (Dificuldade ⭐)
if (*it % 2 == 0) {
v.erase(it); // ❌ it is invalidated!
}
}
Abordagem correta:
for (auto it = v.begin(); it != v.end(); ) {
if (*it % 2 == 0) {
it = v.erase(it); // ✅ erase returns the next valid iterator
} else {
++it;
}
}
Saída:
(program output)
4. Iteradores Reversos
(1) 4.1 O que são Iteradores Reversos?
Iteradores reversos percorrem um contêiner do final ao início.
Exemplo: Saída reversa (Dificuldade ⭐)
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// Using reverse iterator
for (auto it = v.rbegin(); it != v.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
Resultado:
5 4 3 2 1
5. Iteradores de Inserção
(1) 5.1 O que são Iteradores de Inserção?
Iteradores de inserção são iteradores de saída usados para inserir elementos em um contêiner.
Três tipos de iteradores de inserção:
| Iterador | Função | Exemplo |
|---|---|---|
back_inserter |
Inserir no final | std::back_inserter(v) |
front_inserter |
Inserir no início | std::front_inserter(l) |
inserter |
Inserir em posição especificada | std::inserter(v, v.begin()) |
(2) 5.2 Exemplo: Copiando com back_inserter (Dificuldade ⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
int main() {
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst; // Empty container
// ❌ Wrong! dst doesn't have enough space
// std::copy(src.begin(), src.end(), dst.begin());
// ✅ Correct! Use back_inserter for automatic expansion
std::copy(src.begin(), src.end(), std::back_inserter(dst));
std::cout << "Copy result: ";
for (int x : dst) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
Saída:
1 2 3 4 5
6. Iteradores de Fluxo
(1) 6.1 Iteradores de Fluxo de Entrada
Função: Ler dados de um fluxo de entrada.
Exemplo: Lendo da entrada padrão (Dificuldade ⭐⭐)
#include <iostream>
#include <vector>
#include <iterator>
int main() {
std::vector<int> v;
std::cout << "Enter some numbers (Ctrl+Z to end):" << std::endl;
// Read from standard input
std::copy(std::istream_iteratorint(std::cin),
std::istream_iteratorint(),
std::back_inserter(v));
std::cout << "You entered: ";
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
(2) 6.2 Iteradores de Fluxo de Saída
Função: Escrever dados em um fluxo de saída.
Exemplo: Saída para arquivo (Dificuldade ⭐⭐)
#include <iostream>
#include <vector>
#include <iterator>
#include <fstream>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// Output to standard output
std::copy(v.begin(), v.end(),
std::ostream_iteratorint(std::cout, " "));
std::cout << std::endl;
// Output to file
std::ofstream file("output.txt");
std::copy(v.begin(), v.end(),
std::ostream_iteratorint(file, "\n"));
file.close();
return 0;
}
❓ Perguntas Frequentes
P: Por que list não suporta acesso aleatório? R:
listé uma lista encadeada — elementos não são contíguos na memória, então você não pode acessá-los diretamente por índice.
P: Como evitar invalidação de iteradores? R: 1. Readquira iteradores após cada operação no contêiner 2. Use o valor de retorno de
erasepara atualizar iteradores 3. Prefira usar algoritmos em vez de manipulação manual de iteradores
P: O que é const_iterator? R:
const_iteratoré um iterador somente leitura que não pode modificar valores de elementos.
std::vector<int> v = {1, 2, 3};
std::vector<int>::const_iterator it = v.cbegin();
// *it = 10; // ❌ Error! Cannot modify
▶ Exemplo 3: Percorrendo com Iterador (Dificuldade ⭐)
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
Saída:
1 2 3 4 5
* para desreferenciar, ++ para mover, e != para comparar posições.
| Tópico | Pontos-chave |
|---|---|
| Categorias de iteradores | Entrada → Avanço → Bidirecional → Acesso Aleatório |
| Invalidação de iteradores | vector pode invalidar, list não |
| Iteradores reversos | rbegin()/rend() |
| Iteradores de inserção | back_inserter etc. |
| Iteradores de fluxo | Conectam STL e E/S |
📖 Resumo
- Iteradores: objetos semelhantes a ponteiros usados para percorrer elementos de contêineres
- Categorias de iteradores: entrada/saída/avanço/bidirecional/acesso aleatório
- begin()/end(): obter iteradores para o início e fim de um contêiner
- Invalidação de iteradores: inserção/exclusão podem invalidar iteradores
📝 Exercícios
-
Básico (Dificuldade ⭐): Use iteradores para percorrer
vector<int>e exibir todos os elementos. Faça tanto com begin/end quanto com for baseado em intervalo. -
Intermediário (Dificuldade ⭐⭐): Use iteradores reversos rbegin/rend para percorrer um vector de forma reversa e exibir. Observe a diferença em relação ao percurso para frente.
-
Desafio (Dificuldade ⭐⭐⭐): Implemente um iterador personalizado que encapsula um intervalo de inteiros (por exemplo, 1 a 10), suportando operadores
++e*.
- Iteradores são a ponte entre contêineres e algoritmos
- Cinco tipos de iteradores: entrada/saída/avanço/bidirecional/acesso aleatório
- Loops for baseados em intervalo são implementados usando iteradores por baixo
- Invalidação de iteradores: alguns iteradores ficam inutilizáveis após inserção/exclusão
- const_iterator fornece acesso somente leitura aos elementos
Próxima lição: Objetos de Função STL (#38)