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.

Create a free account to save this page, take notes and track your progress. Sign in

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 with size(), substr(), find(), +=, and comparison with ==. For read-only parameters use std::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), and reserve(n).
  • std::unordered_map<K, V>: a hash table with average constant-time lookup. map[key] inserts a default value if missing; find(key) and contains(key) (C++20) don't.
  • std::map keeps keys sorted; std::set keeps unique sorted values.
  • Range-based for loops over any container. Use const auto& to avoid copies.
  • Structured bindings for (const auto& [word, count] : counts) unpack pairs.

The code

Counting words with a vector and an unordered_map
#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; use contains or find.
  • Indexing past the end of a vector with []. It is undefined behavior; use at() while learning, and check empty() before front() or back().
  • Invalidated iterators. Adding elements to a vector (or erasing from a container while looping) can invalidate iterators and references.
  • Relying on unordered_map order. It is arbitrary; sort a copy, or use std::map when order matters.
  • Copying large containers by accident. Pass const std::vector<T>&, and loop with const auto&.

Key points

  • Prefer std::vector as the default container and std::string for text; both manage their own memory.
  • std::unordered_map gives fast lookup, std::map keeps keys sorted.
  • Range-based for with const auto& and structured bindings makes loops clear and efficient.
  • operator[] on maps inserts missing keys; use contains to 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.

Was this page helpful?