Modern C++ Fundamentals: From Basics to Smart Pointers and Threads · Modern C++ Basics: Types, Containers, Functions and Classes
Strings, Vectors and Maps: The Everyday Containers
Use std::string, std::vector and std::unordered_map, loop with range-based for and structured bindings, and find, count and group data without manual memory management.
What you'll build
A word-frequency report: split a sentence into words, count each word in a hash map, sort the results and print them.
The idea
The standard library gives you containers that manage their own memory, so you rarely need raw arrays or new.
std::string: a growable string withsize(),substr(),find(),+=, and comparison with==. For read-only parameters usestd::string_view, a cheap non-owning view.std::vector<T>: a growable array and the container to reach for first.push_back,emplace_back,size(),at(i)(bounds-checked),[i](unchecked), andreserve(n).std::unordered_map<K, V>: a hash table with average constant-time lookup.map[key]inserts a default value if missing;find(key)andcontains(key)(C++20) don't.std::mapkeeps keys sorted;std::setkeeps unique sorted values.- Range-based
forloops over any container. Useconst auto&to avoid copies. - Structured bindings
for (const auto& [word, count] : counts)unpack pairs.
The code
#include <algorithm>
#include <iostream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
std::string to_lower(std::string text) {
std::transform(text.begin(), text.end(), text.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return text;
}
std::vector<std::string> split_words(const std::string& text) {
std::vector<std::string> words;
std::istringstream stream{text};
std::string word;
while (stream >> word) {
// strip trailing punctuation
while (!word.empty() && !std::isalnum(static_cast<unsigned char>(word.back()))) word.pop_back();
if (!word.empty()) words.push_back(to_lower(word));
}
return words;
}
int main() {
const std::string text = "The quick brown fox jumps over the lazy dog. The dog sleeps; the fox runs!";
const auto words = split_words(text);
std::cout << words.size() << " words, first: " << words.front() << ", last: " << words.back() << '\n';
std::unordered_map<std::string, int> counts;
for (const auto& word : words) {
++counts[word]; // missing keys start at 0
}
// Copy into a vector of pairs so we can sort by count (hash maps are unordered)
std::vector<std::pair<std::string, int>> ranked(counts.begin(), counts.end());
std::sort(ranked.begin(), ranked.end(), [](const auto& a, const auto& b) {
return a.second != b.second ? a.second > b.second : a.first < b.first;
});
for (const auto& [word, count] : ranked) {
if (count > 1) std::cout << word << ": " << count << '\n';
}
std::cout << "distinct words: " << counts.size() << '\n';
std::cout << "contains 'fox': " << std::boolalpha << counts.contains("fox") << '\n';
std::cout << "contains 'cat': " << counts.contains("cat") << '\n';
}
Output
15 words, first: the, last: runs the: 4 dog: 2 fox: 2 distinct words: 10 contains 'fox': true contains 'cat': false
Run this on your computer — see “Get Started” for setup.
Common mistakes
- Using
map[key]to test for existence. It inserts a default entry; usecontainsorfind. - Indexing past the end of a vector with
[]. It is undefined behavior; useat()while learning, and checkempty()beforefront()orback(). - Invalidated iterators. Adding elements to a vector (or erasing from a container while looping) can invalidate iterators and references.
- Relying on
unordered_maporder. It is arbitrary; sort a copy, or usestd::mapwhen order matters. - Copying large containers by accident. Pass
const std::vector<T>&, and loop withconst auto&.
Key points
- Prefer
std::vectoras the default container andstd::stringfor text; both manage their own memory. std::unordered_mapgives fast lookup,std::mapkeeps keys sorted.- Range-based
forwithconst auto&and structured bindings makes loops clear and efficient. operator[]on maps inserts missing keys; usecontainsto test.
Further reading: cppreference: Containers library.
Practice questions
Why can `counts["cat"]` be a surprising way to check for a word?
Answer
operator[] on a map inserts a default-constructed value (0 for int) when the key is missing, so merely checking changes the map and makes the key appear present. Use contains() or find() to test without inserting.
Why sort a vector of pairs instead of iterating an unordered_map for a ranked report?
Answer
An unordered_map has no defined order, and it can't be sorted in place. Copying its entries into a vector lets you sort them by count with std::sort.
Open in the app to mark lessons complete, take notes and track your progress.