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.

Você vai aprender
  • O que é um std::map e como criar um: std::map<std::string, int>.
  • Três jeitos de inserir: [], insert e emplace, e a diferença entre eles.
  • Três jeitos de ler: [] (que cria a chave sem querer), at (que lança std::out_of_range) e find/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 cadernoNo std::mapExemplo
O nome do produtoA chave (key): única, não se repete"pera"
A quantidadeO valor (value)8
Uma linha do cadernoUm par chave-valor{"pera", 8}
A ordem alfabéticaO map mantém as chaves sempre ordenadasbanana, 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 do map, sem cópia intermediária.
auto [pos, inseriu] = estoque.insert(…);
O insert devolve duas coisas: um iterador para o par (o novo ou o que já estava lá) e um bool dizendo 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.
FormaChave novaChave que já existeQuando usar
m[chave] = valor;CriaTroca o valorO jeito do dia a dia: "o estoque de pera agora é 8".
m.insert({chave, valor});CriaNão mexe (devolve false)Quando o valor antigo deve ser preservado: "cadastre se ainda não existir".
m.emplace(chave, valor);CriaNão mexeIgual ao insert, um pouco mais eficiente com valores grandes.
m.insert_or_assign(chave, valor);CriaTrocaIgual 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"). O at nã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 fim nunca 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?": true ou false. 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 lerChave existeChave não existeQuando usar
m[chave]O valorCria a chave com 0 e devolve 0Quando criar é o que você quer (contar, somar). Nunca para "só olhar".
m.at(chave)O valorLança std::out_of_rangeQuando a chave tem que existir.
m.contains(chave) C++20truefalseSó para saber se existe.
m.find(chave)Iterador para o parm.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 em qtd. A chave nunca pode ser mudada (ela é const dentro 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 do echo para 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

Apareceram produtos com quantidade 0 que ninguém cadastrou

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

EscritaO 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->secondChave 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):

std::map<std::string, > notas;

2. Perguntar sem criar

Complete para perguntar se existe a chave "kiwi", sem criá-la (C++20):

if (estoque.("kiwi")) { … }

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; at lança std::out_of_range; contains C++20 e find só 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.