Another useful associative container is the map. A map is a collection of key-value pairs. Each key is unique and is used to look up its associated value. Maps are sometimes called dictionaries in other programming languages because they function like a dictionary where you look up a word (the key) to find its definition (the value).
Say we want to read a file and count how many times each word appears. A map is a perfect data structure for this task because we can use the words as keys and their counts as values. When we encounter a word, we can check if it is already in the map. If it is, we increment its count; if not, we add it to the map with a count of 1.
To declare a map, we have to specify the type of the keys and the type of the values. For example, a map from strings to integers useful for counting copies of words would be declared as map<string, int>.
To add items to a map, we can use the .insert() method. This method takes a pair consisting of a key and a value. For example, wordCounts.insert({"hello", 1}) will add the word "hello" with a count of 1 to the map. We can also use .emplace(key, value) to avoid needing to use {} to explicitly create a pair.
To access an existing value, we can use .at(key). For example, wordCounts.at("hello") will give us access to the count for the word "hello" (the integer that it is mapped to) . If "hello" is not already in the map, using .at("hello") will throw an error, so we often check if the key exists first using .contains(key).
Like a vector, we can use mapName[keyName] to access or insert elements in a map. But The bracket syntax will automatically insert the key with a default value if it does not already exist in the map. This can be convenient, but it can also lead to unintended insertions if you are not careful.
To loop through a map, we can use multiple approaches. One is to use the same kind of iterator we use for sets or vectors, which allows us to access each key-value pair in the map. As we do so, the iterator points to a pair where the first element is the key and the second element is the value. For our example, these are a string and an int, so the type of item the iterator points to is pair<string, int>.
// Dereference the iterator to get the pair it points at
pair<string, int> p = *it;
// Access the key (string) as first and value (int) as second
cout << p.first << ": " << p.second << endl;
We can shorten that by using the arrow operator directly on the iterator:
// Access key and value directly through the iterator
cout << it->first << ": " << it->second << endl;
Note21.7.2.
Recall that the arrow operator (->), or pointer member access operator, is used to access members of a struct or class through a pointer or an iterator. It is equivalent to dereferencing the pointer/iterator and then using the dot operator (.) to access the member.
We could also use a range based for loop to iterate through the map. In this case, each item in the loop would be a pair<string, int>, just like when using an iterator. Here is what that looks like:
That sample shows two versions of the same loop. The second version uses auto to simplify the type declaration of the loop variable. Notice that even when using auto, we can (and should) still use const and & to avoid unnecessary copies. If we leave them off, we would be making a copy of each pair in the map for each iteration of the loop.
Finally, C++17 introduced structured bindings which allows us to unpack the key and value directly in the loop declaration, making the code even more readable. Here, each pair is unpacked into word and count variables that are constant references to the key and value of the current item: