C++ STL: a biblioteca de containers e algoritmos
Você já usou std::vector, std::array e std::string. Eles fazem parte de um conjunto maior, que todo programador C++ usa todo dia: a STL. Ela traz prontas as estruturas para guardar dados (listas, filas, dicionários) e dezenas de algoritmos para trabalhar com eles (ordenar, procurar, contar). Esta aula é o mapa: as três peças da STL, como elas se encaixam e qual "caixa" escolher para cada problema. As próximas aulas visitam cada peça com calma.
- O que quer dizer STL e de onde ela veio.
- As três partes: containers, iteradores e algoritmos, e por que elas são separadas.
- Que o mesmo algoritmo funciona com containers diferentes.
- O que todos os containers têm em comum (
size,empty,clear…). - Uma tabela de qual container usar quando:
vector,array,list,deque,map,unordered_map,set,stack,queue. - Qual cabeçalho (
#include) cada um pede, e o erro real quando você esquece.
Todos os programas desta aula foram compilados com o g++ 15.2 na máquina fictícia da Ana, na pasta ~/cpp/stl, com g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa. É bom já conhecer std::vector e Templates de Classe.
O nome: Standard Template Library
STL é a sigla de Standard Template Library, "biblioteca padrão de templates". Ela foi criada nos anos 1990 por Alexander Stepanov e Meng Lee, e entrou no primeiro padrão do C++, o C++98 (aula Versões do C++). Hoje ela é uma parte da biblioteca padrão do C++, que também tem std::string, std::cout, <cmath> e muito mais. Na prática, as pessoas dizem "STL" querendo dizer "os containers e os algoritmos da biblioteca padrão", e é nesse sentido que o guia usa o nome.
"De templates" porque tudo nela é template: std::vector<int>, std::map<std::string, int>. É por isso que você estudou templates antes: agora os <…> fazem sentido.
A ideia: a cozinha
Pense numa cozinha. Há potes de vários formatos (pote de vidro, saco, caixa de ovos). Há receitas (bater, separar, contar). E há as mãos, que pegam as coisas de dentro de qualquer pote, uma por uma, e entregam para a receita. A receita "contar quantos ovos estão quebrados" não depende do pote: funciona na caixa de ovos e na tigela, porque as mãos sabem tirar os ovos dos dois.
A STL é organizada exatamente assim:
| Parte | Na cozinha | O que é | Exemplos |
|---|---|---|---|
| Containers | os potes | Classes que guardam vários valores. Cada uma organiza os valores de um jeito. | std::vector, std::map, std::set |
| Iteradores | as mãos | "Dedos" que apontam um item do container e sabem andar para o próximo. | notas.begin(), notas.end() |
| Algoritmos | as receitas | Funções (templates) que fazem algo com uma faixa de itens, do começo ao fim. | std::sort, std::find, std::count |
1. As três partes num programa só
A Ana tem as notas da turma num vetor. Ela quer ordenar e contar quantos tiraram 10:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> notas{7, 10, 4, 9, 10, 6};
std::sort(notas.begin(), notas.end());
for (int n : notas) {
std::cout << n << ' ';
}
std::cout << '\n';
auto dez = std::count(notas.begin(), notas.end(), 10);
std::cout << "Notas 10: " << dez << '\n';
}
- #include <algorithm>
- O cabeçalho dos algoritmos (
sort,count,find…). - std::vector<int> notas{…};
- O container: o pote com seis notas.
- notas.begin(), notas.end()
- Dois iteradores: um aponta a primeira nota, o outro aponta o lugar depois da última (aula Iteradores). Juntos, dizem "do começo ao fim".
- std::sort(…)
- Um algoritmo: ordena a faixa que você deu, do menor para o maior.
- std::count(…, 10)
- Outro algoritmo: conta quantos itens da faixa são iguais a
10.
$ g++ -std=c++23 -Wall -Wextra tres-partes.cpp -o tres-partes $ ./tres-partes 4 6 7 9 10 10 Notas 10: 2
Repare: o std::sort não é um método do vetor (não é notas.sort()). Ele é uma função solta, que recebe o começo e o fim. Os algoritmos têm aula própria: Algoritmos.
Experimente: troque as notas, acrescente outras, e mude o 10 do std::count para contar outra nota. Depois tente ordenar só os três primeiros itens, com std::sort(notas.begin(), notas.begin() + 3);.
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> notas{7, 10, 4, 9, 10, 6};
std::sort(notas.begin(), notas.end());
for (int n : notas) {
std::cout << n << ' ';
}
std::cout << '\n';
auto dez = std::count(notas.begin(), notas.end(), 10);
std::cout << "Notas 10: " << dez << '\n';
}
2. Por que separar em três partes: um algoritmo, vários containers
A grande ideia da STL: o algoritmo não sabe (nem quer saber) em que container os dados estão. Ele só precisa de iteradores. O mesmo std::count funciona num vector, numa list e num array:
#include <algorithm>
#include <array>
#include <iostream>
#include <list>
#include <string>
#include <vector>
int main() {
std::vector<std::string> compras{"arroz", "café", "leite"};
std::list<std::string> tarefas{"lavar", "café", "estudar"};
std::array<std::string, 3> cores{"azul", "verde", "café"};
std::cout << std::count(compras.begin(), compras.end(), "café") << '\n';
std::cout << std::count(tarefas.begin(), tarefas.end(), "café") << '\n';
std::cout << std::count(cores.begin(), cores.end(), "café") << '\n';
}
$ g++ -std=c++23 -Wall -Wextra mesmo-algoritmo.cpp -o mesmo-algoritmo $ ./mesmo-algoritmo 1 1 1
Pense na conta: são uns 10 containers e mais de 100 algoritmos. Se cada container tivesse seu próprio "contar", "procurar", "ordenar"…, seriam mais de mil funções. Com os iteradores no meio, cada algoritmo é escrito uma vez e serve para todos. E se você criar o seu próprio container com iteradores, ganha os 100 algoritmos de graça.
Os iteradores não são todos iguais. O de std::vector pode pular direto para qualquer posição; o de std::list só anda um passo de cada vez. O std::sort precisa pular, e por isso não aceita std::list (que tem o seu próprio .sort()). Você vê o erro real em list e deque.
3. O que todos os containers têm em comum
Os containers foram desenhados para se parecerem. Aprendeu num, sabe o básico de todos:
#include <iostream>
#include <list>
#include <map>
#include <set>
#include <string>
#include <vector>
int main() {
std::vector<int> notas{7, 9, 10};
std::list<std::string> fila{"Ana", "Bruno"};
std::set<char> letras{'a', 'b', 'a', 'c'};
std::map<std::string, int> estoque{{"maçã", 12}, {"pera", 5}};
std::cout << notas.size() << ' ' << fila.size() << ' '
<< letras.size() << ' ' << estoque.size() << '\n';
notas.clear();
std::cout << std::boolalpha << notas.empty() << ' ' << fila.empty() << '\n';
}
$ g++ -std=c++23 -Wall -Wextra comum.cpp -o comum $ ./comum 3 2 3 2 « o set guardou só a, b, c: ele não aceita repetidos true false
| Método | O que faz | Existe em |
|---|---|---|
.size() | Quantos itens há | todos |
.empty() | true se não há nenhum item | todos |
.clear() | Apaga todos os itens | todos, menos array (tamanho fixo), stack e queue |
.begin() / .end() | Iteradores do começo e do fim | todos, menos stack e queue |
for (auto x : c) | Percorre os itens (aula range-for) | todos que têm begin/end |
4. Os containers: qual usar quando
A STL organiza os containers em três famílias:
- Sequências: guardam os itens na ordem em que você os pôs, como uma fila de pessoas.
vector,array,list,deque. - Associativos: guardam os itens organizados por uma chave, para achar rápido.
map,sete as versõesunordered_. - Adaptadores: uma "capa" sobre outro container, que deixa usar só algumas operações.
stack,queue.
| Container | Como imaginar | Use quando… | Cabeçalho | Aula |
|---|---|---|---|---|
std::vector<T> | Uma fileira de caixas coladas, que cresce no fim | Quase sempre. É o padrão: rápido para ler por posição (v[3]) e para pôr ou tirar no fim. | <vector> | std::vector |
std::array<T, N> | Uma fileira de tamanho fixo | O número de itens é conhecido e nunca muda (os 7 dias da semana, 3 notas). | <array> | std::array |
std::list<T> | Vagões de trem engatados | Você insere e remove muito no meio, e quase nunca precisa do item número tal. Raro. | <list> | list e deque |
std::deque<T> | Uma fila que cresce dos dois lados | Você põe e tira no começo e no fim. | <deque> | list e deque |
std::map<Chave, Valor> | Um dicionário em ordem alfabética | Você procura um valor por um nome (produto → quantidade) e quer os itens em ordem de chave. | <map> | std::map |
std::unordered_map<Chave, Valor> | Um fichário com gavetas numeradas por um cálculo | Igual ao map, mas a ordem não importa e você quer a busca mais rápida. | <unordered_map> | std::unordered_map |
std::set<T> | Uma coleção de figurinhas sem repetidas, arrumada | Você quer itens sem repetidos e perguntar "tem este?". Existe também std::unordered_set. | <set> | std::set |
std::stack<T> | Uma pilha de pratos | Só importa o último que entrou (desfazer, voltar). | <stack> | stack e queue |
std::queue<T> | A fila do banco | Quem chega primeiro é atendido primeiro. | <queue> | stack e queue |
Um roteiro para escolher
Na dúvida, faça estas perguntas, nesta ordem:
- Vou procurar valores por um nome ou código (como "quantas maçãs tem no estoque?")? Use
std::map. Se a ordem não importa,std::unordered_map. - Só preciso saber se algo está na coleção, sem repetidos? Use
std::set(oustd::unordered_set). - Só uso o último que entrou, ou o primeiro que entrou? Use
std::stackoustd::queue. - O tamanho é fixo e conhecido? Use
std::array. - Nenhum dos casos acima? Use
std::vector. Só troque porlistoudequese tiver um motivo claro.
É o conselho dos próprios criadores do C++. O std::vector guarda os itens colados na memória (aula Como a Memória Funciona), e o processador lê memória colada muito depressa. Mesmo em situações em que a teoria favorece a list, o vector costuma ganhar na prática com até alguns milhares de itens.
5. Os cabeçalhos
Cada container mora no seu cabeçalho, com o mesmo nome. Os algoritmos ficam quase todos em <algorithm>; os de conta (somar tudo, por exemplo) ficam em <numeric>.
| Cabeçalho | O que traz |
|---|---|
<vector>, <array>, <list>, <deque> | As sequências |
<map> | std::map e std::multimap |
<unordered_map> | std::unordered_map e std::unordered_multimap |
<set> | std::set e std::multiset |
<unordered_set> | std::unordered_set e std::unordered_multiset |
<stack> | std::stack |
<queue> | std::queue e std::priority_queue |
<algorithm> | sort, find, count, reverse, max_element… (aula Algoritmos) |
<numeric> | accumulate (somar tudo), iota (preencher com 1, 2, 3…) |
<iterator> | Ferramentas de iteradores: std::next, std::distance… |
<ranges> | Os ranges do C++20 (aula Ranges) |
Esquecer o cabeçalho é um erro comum. A boa notícia: o g++ 15 costuma dizer qual falta.
#include <iostream>
int main() {
std::list<int> fila{1, 2, 3};
std::cout << fila.size() << '\n';
}
$ g++ -std=c++23 -Wall -Wextra sem-include.cpp -o sem-include
sem-include.cpp: In function ‘int main()’:
sem-include.cpp:4:10: error: ‘list’ is not a member of ‘std’
4 | std::list<int> fila{1, 2, 3};
| ^~~~
sem-include.cpp:2:1: note: ‘std::list’ is defined in header ‘<list>’; this is probably fixable by adding ‘#include <list>’
1 | #include <iostream>
+++ |+#include <list>
2 |
sem-include.cpp:4:15: error: expected primary-expression before ‘int’
4 | std::list<int> fila{1, 2, 3};
| ^~~
sem-include.cpp:5:18: error: ‘fila’ was not declared in this scope
5 | std::cout << fila.size() << '\n';
| ^~~~
- error: ‘list’ is not a member of ‘std’
- "
listnão é membro destd": o compilador não conhecestd::list, porque ninguém incluiu o cabeçalho dela. - note: ‘std::list’ is defined in header ‘<list>’
- A dica de ouro: "
std::listfica no cabeçalho<list>; provavelmente se conserta com#include <list>". As linhas com+++ |+mostram onde ele colocaria. - os outros dois erros
- Consequência do primeiro. Conserte o primeiro e compile de novo.
No editor do guia, o Clang diz no member named 'list' in namespace 'std' ("não há membro chamado list no namespace std"), sem a dica do cabeçalho. Use a tabela acima.
Às vezes um programa usa std::vector sem #include <vector> e compila assim mesmo, porque outro cabeçalho (como <string> ou <map>) incluiu o <vector> por dentro. Não conte com isso: em outra versão do compilador, ou em outro sistema, quebra. Inclua o cabeçalho de tudo o que você usa.
Quando dá errado
error: ‘list’ is not a member of ‘std’
O que significa: faltou o #include do container (ou o nome está errado: std::List, std::vetor).
Como consertar: leia a note: do g++ (ela diz o cabeçalho) ou procure na tabela da seção 5.
O que significa: talvez o container não combine com o uso. Exemplos: procurar um nome num vector de 1 milhão de itens, um por um, quando um map acharia direto; inserir sempre no começo de um vector, que empurra todos os outros.
Como consertar: refaça o roteiro da seção 4 pensando na operação que o programa mais faz.
Referência rápida
| Termo | Quer dizer |
|---|---|
| STL | Standard Template Library: os containers, iteradores e algoritmos da biblioteca padrão |
| Container | Objeto que guarda vários valores (vector, map…) |
| Iterador | Objeto que aponta um item e anda para o próximo (begin(), end()) |
| Algoritmo | Função que trabalha numa faixa [begin, end) (sort, find, count) |
| Sequência | Container que guarda na ordem de inserção: vector, array, list, deque |
| Associativo | Container organizado por chave: map, set, unordered_map, unordered_set |
| Adaptador | "Capa" sobre outro container: stack, queue, priority_queue |
Exercícios
1. O cabeçalho
Qual #include traz o std::sort?
2. Do começo ao fim
Complete para contar quantos 3 existem no vetor v:
3. A Ana quer guardar o estoque da lojinha: para cada nome de produto, a quantidade. E quer listar os produtos em ordem alfabética. Qual container?
4. O que liga os algoritmos aos containers na STL?
5. Você não sabe qual container escolher e nenhum caso especial se aplica. Qual é a escolha padrão?
Missão final: a lista de compras arrumada
Missão: a lista de compras da Ana está bagunçada e repetida. Use std::sort para ordenar e mostre a lista; depois use std::count para contar quantas vezes aparece "café". Saída esperada:
arroz café café feijão leite café aparece 2 vezes
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<std::string> compras{"leite", "café", "arroz", "feijão", "café"};
// ordene aqui
for (const auto& item : compras) {
std::cout << item << ' ';
}
std::cout << '\n';
// conte os cafés aqui e mostre "café aparece N vezes"
}
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<std::string> compras{"leite", "café", "arroz", "feijão", "café"};
std::sort(compras.begin(), compras.end());
for (const auto& item : compras) {
std::cout << item << ' ';
}
std::cout << '\n';
auto cafes = std::count(compras.begin(), compras.end(), "café");
std::cout << "café aparece " << cafes << " vezes\n";
}
Resumo
- A STL (Standard Template Library) é a parte da biblioteca padrão com containers, iteradores e algoritmos. Tudo nela é template.
- Containers guardam; algoritmos trabalham; iteradores ligam um ao outro. Por isso um algoritmo escrito uma vez serve para vários containers.
- Todos os containers têm
size()eempty(); quase todos têmclear(),begin()eend(). - Escolha: chave → valor,
map/unordered_map; sem repetidos,set; último/primeiro que entrou,stack/queue; tamanho fixo,array; o resto,vector. - Cada container tem seu cabeçalho com o mesmo nome; os algoritmos estão em
<algorithm>. Inclua sempre o que usa. - Próximo passo: as "mãos" da STL, em Iteradores.