C++ Map Explained: Complete Guide to Using Maps in C++
There are many programming languages today, each with its advantages and limitations. These languages follow different operating principles to solve various problems. One such approach is the object-oriented programming (OOP) paradigm. OOP views a problem in terms of objects rather than procedures, focusing more on the entities involved and their interactions.
Object-oriented programming is a technique that organizes software design around data, or objects, rather than functions and logic. An object represents a real-world entity and contains both data and methods to manipulate that data. This paradigm allows for modular, reusable, and maintainable code.
For example, consider simulating traffic flow at a red light crossing. In an OOP approach, the problem is viewed through objects such as cars, trucks, buses, scooters, taxis, and pedestrians. Each object has characteristics (attributes) and behaviors (methods). A four-wheeled vehicle may have attributes like a steering wheel and brakes, and behaviors like moving or stopping. In this example, a car is an object belonging to a class named Vehicle.
C++ is one of the most widely used programming languages that supports object-oriented programming. It combines both high-level and low-level features, allowing efficient system-level programming alongside OOP principles. C++ is heavily used in various domains such as game development, embedded systems, and high-performance applications.
This language offers flexibility, performance, and rich libraries, making it a favorite among developers worldwide. Understanding C++ and its OOP capabilities is essential for those looking to build strong programming foundations and advance in software development careers.
C++ supports the Standard Template Library, commonly known as STL. The STL is a powerful set of C++ template classes that provide general-purpose data structures and algorithms. These templates allow programmers to reuse well-tested components and avoid writing code from scratch for common operations.
STL is broadly categorized into three components:
STL algorithms are pre-written functions that perform common operations such as searching, sorting, and manipulating data. They work with containers like arrays, vectors, and maps. Using STL algorithms makes code cleaner, easier to understand, and efficient.
Some commonly used algorithms include:
Other important algorithms are min(), max(), rotate(), min_element(), max_element(), and binary_search().
Containers store data. STL provides several container types that differ in how they manage data storage and access. Some popular containers are:
Other containers include set, multiset, hash_map, deque, and multimap.
Iterators are objects that point to elements within containers. They provide a way to access container elements sequentially without exposing the underlying structure. Iterators help in traversing and manipulating container contents efficiently.
Types of iterators include:
Iterators provide a uniform interface that algorithms can use regardless of the container type.
The map container in C++ STL is an ordered associative container. It stores elements in key-value pairs where each key is unique, and the pairs are automatically sorted based on the keys. This sorted nature allows fast retrieval of elements using keys.
Consider a scenario where you want to track the number of students enrolled in different subjects. You can use a map with subject names as keys and the number of students as values:
Key | Value |
Mathematics | 10 |
Computer_Science | 20 |
English | 15 |
Physics | 20 |
Notice that all keys (subject names) are unique, but the values (number of students) can be the same for multiple keys.
You declare a map in C++ using the following syntax:
cpp
CopyEdit
map<datatype_of_key, datatype_of_value> name_of_map;
Example:
cpp
CopyEdit
map<char, int> mp;
In this example, char is the datatype of keys, int is the datatype of values, and mp is the name of the map.
Maps are essential in scenarios where quick lookup, insertion, and deletion of key-value pairs are required. Some of the main purposes of maps include:
The sorted nature of the map and its flexibility with custom comparators make it highly valuable for developers working on complex data manipulation tasks.
Creating a map in C++ can be done in several ways, depending on the use case. Whether you want to start with an empty map and add elements later or initialize a map with predefined key-value pairs, C++ STL provides convenient syntax and functions to achieve this.
The most straightforward way to create a map is to declare it as empty and then insert elements one by one. This method is useful when you need to build the map dynamically during program execution.
cpp
CopyEdit
map<key_datatype, value_datatype> map_name;
Example:
cpp
CopyEdit
map<string, int> subjects;
This declares a map called subjects where the keys are strings (subject names) and the values are integers (number of students).
To add elements to the map, use the insert() method. The insert() method takes a pair representing the key and value to be added.
Syntax:
cpp
CopyEdit
map_name.insert({key, value});
Example:
cpp
CopyEdit
subjects.insert({“Mathematics”, 10});
subjects.insert({“Physics”, 20});
subjects.insert({“English”, 15});
These insertions add three key-value pairs into the map.
cpp
CopyEdit
#include <iostream>
#include <map>
using namespace std;
int main() {
// Declare an empty map with string keys and int values
map<string, int> subjects;
// Insert key-value pairs
for subjects.insert({“Mathematics”, 10});
subjects.insert({“Computer_Science”, 20});
subjects.insert({“English”, 15});
subjects.insert({“Physics”, 20});
// Iterate and print elements
for (auto it = subjects.begin(); it != subjects.end(); ++it) {
cout << it->first << “: ” << it->second << endl;
}
return 0;
}
If you already know the key-value pairs you want to store, you can initialize the map directly using initializer lists. This approach is cleaner and more concise.
cpp
CopyEdit
map<key_datatype, value_datatype> map_name { {key1, value1}, {key2, value2}, … };
Example:
cpp
CopyEdit
map<string, int> subjects { {“Mathematics”, 10}, {“Computer_Science”, 20} };
This creates a map named subjects with two entries right at declaration.
cpp
CopyEdit
#include <iostream>
#include <map>
using namespace std;
int main() {
map<string, int> subjects { {“Mathematics”, 10}, {“Computer_Science”, 20} };
for (const auto& pair : subjects) {
cout << pair.first << “: ” << pair.second << endl;
}
return 0;
}
The C++ map container class provides numerous member functions that allow you to manipulate the map effectively. These functions handle inserting, deleting, searching, and accessing elements. Understanding these member functions will give you more control and flexibility while working with maps.
Example:
cpp
CopyEdit
map<string, int> subjects;
subjects[“Mathematics”] = 10; // Inserts key “Mathematics” with value 10
subjects[“Physics”] = 20; // Inserts key “Physics” with value 20
cout << subjects[“Mathematics”]; // Outputs 10
Example:
cpp
CopyEdit
cout << subjects.at(“Physics”); // Outputs 20
// cout << subjects.at(“Biology”); // Throws std::out_of_range exception
Example:
cpp
CopyEdit
subjects.insert({“Chemistry”, 25});
Example:
cpp
CopyEdit
subjects.emplace(“Biology”, 18);
Examples:
cpp
CopyEdit
subjects.erase(“Physics”); // Erase by key
auto it = subjects.find(“Mathematics”);
subjects.erase(it); // Erase by iterator
subjects.erase(subjects.begin(), subjects.end()); // Erase all elements
Example:
cpp
CopyEdit
auto it = subjects.find(“English”);
if (it != subjects.end()) {
cout << “Found: ” << it->first << ” = ” << it->second << endl;
} else {
cout << “Not found” << endl;
}
Example:
cpp
CopyEdit
cout << “Number of subjects: ” << subjects.size() << endl;
if (subjects.empty()) {
cout << “Map is empty” << endl;
}
Example:
cpp
CopyEdit
subjects.clear();
cout << “Size after clearing: ” << subjects.size() << endl;
Traversing or iterating over the map elements is crucial for reading or modifying the stored data. C++ STL provides multiple ways to iterate through a map.
Iterators act like pointers that traverse through the elements in the container. Since the map stores elements sorted by keys, iteration happens in ascending order by default.
Example of using iterators:
cpp
CopyEdit
for (auto it = subjects.begin(); it != subjects.end(); ++it) {
cout << it->first << “: ” << it->second << endl;
}
Here, it->first is the key, and it->second is the value.
A cleaner and modern way to iterate through maps is using the range-based for loop, introduced in C++11.
Example:
cpp
CopyEdit
for (const auto& pair : subjects) {
cout << pair.first << “: ” << pair.second << endl;
}
Maps also support reverse iterators, allowing traversal from the largest to the smallest key.
Example:
cpp
CopyEdit
for (auto rit = subjects.rbegin(); rit != subjects.rend(); ++rit) {
cout << rit->first << “: ” << rit->second << endl;
}
Before accessing or modifying elements, you may want to check if a key exists in the map.
Example:
cpp
CopyEdit
if (subjects.find(“Mathematics”) != subjects.end()) {
cout << “Mathematics is present” << endl;
}
Alternatively, you can use the count() function, which returns the number of elements with a given key (0 or 1 for maps).
Example:
cpp
CopyEdit
if (subjects.count(“Physics”)) {
cout << “Physics is present” << endl;
}
While keys in a map cannot be changed once inserted, the values can be updated using either the bracket operator or iterators.
Example:
cpp
CopyEdit
subjects[“Mathematics”] = 12; // Update value
auto it = subjects.find(“English”);
if (it != subjects.end()) {
it->second = 16;
}
By default, C++ maps sort keys using the std::less comparator, which sorts keys in ascending order. However, you can customize the sorting order by providing a comparator function or a functor.
Define a comparator function that returns true if the first key should come before the second key.
Example:
cpp
CopyEdit
struct CustomCompare {
bool operator()(const int& lhs, const int& rhs) const {
return lhs > rhs; // Descending order
}
};
map<int, string, CustomCompare> custom_map;
Now, custom_map will sort keys in descending order.
You can also use a lambda expression as a comparator.
Example:
cpp
CopyEdit
auto cmp = [](const int& a, const int& b) { return a > b; };
map<int, string, decltype(cmp)> custom_map(cmp);
The swap() member function exchanges the contents of two maps efficiently.
Example:
cpp
CopyEdit
map<string, int> map1 { {“A”, 1}, {“B”, 2} };
map<string, int> map2 { {“X”, 24}, {“Y”, 25} };
map1.swap(map2);
After swapping, map1 contains the elements of map2 and vice versa.
Maps support range queries through lower_bound() and upper_bound() functions:
Example:
cpp
CopyEdit
auto lb = subjects.lower_bound(“English”); // Points to “English” or next higher key
auto ub = subjects.upper_bound(“English”); // Points to next key after “English”
These functions are useful for range searches and interval queries.
The std::map container is extremely versatile and widely used in real-world C++ programs. Understanding how and when to use it effectively can boost your program’s efficiency and clarity.
A common use of a map is to store key-value pairs for quick lookups, such as words and definitions, configuration parameters, or environment variables. It is ideal for counting frequencies when you want to count occurrences of elements, like counting words in a text. For example:
cpp
CopyEdit
map<string, int> wordCount;
string word;
while (cin >> word) {
wordCount[word]++;
}
Maps are also great for associating metadata where objects identified by unique IDs or keys require attributes. They can be used in caching results where memoization techniques cache intermediate results keyed by function parameters. Since std::map keeps keys sorted, it is useful when you need ordered traversal.
std::map is typically implemented as a balanced binary tree (such as a Red-Black tree). Understanding its performance characteristics helps in choosing the right container.
Operations such as insertion, deletion, and search in a std::map generally take O(log n) time, where n is the number of elements. Access by key with the subscript operator also takes O(log n) but may insert a new element if the key does not exist. Iteration over the entire map is O(n) and happens in sorted key order.
Each element in std::map consumes space for the key, value, and tree management (pointers to left child, right child, parent, and balancing info). This overhead can be considerable compared to unordered_map, which uses hash tables and typically uses less space.
C++ offers two main associative containers: std::map and std::unordered_map.
Feature | std::map | std::unordered_map |
Underlying structure | Balanced binary search tree (Red-Black) | Hash table |
Key ordering | Sorted in ascending order | No ordering (arbitrary) |
Search/insertion | O(log n) | Average O(1), worst O(n) |
Memory usage | Higher due to the tree nodes | Usually less overhead |
Iterator validity | Valid except for erased elements | Valid except for erased elements |
Use case | When sorted keys or ordered traversal are needed | When the average fast lookup/insertion needed |
cpp
CopyEdit
#include <iostream>
#include <map>
#include <unordered_map>
using namespace std;
int main() {
map<int, string> ordered_map;
unordered_map<int, string> unordered_map;
for (int i = 0; i < 5; ++i) {
ordered_map[i] = “Ordered_” + to_string(i);
unordered_map[i] = “Unordered_” + to_string(i);
}
cout << “Ordered map iteration:\n”;
for (auto& p : ordered_map) {
cout << p.first << “: ” << p.second << “\n”;
}
cout << “\nUnordered map iteration:\n”;
for (auto& p : unordered_map) {
cout << p.first << “: ” << p.second << “\n”;
}
}
The std::map class provides many member functions for manipulating map elements efficiently. These functions include insert(), erase(), find(), count(), clear(), size(), empty(), and many more.
You can insert elements using insert(), which takes a pair of key and value,,e or using the subscript operator []. The insert() function returns a pair consisting of an iterator and a bool indicating whether insertion took place or the element already existed.
Elements can be removed by key using erase(key), or by iterator using erase(iterator). You can also erase a range of elements.
The find(key) function returns an iterator to the element if found, or end() if not. The count(key) returns 1 if the key exists or 0 otherwise.
The subscript operator [] can be used to access values by key. If the key does not exist, it inserts a new element with the default value.
Developers often use maps in conjunction with custom comparator functions to define alternative orderings for the keys. For example, sorting keys in descending order or sorting complex key types like structs by specific fields.
You can define your comparator by creating a struct with operator() and passing it as the third template parameter to std::map.
Example:
cpp
CopyEdit
struct DescendingOrder {
bool operator()(const int& lhs, const int& rhs) const {
return lhs > rhs; // reverse sorting order
}
};
map<int, string, DescendingOrder> reverse_map;
Maps can use complex data types as keys or values. For keys, the data type must support comparison operators by default, or you must provide a comparator. For values, any type that can be copied or moved is supported.
Example with struct keys:
cpp
CopyEdit
struct Point {
int x, y;
bool operator<(const Point& other) const {
if (x == other.x)
return y < other.y;
return x < other.x;
}
};
map<Point, string> pointMap;
Maps support bidirectional iterators, allowing traversal from beginning to end and vice versa. Iterators can be used to access keys and values via the ->first and ->second members of the pair.
You can also use range-based for loops for cleaner code:
cpp
CopyEdit
for (const auto& pair : pointMap) {
cout << “Point(” << pair.first.x << “,” << pair.first.y << “) -> ” << pair.second << “\n”;
The std::map container provides several modifier functions to manipulate the elements within the map efficiently. These functions include insert(), erase(), clear(), swap(), and emplace().
The insert() function has multiple overloaded versions that enable you to add new elements into the map. You can insert elements by passing a pair, by using hint iterators to optimize insertion, or by inserting a range of elements.
Using insert with a pair:
cpp
CopyEdit
map<int, string> myMap;
myMap.insert(pair<int, string>(1, “One”));
Using insert with hint iterator:
cpp
CopyEdit
auto it = myMap.begin();
myMap.insert(it, pair<int, string>(2, “Two”));
Inserting a range:
cpp
CopyEdit
map<int, string> anotherMap;
anotherMap.insert(myMap.begin(), myMap.end());
The emplace() function constructs the element in place without copying or moving it, often resulting in better performance. It takes constructor arguments for the element’s key and value:
cpp
CopyEdit
myMap.emplace(3, “Three”);
Elements can be erased in several ways:
Example of erasing by key:
cpp
CopyEdit
myMap.erase(2);
Erasing by iterator:
cpp
CopyEdit
auto it = myMap.find(1);
if (it != myMap.end()) {
myMap.erase(it);
}
Erasing by range:
cpp
CopyEdit
auto start = myMap.begin();
auto end = myMap.find(3);
myMap.erase(start, end);
std::map offers functions to find and check the presence of elements efficiently.
The find() function searches for the key and returns an iterator to the element or end() if the key is not found.
cpp
CopyEdit
auto it = myMap.find(3);
if (it != myMap.end()) {
cout << “Found key 3 with value: ” << it->second << endl;
} else {
cout << “Key 3 not found” << endl;
}
The count() function returns 1 if the key exists, else 0 since std::map stores unique keys.
cpp
CopyEdit
if (myMap.count(3)) {
cout << “Key 3 exists in the map” << endl;
}
std::map provides observer functions key_comp() and value_comp() to obtain the comparison objects used internally.
The key_comp() returns a function object that compares keys and can be used for custom ordering checks.
cpp
CopyEdit
auto comp = myMap.key_comp();
if (comp(1, 2)) {
cout << “1 is considered less than 2” << endl;
}
The value_comp() returns a function object that compares key-value pairs.
The allocator_type can be accessed via get_allocator(), which returns the allocator object used by the map to handle memory.
cpp
CopyEdit
auto alloc = myMap.get_allocator();
This is useful when you want to understand or customize how memory is allocated for the map’s elements.
A common use of std::map is to count the frequency of words in a body of text.
cpp
CopyEdit
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
map<string, int> frequency;
string word;
while (cin >> word) {
frequency[word]++;
}
for (const auto& pair : frequency) {
cout << pair.first << “: ” << pair.second << endl;
}
return 0;
}
This program reads words from the standard input and outputs each unique word and its count in sorted order.
std::map can be used to implement a phonebook where names are unique keys and phone numbers are values.
cpp
CopyEdit
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
map<string, string> phonebook;
phonebook[“Alice”] = “123-456-7890”;
phonebook[“Bob”] = “098-765-4321”;
string name;
cout << “Enter a name to look up: “;
getline(cin, name);
auto it = phonebook.find(name);
if (it != phonebook.end()) {
cout << name << “‘s number is ” << it->second << endl;
} else {
cout << “Name not found in phonebook.” << endl;
}
return 0;
}
Memoization stores the results of expensive function calls so that future calls with the same parameters return immediately.
cpp
CopyEdit
#include <iostream>
#include <map>
using namespace std;
map<int, long long> fib_cache;
long long fibonacci(int n) {
if (n <= 1) return n;
if (fib_cache.find(n) != fib_cache.end()) return fib_cache[n];
fib_cache[n] = fibonacci(n – 1) + fibonacci(n – 2);
return fib_cache[n];
}
int main() {
int n = 50;
cout << “Fibonacci(” << n << “) = ” << fibonacci(n) << endl;
return 0;
}
Maps by default sort keys in ascending order. You can customize this behavior with your comparator.
Example sorting keys by their absolute values:
cpp
CopyEdit
#include <iostream>
#include <map>
#include <cmath>
using namespace std;
struct AbsCompare {
bool operator()(int a, int b) const {
return abs(a) < abs(b);
}
};
int main() {
map<int, string, AbsCompare> absMap;
absMap[-10] = “Negative Ten”;
absMap[5] = “Five”;
absMap[-3] = “Negative Three”;
for (const auto& p : absMap) {
cout << p.first << “: ” << p.second << endl;
}
return 0;
}
This map will be sorted by the absolute values of the keys.
Choose std::map when you require ordered key-value pairs with unique keys and O(log n) complexity for insertions and lookups. For faster average-case lookups without order, prefer std::unordered_map.
If you expect frequent insertions in very large maps, consider using std::unordered_map for faster average insertion and lookup time.
Use emplace() instead of insert() when constructing elements in place to avoid unnecessary copies or moves, especially for complex key or value types.
In std::map, inserting or erasing elements invalidates iterators pointing to the erased elements, but other iterators remain valid.
When defining custom comparators, ensure that they implement strict weak ordering to avoid undefined behavior.
Keys in std::map are const once inserted. Modifying them can cause undefined behavior. Always modify values, not keys.
Using the subscript operator [] inserts default values if the key is absent. Use find() for read-only access to avoid unintended insertions.
When using find(), always check if the returned iterator is equal to end() before dereferencing.
std::map is not thread-safe. Protect access with mutexes when sharing between threads.
The C++ std::map container is a powerful associative container that stores key-value pairs in sorted order with unique keys. It provides efficient insertion, deletion, and lookup operations with O(log n) complexity. The rich set of member functions, support for custom comparators, and integration with STL algorithms make it highly versatile. Proper understanding of its performance characteristics, iterator behavior, and memory usage is essential for writing efficient and maintainable C++ code. Practical applications include frequency counting, caching, indexing, and ordered data management. Avoid common pitfalls by respecting the immutability of keys, choosing the right container for your use case, and using member functions correctly.
Popular posts
Recent Posts