Menu

C++ std::map: claves, valores, búsqueda e inserción

El std::map de C++ explicado: un contenedor clave-valor ordenado con búsqueda logarítmica. Inserta, busca, itera y evita el clásico fallo de operator[] que inserta claves sin avisar.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Buscar cosas por clave

Un vector es ideal cuando indexas por posición: el elemento 0, el elemento 1, etc. Pero a menudo no tienes una posición, sino un nombre y quieres lo que va asociado a él: un usuario y su puntuación, una palabra y su número de apariciones, un código de país y su capital. Recorrer un vector para encontrar una coincidencia es O(n) y se vuelve lento enseguida.

std::map resuelve esto. Almacena pares clave-valor, los mantiene ordenados por clave y te permite buscar un valor por su clave en tiempo O(log n). Incluye <map> para usarlo:

map<string, int> se lee como "un map de claves string a valores int". Las claves son únicas: si asignas dos veces a la misma clave, gana el segundo valor. Internamente, un map es un árbol binario de búsqueda equilibrado, por eso todo se mantiene ordenado y las búsquedas son logarítmicas en lugar de constantes.

Insertar elementos

Hay varias formas de añadir entradas, y la diferencia entre ellas importa. La más común es operator[], que crea la clave si no existe y devuelve una referencia a la que puedes asignar:

Si quieres una inserción que se niegue a sobrescribir una clave existente, usa insert o emplace. Ambas devuelven un pair cuyo .second es un bool que te dice si la inserción ocurrió realmente:

Usa [] cuando quieras que "gane la última escritura", e insert/emplace cuando una clave existente deba quedarse intacta.

El fallo de operator[]: inserta al leer

Este es el error más común con map. operator[] no es una lectura pura: si la clave no existe, la inserta silenciosamente con un valor construido por defecto (0 para int, "" para string, etc.) y devuelve una referencia a ese valor. Así que el simple hecho de comprobar una clave con [] modifica el map:

map<string, int> m;
if (m["maybe"] == 0) {   // BUG: esto acaba de crear "maybe" -> 0
    // ...
}
cout << m.size();        // 1, no 0 - insertaste una clave por accidente

Esto también te muerde con un const map, donde operator[] ni siquiera compila porque podría necesitar insertar. Para leer sin insertar, usa find, count/contains o at:

Regla práctica: si tu intención es leer, nunca recurras a []. Usa at cuando la clave deba existir, y find/contains cuando podría no existir.

Iterar en orden

Como un map está respaldado por un árbol, iterarlo recorre las claves en orden ascendente siempre, y gratis. Cada elemento es un pair<const Key, Value>, así que usa un enlace estructurado para desempaquetar la clave y el valor de forma limpia:

Fíjate en que la clave del enlace es const: puedes cambiar un valor en el bucle con auto&, pero nunca puedes cambiar una clave en su sitio (eso rompería el orden). La salida sale en orden alfabético (blue, sea, sky) sin ningún paso de ordenación, que es exactamente por lo que la gente elige map frente a una tabla hash cuando importa recorrer en orden.

Ese idioma wordCount[w]++ es también el uso canónico del comportamiento de inserción al acceder: aquí quieres que una clave nueva empiece en 0, así que [] es la herramienta adecuada.

Eliminar elementos y medir el tamaño

Elimina por clave con erase, que devuelve cuántos elementos se eliminaron (0 o 1 en un map). También puedes borrar mediante un iterador obtenido con find. Comprueba el estado del contenedor con size() y empty():

Un fallo al borrar dentro de un bucle: m.erase(it) invalida it, así que no puedes hacer ++it después. El patrón seguro desde C++11 es it = m.erase(it), que devuelve un iterador al siguiente elemento. Para una eliminación condicional de una sola pasada por todo el map, std::erase_if(m, predicate) (C++20) es más limpio.

Siguiente: unordered_map

std::map te da claves ordenadas y operaciones O(log n) predecibles, pero pagas ese orden en cada búsqueda. Cuando no te importa el orden de las claves y solo quieres las búsquedas más rápidas posibles, unordered_map cambia el árbol ordenado por una tabla hash: acceso O(1) en promedio. A continuación veremos cómo funciona, cuándo su tiempo constante medio supera al logaritmo de map y las trampas del hashing (tipos de clave personalizados, colisiones en el peor caso) que conlleva.

Preguntas frecuentes

¿Qué es un std::map en C++?

Un std::map es un contenedor asociativo que almacena pares clave-valor ordenados por clave. Las búsquedas, inserciones y eliminaciones son todas O(log n) porque está respaldado por un árbol binario de búsqueda equilibrado. Cada clave es única: insertar una clave duplicada no modifica el valor existente.

¿Cuál es la diferencia entre operator[] y at() en un map de C++?

map[key] devuelve una referencia al valor y, si la clave no existe, la inserta silenciosamente con un valor construido por defecto. map.at(key) también devuelve una referencia, pero lanza std::out_of_range si la clave no está, en lugar de insertarla. Usa at() (o find()) cuando solo quieras leer.

¿Cómo compruebo si una clave existe en un map de C++?

Usa m.contains(key) (C++20), m.count(key) que devuelve 0 o 1, o m.find(key) != m.end(). Evita comprobarlo con m[key]: eso inserta la clave si no existe, que casi nunca es lo que quieres.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR