C++ std::map
A Ana quer controlar o estoque da lojinha: 12 maçãs, 30 bananas, 8 peras. Com um vector, ela teria que guardar os nomes num vetor, as quantidades em outro, e procurar "pera" item por item. O std::map resolve isso: ele guarda pares "chave → valor" ("pera" → 8) e acha o valor pela chave, direto, como um dicionário acha a definição pela palavra. E ainda mantém tudo em ordem alfabética.
- O que é um
std::mape como criar um:std::map<std::string, int>. - Três jeitos de inserir:
[],inserteemplace, e a diferença entre eles. - Três jeitos de ler:
[](que cria a chave sem querer),at(que lançastd::out_of_range) efind/contains. - Como percorrer em ordem com
for (const auto& [nome, qtd] : estoque). - Como apagar com
erase. - O clássico: contar quantas vezes cada palavra aparece num texto.
Todos os programas desta aula foram compilados com o g++ 15.2 na máquina fictícia da Ana, na pasta ~/cpp/map, com g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa. É bom conhecer O que é a STL e Iteradores.
A ideia: o caderno de estoque
A Ana tem um caderno com uma linha por produto, em ordem alfabética: "banana ... 30", "maçã ... 12", "pera ... 8". Para saber quantas peras tem, ela não lê o caderno inteiro: vai direto na letra P. Cada produto aparece uma vez só; se chegam mais peras, ela corrige o número da linha "pera", não escreve outra linha.
| No caderno | No std::map | Exemplo |
|---|---|---|
| O nome do produto | A chave (key): única, não se repete | "pera" |
| A quantidade | O valor (value) | 8 |
| Uma linha do caderno | Um par chave-valor | {"pera", 8} |
| A ordem alfabética | O map mantém as chaves sempre ordenadas | banana, maçã, pera |
Em outras linguagens isso se chama "dicionário" (Python), "array associativo" (PHP) ou "objeto" (JavaScript). map quer dizer "mapa", no sentido de "mapear": ligar cada chave a um valor.
1. Criar e usar com []
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> estoque{
{"maçã", 12},
{"banana", 30},
};
estoque["pera"] = 8;
estoque["maçã"] = 15;
std::cout << "Maçãs: " << estoque["maçã"] << '\n';
std::cout << "Peras: " << estoque["pera"] << '\n';
std::cout << "Produtos diferentes: " << estoque.size() << '\n';
}
- #include <map>
- O cabeçalho do
std::map. - std::map<std::string, int>
- Um template com dois tipos: o da chave (
std::string, o nome) e o do valor (int, a quantidade). - {{"maçã", 12}, {"banana", 30},}
- Já cria com dois pares. Cada par vai entre chaves:
{chave, valor}. A vírgula depois do último é permitida. - estoque["pera"] = 8;
- Se a chave
"pera"não existe, ela é criada com o valor 8. - estoque["maçã"] = 15;
- Se a chave já existe, o valor é trocado: a maçã passa de 12 para 15. Não aparece uma segunda "maçã".
- estoque["maçã"]
- Lê o valor da chave.
$ g++ -std=c++23 -Wall -Wextra estoque.cpp -o estoque $ ./estoque Maçãs: 15 Peras: 8 Produtos diferentes: 3
Três produtos: maçã, banana, pera. As chaves não se repetem, então size() é o número de produtos diferentes.
Missão: a Ana vendeu 5 bananas e recebeu um produto novo, 6 kiwis. Use [] para tirar 5 das bananas (dica: estoque["banana"] -= 5;) e criar "kiwi" com 6. A saída deve ser Bananas: 25, Kiwis: 6 e Produtos: 3.
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> estoque{{"maçã", 12}, {"banana", 30}};
// venda 5 bananas e cadastre 6 kiwis aqui
std::cout << "Bananas: " << estoque["banana"] << '\n';
std::cout << "Kiwis: " << estoque["kiwi"] << '\n';
std::cout << "Produtos: " << estoque.size() << '\n';
}
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> estoque{{"maçã", 12}, {"banana", 30}};
estoque["banana"] -= 5;
estoque["kiwi"] = 6;
std::cout << "Bananas: " << estoque["banana"] << '\n';
std::cout << "Kiwis: " << estoque["kiwi"] << '\n';
std::cout << "Produtos: " << estoque.size() << '\n';
}
Rode também o código inicial, sem mudar nada: aparece Kiwis: 0 e Produtos: 3, mesmo sem kiwi nenhum. Por quê? É o assunto da seção 3.
2. Outras formas de inserir: insert e emplace
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> estoque{{"maçã", 12}};
estoque.insert({"uva", 40});
estoque.emplace("kiwi", 5);
auto [pos, inseriu] = estoque.insert({"maçã", 99});
std::cout << std::boolalpha << "inseriu? " << inseriu << '\n';
std::cout << "maçã continua com " << pos->second << '\n';
estoque.insert_or_assign("maçã", 99);
std::cout << "agora maçã tem " << estoque["maçã"] << '\n';
for (const auto& [nome, qtd] : estoque) {
std::cout << nome << ": " << qtd << '\n';
}
}
$ g++ -std=c++23 -Wall -Wextra inserir.cpp -o inserir $ ./inserir inseriu? false maçã continua com 12 « o insert NÃO troca um valor que já existe agora maçã tem 99 kiwi: 5 « em ordem alfabética, não na ordem em que entraram maçã: 99 uva: 40
- estoque.insert({"uva", 40});
- Insere o par só se a chave ainda não existe.
- estoque.emplace("kiwi", 5);
- O mesmo que o
insert, mas você passa a chave e o valor soltos, e o par é construído direto dentro domap, sem cópia intermediária. - auto [pos, inseriu] = estoque.insert(…);
- O
insertdevolve duas coisas: um iterador para o par (o novo ou o que já estava lá) e umbooldizendo se inseriu. Os colchetes desmontam essa dupla em duas variáveis (veja a seção 4). - pos->second
- O iterador aponta um par.
->firsté a chave e->secondé o valor. A setinha é o mesmo que(*pos).second. - insert_or_assign
- "Insere ou atribui" C++17: insere se não existe, troca se existe. Faz o mesmo que
estoque["maçã"] = 99, mas diz a intenção no nome.
| Forma | Chave nova | Chave que já existe | Quando usar |
|---|---|---|---|
m[chave] = valor; | Cria | Troca o valor | O jeito do dia a dia: "o estoque de pera agora é 8". |
m.insert({chave, valor}); | Cria | Não mexe (devolve false) | Quando o valor antigo deve ser preservado: "cadastre se ainda não existir". |
m.emplace(chave, valor); | Cria | Não mexe | Igual ao insert, um pouco mais eficiente com valores grandes. |
m.insert_or_assign(chave, valor); | Cria | Troca | Igual ao [], mas explícito; funciona também com valores que não têm construtor vazio. |
3. Ler com []: a chave criada sem querer
O [] tem uma regra que pega todo mundo: se a chave não existe, ele a cria, com o valor "vazio" do tipo (0 para int, "" para std::string). Isso vale até quando você só queria olhar:
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> estoque{{"maçã", 12}, {"pera", 8}};
std::cout << "Produtos: " << estoque.size() << '\n';
if (estoque["uva"] > 0) {
std::cout << "Tem uva!\n";
}
std::cout << "Produtos: " << estoque.size() << '\n';
for (const auto& [nome, qtd] : estoque) {
std::cout << nome << ": " << qtd << '\n';
}
}
$ g++ -std=c++23 -Wall -Wextra criar-sem-querer.cpp -o criar-sem-querer $ ./criar-sem-querer Produtos: 2 Produtos: 3 « a pergunta "tem uva?" cadastrou a uva! maçã: 12 pera: 8 uva: 0
A Ana só perguntou se havia uva, e o estoque ganhou um produto "uva" com quantidade 0. Num relatório, isso aparece como um produto que a loja nunca vendeu. É por isso que o [] é ótimo para escrever e perigoso para ler. Para ler, use at ou find/contains.
Pelo mesmo motivo, o [] não funciona num map const: como ele pode criar chaves, ele muda o map, e um map constante não pode mudar. Veja em "Quando dá errado".
4. Ler com segurança: at, find e contains
at: lê ou lança uma exceção
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> estoque{{"maçã", 12}, {"pera", 8}};
std::cout << "Maçãs: " << estoque.at("maçã") << '\n';
std::cout << "Uvas: " << estoque.at("uva") << '\n';
std::cout << "fim\n";
}
$ g++ -std=c++23 -Wall -Wextra at.cpp -o at $ ./at Maçãs: 12 terminate called after throwing an instance of 'std::out_of_range' what(): map::at Abortado (imagem do núcleo gravada) ./at $ echo $? 134
- terminate called after throwing an instance of 'std::out_of_range'
- "O programa foi encerrado depois de lançar um
std::out_of_range" ("fora do intervalo"). Oatnão achou a chave"uva"e, em vez de criá-la, lançou uma exceção. Ninguém a pegou, então o programa foi abortado. - what(): map::at
- A mensagem da exceção: veio do
map::at. Repare que ela não diz qual chave faltou. - Abortado … / 134
- O sistema matou o programa (128 + 6, o sinal de "abortar"). O
fimnunca apareceu.
No editor do guia, que usa outra biblioteca padrão (a libc++, do projeto LLVM), a mesma situação aparece como libc++abi: terminating due to uncaught exception of type std::out_of_range: map::at: key not found ("encerrando por causa de uma exceção não capturada do tipo std::out_of_range: chave não encontrada"). As palavras mudam; o tipo da exceção é o mesmo.
O at é a escolha quando a chave tem que existir: se não existe, é um erro, e é melhor parar do que seguir com um 0 inventado. Se quiser tratar o erro, use try/catch:
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>
int main() {
std::map<std::string, int> estoque{{"maçã", 12}, {"pera", 8}};
try {
std::cout << "Uvas: " << estoque.at("uva") << '\n';
} catch (const std::out_of_range& e) {
std::cout << "Erro: " << e.what() << '\n';
}
std::cout << "Produtos: " << estoque.size() << '\n';
}
$ g++ -std=c++23 -Wall -Wextra at-try.cpp -o at-try $ ./at-try Uvas: Erro: map::at « o "Uvas: " já tinha saído quando o at falhou Produtos: 2 « o at não criou a uva
find e contains: perguntar antes
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> estoque{{"maçã", 12}, {"pera", 8}};
if (estoque.contains("pera")) {
std::cout << "Tem pera\n";
}
if (!estoque.contains("uva")) {
std::cout << "Não tem uva\n";
}
auto it = estoque.find("maçã");
if (it != estoque.end()) {
std::cout << it->first << ": " << it->second << '\n';
}
std::cout << "count(\"pera\") = " << estoque.count("pera") << '\n';
std::cout << "count(\"uva\") = " << estoque.count("uva") << '\n';
std::cout << "Produtos: " << estoque.size() << '\n';
}
$ g++ -std=c++23 -Wall -Wextra procurar.cpp -o procurar
$ ./procurar
Tem pera
Não tem uva
maçã: 12
count("pera") = 1
count("uva") = 0
Produtos: 2 « nenhuma dessas perguntas criou chave
- contains("pera")
- C++20 "Contém?":
trueoufalse. A forma mais legível de perguntar. - find("maçã")
- Devolve um iterador para o par, ou
end()se não achou. Use quando, além de saber se existe, você quer usar o valor: assim procura uma vez só. - it->first, it->second
- A chave e o valor do par apontado.
- count("pera")
- Quantas vezes a chave aparece: num
map, só pode ser 0 ou 1. Era o jeito de perguntar "existe?" antes do C++20.
Com -std=c++17, o contains não existe:
$ g++ -std=c++17 -Wall -Wextra procurar.cpp -o procurar
procurar.cpp: In function ‘int main()’:
procurar.cpp:8:17: error: ‘class std::map<std::__cxx11::basic_string<char>, int>’ has no member named ‘contains’
8 | if (estoque.contains("pera")) {
| ^~~~~~~~
procurar.cpp:11:18: error: ‘class std::map<std::__cxx11::basic_string<char>, int>’ has no member named ‘contains’
11 | if (!estoque.contains("uva")) {
| ^~~~~~~~
Em código antigo, troque por estoque.count("pera") > 0 ou estoque.find("pera") != estoque.end().
| Para ler | Chave existe | Chave não existe | Quando usar |
|---|---|---|---|
m[chave] | O valor | Cria a chave com 0 e devolve 0 | Quando criar é o que você quer (contar, somar). Nunca para "só olhar". |
m.at(chave) | O valor | Lança std::out_of_range | Quando a chave tem que existir. |
m.contains(chave) C++20 | true | false | Só para saber se existe. |
m.find(chave) | Iterador para o par | m.end() | Para saber se existe e usar o valor. |
5. Percorrer em ordem: for (const auto& [nome, qtd] : estoque)
Cada item de um map é um par (std::pair), com .first (a chave) e .second (o valor). Há duas formas de percorrer:
// forma 1: structured bindings (C++17)
for (const auto& [nome, qtd] : estoque) {
std::cout << nome << ": " << qtd << '\n';
}
// forma 2: o par inteiro
for (const auto& par : estoque) {
std::cout << par.first << ": " << par.second << '\n';
}
- [nome, qtd]
- Structured bindings ("ligações estruturadas") C++17: desmonta o par em duas variáveis com nomes que você escolhe. Muito mais legível que
.first/.second. A aula pair, tuple e Desestruturação conta mais. - const auto&
- Sem cópia e sem mudar. Se quiser mudar as quantidades no laço, use
auto& [nome, qtd]e escreva emqtd. A chave nunca pode ser mudada (ela éconstdentro do par), senão a ordem se quebraria.
A ordem é sempre a das chaves, do menor para o maior: para textos, a ordem alfabética (com um detalhe: maiúsculas vêm antes das minúsculas, e letras acentuadas no começo, como em "água", vão para o fim, porque o map compara os bytes do texto; veja Acentos e UTF-8). Para números, a ordem crescente. Se a ordem não importa, existe o std::unordered_map.
6. Apagar com erase
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> estoque{{"maçã", 12}, {"pera", 0}, {"uva", 40}};
auto apagados = estoque.erase("pera");
std::cout << "apagados: " << apagados << '\n';
std::cout << "apagados: " << estoque.erase("melão") << '\n';
for (auto it = estoque.begin(); it != estoque.end(); ++it) {
std::cout << it->first << " = " << it->second << '\n';
}
}
$ g++ -std=c++23 -Wall -Wextra apagar.cpp -o apagar $ ./apagar apagados: 1 apagados: 0 « apagar uma chave que não existe não é erro maçã = 12 uva = 40
erase(chave) devolve quantos itens apagou (0 ou 1). Também existe erase(iterador), que, como no vector, devolve o iterador do próximo (aula Iteradores), e std::erase_if(estoque, …) C++20 para apagar todos os que cumprem uma condição. No map, apagar um item só invalida o iterador dele.
7. O clássico: contar palavras
O [] que cria chaves com 0 é perfeito para contar: ++contagem[palavra] funciona tanto na primeira vez (cria com 0 e soma 1) quanto nas seguintes (soma 1).
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> contagem;
std::string palavra;
while (std::cin >> palavra) {
++contagem[palavra];
}
for (const auto& [p, vezes] : contagem) {
std::cout << p << ": " << vezes << '\n';
}
}
$ g++ -std=c++23 -Wall -Wextra contar.cpp -o contar $ echo "o gato e o rato e o queijo" | ./contar e: 2 gato: 1 o: 3 queijo: 1 rato: 1
- while (std::cin >> palavra)
- Lê uma palavra por vez (o
>>separa nos espaços) até a entrada acabar (aula Entrada com cin). - ++contagem[palavra];
- Se a palavra é nova, o
[]cria com 0 e o++deixa 1. Se já existe, só soma 1. - echo "…" | ./contar
- No terminal, o
|manda o texto doechopara a entrada do programa.
Em quatro linhas de lógica: cada palavra uma vez só, com a contagem, em ordem alfabética. Esse é o tipo de problema em que o map brilha.
Quando dá errado
O que significa: algum lugar do programa leu com m[chave] uma chave que não existia, e o [] a criou.
Como consertar: para ler, use contains, find ou at. Deixe o [] para escrever.
terminate called after throwing an instance of 'std::out_of_range' / what(): map::at
O que significa: um at(chave) não achou a chave, e ninguém pegou a exceção.
Como consertar: confira antes com contains, ou envolva com try/catch (const std::out_of_range&). Se a chave deveria existir, o erro está em quem esqueceu de cadastrá-la.
error: passing ‘const std::map<…>’ as ‘this’ argument discards qualifiers
$ g++ -std=c++23 -Wall -Wextra const-map.cpp -o const-map
const-map.cpp: In function ‘void mostrar(const std::map<std::__cxx11::basic_string<char>, int>&)’:
const-map.cpp:6:45: error: passing ‘const std::map<std::__cxx11::basic_string<char>, int>’ as ‘this’ argument discards qualifiers [-fpermissive]
6 | std::cout << "Maçãs: " << estoque["maçã"] << '\n';
| ^
In file included from /usr/include/c++/15/map:65,
from const-map.cpp:2:
/usr/include/c++/15/bits/stl_map.h:547:7: note: in call to ‘std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](key_type&&) [with _Key = std::__cxx11::basic_string<char>; _Tp = int; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, int> >; mapped_type = int; key_type = std::__cxx11::basic_string<char>]’
547 | operator[](key_type&& __k)
| ^~~~~~~~
O que significa: a função recebeu o map como const std::map<…>& (só leitura) e usou [], que pode criar chaves, ou seja, mudar o map. "Passar um map constante como this descarta o const": o [] não é um método const. No Clang: no viable overloaded operator[] for type 'const std::map<std::string, int>'.
Como consertar: dentro da função, leia com estoque.at("maçã") ou com find. Não tire o const do parâmetro só para o [] funcionar.
error: ‘class std::map<…>’ has no member named ‘contains’
O que significa: compilou com um padrão anterior ao C++20.
Como consertar: -std=c++23, ou m.count(chave) > 0.
Referência rápida
| Escrita | O que faz |
|---|---|
std::map<K, V> m; | Cria um map vazio de chave K e valor V (<map>) |
m[k] = v; | Cria ou troca |
m.insert({k, v}) / m.emplace(k, v) | Cria só se não existe; devolve {iterador, inseriu} |
m.insert_or_assign(k, v) | Cria ou troca C++17 |
m[k] (lendo) | O valor; cria a chave se não existe |
m.at(k) | O valor; lança std::out_of_range se não existe |
m.contains(k) | true/false C++20 |
m.find(k) | Iterador para o par, ou m.end() |
m.count(k) | 0 ou 1 |
m.erase(k) | Apaga; devolve 0 ou 1 |
m.size(), m.empty(), m.clear() | Quantos pares, se está vazio, apagar tudo |
for (const auto& [k, v] : m) | Percorre em ordem de chave |
it->first, it->second | Chave e valor do par apontado |
Exercícios
1. O tipo
Complete o tipo de um map que liga o nome de um aluno (texto) à nota (com vírgula):
2. Perguntar sem criar
Complete para perguntar se existe a chave "kiwi", sem criá-la (C++20):
3. O map tem {{"pera", 8}}. Depois de std::cout << estoque["uva"];, quanto vale estoque.size()?
4. O map tem {{"maçã", 12}}. O que faz estoque.insert({"maçã", 50});?
5. Em que ordem o for percorre um std::map<std::string, int>?
Missão final: contar as frutas vendidas
Missão: a entrada (já preenchida) tem uma fruta por venda: banana maçã banana pera maçã banana. Use um std::map<std::string, int> para contar as vendas de cada fruta e mostre fruta: vendas, em ordem alfabética:
banana: 3 maçã: 2 pera: 1
#include <iostream>
#include <map>
#include <string>
int main() {
std::string fruta;
while (std::cin >> fruta) {
// conte a fruta aqui
}
// mostre a contagem aqui
}
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> vendas;
std::string fruta;
while (std::cin >> fruta) {
++vendas[fruta];
}
for (const auto& [nome, qtd] : vendas) {
std::cout << nome << ": " << qtd << '\n';
}
}
Resumo
std::map<Chave, Valor>guarda pares chave → valor, com chaves únicas, sempre em ordem de chave.- Escrever:
m[k] = v(cria ou troca),insert/emplace(só cria),insert_or_assign(cria ou troca). - Ler:
m[k]cria a chave se ela não existe;atlançastd::out_of_range;containsC++20 efindsó perguntam. - Percorrer:
for (const auto& [nome, qtd] : m). - Apagar:
m.erase(k), que devolve 0 ou 1. - Contar:
++contagem[palavra]. - Próximo passo: a versão sem ordem e mais rápida, std::unordered_map.