The C++ Standard Template Library (STL) provides a container called std::map that stores data as key–value pairs. Think of it as a dictionary: you look up a word (the key) and get its definition (the value). The map keeps all its keys sorted automatically, and every key must be unique.
To use a map, include its header:
#include <map> #include <iostream> #include <string> using namespace std;
Example 1 — Creating a Map and Inserting Entries
A map is declared as map<KeyType, ValueType>. There are three common ways to insert entries.
#include <map>
#include <iostream>
#include <string>
using namespace std;
int main() {
map<string, int> scores; // keys are strings, values are ints
// Method 1: bracket operator (creates the key if it doesn't exist)
scores["Alice"] = 95;
scores["Bob"] = 87;
// Method 2: insert() with make_pair
scores.insert(make_pair("Carol", 91));
// Method 3: insert() with brace initializer (C++11 and later)
scores.insert({"Dave", 78});
// Print all entries — notice they are sorted by key (alphabetical)
for (auto& entry : scores) {
cout << entry.first << ": " << entry.second << "\n";
}
return 0;
}Output:
Alice: 95 Bob: 87 Carol: 91 Dave: 78
Key point: entry.first is the key and entry.second is the value. The map automatically keeps entries sorted by key — you never have to sort manually.
Example 2 — Accessing Values
There are two safe ways to read a value and one way that throws an exception on a missing key.
#include <map>
#include <iostream>
#include <string>
using namespace std;
int main() {
map<string, int> scores = {
{"Alice", 95}, {"Bob", 87}, {"Carol", 91}
};
// --- Method 1: bracket operator ---
// WARNING: if the key does not exist, it is created with value 0!
cout << "Alice's score: " << scores["Alice"] << "\n";
// --- Method 2: at() ---
// Safe read: throws std::out_of_range if the key is missing
cout << "Bob's score: " << scores.at("Bob") << "\n";
// --- Method 3: find() ---
// Returns an iterator to the entry, or map::end() if not found
auto it = scores.find("Carol");
if (it != scores.end()) {
cout << "Carol's score: " << it->second << "\n";
}
// Demonstrating the [] pitfall
cout << "Erin's score: " << scores["Erin"] << "\n"; // inserts Erin = 0!
cout << "Map now has " << scores.size() << " entries\n";
return 0;
}Output:
Alice's score: 95 Bob's score: 87 Carol's score: 91 Erin's score: 0 Map now has 4 entries
Key point: Using [] on a key that does not exist inserts that key with a default value. Use find() or at() when you only want to read.
Example 3 — Checking Whether a Key Exists
Before reading or modifying a value, it is good practice to confirm the key is present.
#include <map>
#include <iostream>
#include <string>
using namespace std;
int main() {
map<string, string> capitals = {
{"France", "Paris"},
{"Japan", "Tokyo"},
{"Brazil", "Brasília"}
};
// --- Method 1: count() ---
// Returns 1 if the key exists, 0 if not (maps have unique keys)
string country = "Japan";
if (capitals.count(country)) {
cout << country << " -> " << capitals[country] << "\n";
}
// --- Method 2: find() ---
// Preferred when you also need the value — avoids a second lookup
auto it = capitals.find("Germany");
if (it == capitals.end()) {
cout << "Germany not found in the map\n";
} else {
cout << "Germany -> " << it->second << "\n";
}
// C++20: contains() — the clearest option
// if (capitals.contains("France")) { ... }
return 0;
}Output:
Japan -> Tokyo Germany not found in the map
Example 4 — Iterating Over a Map
There are three common iteration styles. All of them visit entries in sorted key order.
#include <map>
#include <iostream>
#include <string>
using namespace std;
int main() {
map<string, int> inventory = {
{"pencil", 120}, {"notebook", 45}, {"eraser", 200}, {"ruler", 30}
};
// --- Style 1: range-based for with auto& (C++11, recommended) ---
cout << "=== Inventory ===\n";
for (auto& [item, qty] : inventory) { // structured binding (C++17)
cout << item << ": " << qty << "\n";
}
// --- Style 2: range-based for without structured binding ---
for (auto& entry : inventory) {
// entry.first = key, entry.second = value
if (entry.second < 50) {
cout << "Low stock: " << entry.first << "\n";
}
}
// --- Style 3: explicit iterator ---
cout << "=== Iterator walk ===\n";
for (map<string,int>::iterator it = inventory.begin();
it != inventory.end(); ++it) {
cout << it->first << " -> " << it->second << "\n";
}
return 0;
}Output:
=== Inventory === eraser: 200 notebook: 45 pencil: 120 ruler: 30 Low stock: notebook Low stock: ruler === Iterator walk === eraser -> 200 notebook -> 45 pencil -> 120 ruler -> 30
Example 5 — Updating and Erasing Entries
#include <map>
#include <iostream>
#include <string>
using namespace std;
int main() {
map<string, int> ages = {
{"Alice", 30}, {"Bob", 25}, {"Carol", 28}, {"Dave", 35}
};
// --- Update: bracket operator assigns a new value ---
ages["Bob"] = 26; // Bob had a birthday
cout << "Bob's updated age: " << ages["Bob"] << "\n";
// --- Erase by key ---
ages.erase("Dave");
cout << "After erasing Dave, size = " << ages.size() << "\n";
// --- Erase by iterator (useful inside a loop) ---
auto it = ages.find("Carol");
if (it != ages.end()) {
ages.erase(it);
}
cout << "After erasing Carol, size = " << ages.size() << "\n";
// --- Erase entries matching a condition (C++20 erase_if) ---
// erase_if(ages, [](auto& e) { return e.second < 28; });
// Print what remains
for (auto& [name, age] : ages) {
cout << name << ": " << age << "\n";
}
return 0;
}Output:
Bob's updated age: 26 After erasing Dave, size = 3 After erasing Carol, size = 2 Alice: 30 Bob: 26
Example 6 — Useful Map Methods at a Glance
#include <map>
#include <iostream>
#include <string>
using namespace std;
int main() {
map<int, string> m = {{1, "one"}, {2, "two"}, {3, "three"}};
cout << "size() = " << m.size() << "\n"; // number of entries
cout << "empty() = " << m.empty() << "\n"; // 0 (false) — not empty
// Smallest and largest key
cout << "First key: " << m.begin()->first << "\n"; // 1
cout << "Last key: " << prev(m.end())->first << "\n"; // 3
// lower_bound / upper_bound — find a range of keys
// lower_bound(k): first key >= k
// upper_bound(k): first key > k
auto lo = m.lower_bound(2); // points to key 2
auto hi = m.upper_bound(2); // points to key 3
cout << "lower_bound(2): " << lo->first << "\n";
cout << "upper_bound(2): " << hi->first << "\n";
m.clear();
cout << "After clear(), size = " << m.size() << "\n";
return 0;
}Output:
size() = 3 empty() = 0 First key: 1 Last key: 3 lower_bound(2): 2 upper_bound(2): 3 After clear(), size = 0
Example 7 — Real-World Use Case: Word Frequency Counter
One of the most common uses of map is counting occurrences. The [] operator’s default-to-zero behaviour makes this elegant.
#include <map>
#include <iostream>
#include <sstream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
string text = "the quick brown fox jumps over the lazy dog "
"the fox and the dog";
map<string, int> freq;
// Tokenize and count
istringstream ss(text);
string word;
while (ss >> word) {
freq[word]++; // creates entry with 0 if new, then increments
}
// Print in alphabetical order (map is already sorted)
cout << "=== Word Frequencies ===\n";
for (auto& [w, count] : freq) {
cout << w << ": " << count << "\n";
}
// Find the most common word
auto max_it = max_element(freq.begin(), freq.end(),
[](auto& a, auto& b) { return a.second < b.second; });
cout << "\nMost common: \"" << max_it->first
<< "\" (" << max_it->second << " times)\n";
return 0;
}Output:
=== Word Frequencies === and: 1 brown: 1 dog: 2 fox: 2 jumps: 1 lazy: 1 over: 1 quick: 1 the: 4 Most common: "the" (4 times)
Example 8 — Map with a Custom Key or Value Type
Any type can be a key as long as the < operator is defined. Structs need a custom comparator.
#include <map>
#include <iostream>
#include <string>
using namespace std;
struct Point {
int x, y;
// Maps need operator< to sort keys
bool operator<(const Point& other) const {
if (x != other.x) return x < other.x;
return y < other.y;
}
};
int main() {
map<Point, string> grid;
grid[{0, 0}] = "origin";
grid[{1, 0}] = "right";
grid[{0, 1}] = "up";
grid[{1, 1}] = "diagonal";
for (auto& [pt, label] : grid) {
cout << "(" << pt.x << "," << pt.y << ") = " << label << "\n";
}
return 0;
}Output:
(0,0) = origin (0,1) = up (1,0) = right (1,1) = diagonal
map vs unordered_map — Which Should You Use?
| Feature | map | unordered_map |
|---|---|---|
| Header | <map> | <unordered_map> |
| Key order | Sorted (ascending) | No guaranteed order |
| Lookup / insert / erase | O(log n) | O(1) average, O(n) worst |
| Key requirement | Must support < | Must support == and hash |
| Memory | Lower overhead | Higher (hash table buckets) |
| Best for | Sorted output, range queries | Maximum lookup speed |
Use map when you need entries in sorted order or need lower_bound/upper_bound. Use unordered_map when raw lookup speed matters and order is unimportant.
Quick Reference: Common map Operations
| Operation | Code | Notes |
|---|---|---|
| Declare | map<K,V> m; | |
| Insert / update | m[key] = val; | Creates if missing |
| Safe insert (no overwrite) | m.insert({key, val}); | Ignores if key exists |
| Read (safe) | m.at(key) | Throws if missing |
| Check existence | m.count(key) | Returns 0 or 1 |
| Find iterator | m.find(key) | Returns end() if missing |
| Erase by key | m.erase(key) | |
| Erase by iterator | m.erase(it) | |
| Size | m.size() | |
| Empty? | m.empty() | |
| Clear all | m.clear() | |
| First/last key | m.begin()->first / prev(m.end())->first |
