Buscar coisas pela chave
Um vector é ótimo quando você indexa por posição: elemento 0, elemento 1, e assim por diante. Mas muitas vezes você não tem uma posição; você tem um nome e quer aquilo que está associado a ele: um usuário e sua pontuação, uma palavra e quantas vezes ela aparece, um código de país e sua capital. Percorrer um vector para encontrar uma correspondência é O(n) e fica lento rapidamente.
std::map resolve isso. Ele armazena pares chave-valor, mantém-nos ordenados pela chave e permite buscar um valor pela sua chave em tempo O(log n). Inclua <map> para usá-lo:
map<string, int> lê-se como "um map de chaves string para valores int". As chaves são únicas: se você atribuir duas vezes à mesma chave, o segundo valor vence. Internamente, um map é uma árvore binária de busca balanceada, por isso tudo permanece ordenado e as buscas são logarítmicas em vez de constantes.
Inserir elementos
Há algumas formas de adicionar entradas, e a diferença entre elas importa. A mais comum é operator[], que cria a chave se ela não existir e retorna uma referência à qual você pode atribuir:
Se você quer uma inserção que se recuse a sobrescrever uma chave existente, use insert ou emplace. Ambos retornam um pair cujo .second é um bool que informa se a inserção realmente aconteceu:
Use [] quando "a última escrita vence" for o que você quer, e insert/emplace quando uma chave existente deve ser deixada em paz.
A armadilha do operator[]: ele insere ao ler
Esse é o bug mais comum com map. operator[] não é uma leitura pura: se a chave estiver ausente, ele a insere silenciosamente com um valor construído por padrão (0 para int, "" para string, etc.) e retorna uma referência a esse valor. Então o simples ato de verificar uma chave com [] modifica o map:
map<string, int> m;
if (m["maybe"] == 0) { // BUG: isso acabou de criar "maybe" -> 0
// ...
}
cout << m.size(); // 1, não 0 - você inseriu uma chave por acidente
Isso também te pega com um const map, onde operator[] nem sequer compila porque ele pode precisar inserir. Para ler sem inserir, use find, count/contains ou at:
Regra prática: se sua intenção é ler, nunca recorra a []. Use at quando a chave precisa existir, e find/contains quando ela pode não existir.
Iterar em ordem
Como um map é baseado em árvore, iterá-lo visita as chaves em ordem crescente sempre, e de graça. Cada elemento é um pair<const Key, Value>, então use uma ligação estruturada para desempacotar a chave e o valor de forma limpa:
Repare que a chave na ligação é const: você pode alterar um valor pelo laço com auto&, mas nunca pode alterar uma chave no lugar (isso quebraria a ordenação). A saída sai em ordem alfabética (blue, sea, sky) sem nenhum passo de ordenação, que é exatamente o motivo pelo qual as pessoas escolhem map em vez de uma tabela hash quando a travessia ordenada importa.
Esse idioma wordCount[w]++ é também o uso canônico do comportamento de inserção ao acessar: aqui você quer que uma chave nova comece em 0, então [] é a ferramenta certa.
Remover elementos e medir o tamanho
Remova por chave com erase, que retorna quantos elementos foram removidos (0 ou 1 em um map). Você também pode remover por meio de um iterador obtido com find. Verifique o estado do contêiner com size() e empty():
Uma armadilha ao remover dentro de um laço: m.erase(it) invalida it, então você não pode fazer ++it depois. O padrão seguro desde C++11 é it = m.erase(it), que retorna um iterador para o próximo elemento. Para uma remoção condicional de uma única passada por todo o map, std::erase_if(m, predicate) (C++20) é mais limpo.
Próximo: unordered_map
std::map te dá chaves ordenadas e operações O(log n) previsíveis, mas você paga por essa ordenação em cada busca. Quando você não se importa com a ordem das chaves e só quer as buscas mais rápidas possíveis, unordered_map troca a árvore ordenada por uma tabela hash: acesso O(1) em média. A seguir veremos como ele funciona, quando seu tempo constante médio supera o logaritmo do map e as armadilhas do hashing (tipos de chave personalizados, colisões no pior caso) que vêm junto.
Perguntas frequentes
O que é um std::map em C++?
Um std::map é um contêiner associativo que armazena pares chave-valor ordenados pela chave. Buscas, inserções e remoções são todas O(log n) porque ele é implementado sobre uma árvore binária de busca balanceada. Cada chave é única: inserir uma chave duplicada não altera o valor existente.
Qual é a diferença entre operator[] e at() em um map de C++?
map[key] retorna uma referência ao valor e, se a chave não existir, ela é inserida silenciosamente com um valor construído por padrão. map.at(key) também retorna uma referência, mas lança std::out_of_range se a chave estiver ausente, em vez de inseri-la. Use at() (ou find()) quando você só quer ler.
Como verifico se uma chave existe em um map de C++?
Use m.contains(key) (C++20), m.count(key) que retorna 0 ou 1, ou m.find(key) != m.end(). Evite testar com m[key]: isso insere a chave se ela não existir, o que raramente é o que você quer.