C++ Learning Manual – Templates and Generic Programming 39 – STL Containers and Algorithms (vector, map, sort)

In previous studies, we delved into the mechanisms of C++ templates, including function templates and class templates. They are the cornerstone of generic programming—writing code independent of data types. The STL (Standard Template Library) is the most outstanding practice of the generic programming concept. It provides a series of generic, type-safe, high-performance containers, algorithms, and iterators that work perfectly together through template technology.

This article will focus on the three core components of the STL: the two most commonly used containers, <span>vector</span> and <span>map</span>, and a powerful algorithm <span>sort</span>, demonstrating how they can be used together through examples.

1. Sequence Container:<span>std::vector</span>

<span>std::vector</span> is a dynamic array and a typical representative of sequence containers. It can automatically manage memory, dynamically grow or shrink at runtime, and provides fast random access to elements.

1.1 Basic Usage
#include <iostream>
#include <vector> // Must include vector header

int main() {
    // 1. Define a vector that stores int
    std::vector<int> myVector;

    // 2. Add elements to the end (dynamic resizing)
    myVector.push_back(10);
    myVector.push_back(20);
    myVector.push_back(30);

    // 3. Access elements using index like an array
    std::cout << "First element: " << myVector[0] << std::endl; // Output 10

    // 4. Use at() for access, which performs boundary checking, safer
    std::cout << "Second element: " << myVector.at(1) << std::endl; // Output 20
    // myVector.at(5); // If out of bounds, throws std::out_of_range exception

    // 5. Get size and capacity
    std::cout << "Size: " << myVector.size() << std::endl;     // Current number of elements: 3
    std::cout << "Capacity: " << myVector.capacity() << std::endl; // Current allocated capacity

    // 6. Range for loop traversal
    std::cout << "Vector elements: ";
    for (int num : myVector) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}
1.2 The Power of Generics

<span>vector</span> is a class template, which means it can hold almost any type.

#include <vector>
#include <string>

// Store strings
std::vector<std::string> stringVec = {"Hello", "World", "from", "C++"};

// Store custom struct
struct Point {
    double x, y;
};
std::vector<Point> pointsVec = {{1.0, 2.5}, {3.4, 5.6}};

// Even store another vector (2D array)
std::vector<std::vector<int>> matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

2. Associative Container:<span>std::map</span>

<span>std::map</span> is an associative container that stores key-value pairs. Each key is unique, and the container automatically sorts based on the key’s value (default in ascending order). It provides fast lookup capabilities based on keys.

2.1 Basic Usage
#include <iostream>
#include <map>   // Must include map header
#include <string>

int main() {
    // 1. Define a map, key is string, value is int
    // Can be used to store student names and scores
    std::map<std::string, int> studentScores;

    // 2. Insert key-value pairs
    studentScores["Alice"] = 95;   // Insert using subscript operator
    studentScores["Bob"] = 87;
    studentScores.insert(std::make_pair("Charlie", 92)); // Insert using insert function

    // 3. Access elements (by key)
    std::cout << "Alice's score: " << studentScores["Alice"] << std::endl;

    // Note: Accessing a non-existent key with subscript will automatically create that key (value initialized)
    std::cout << "David's score (new): " << studentScores["David"] << std::endl; // Output 0

    // 4. Find elements (safe way)
    auto it = studentScores.find("Bob");
    if (it != studentScores.end()) { // Check if found
        std::cout << "Found Bob, score: " << it->second << std::endl; // it->first is key, it->second is value
    } else {
        std::cout << "Bob not found." << std::endl;
    }

    // 5. Traverse map
    std::cout << "\nAll students and scores:" << std::endl;
    for (const auto& pair : studentScores) { // pair is a std::pair<const std::string, int>
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

3. Generic Algorithm:<span>std::sort</span>

The STL provides a large number of generic algorithms independent of containers, which operate on elements in containers through iterators.<span>std::sort</span> is one of the most commonly used algorithms for sorting sequences.

3.1 Sorting <span>vector</span>
#include <iostream>
#include <vector>
#include <algorithm> // Must include algorithm header

int main() {
    std::vector<int> numbers = {5, 2, 8, 1, 9, 3};

    // Use std::sort for default sorting (ascending)
    // numbers.begin() and numbers.end() are iterators defining the sorting range
    std::sort(numbers.begin(), numbers.end());

    std::cout << "Sorted (ascending): ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // Use custom comparison function for descending sorting
    // Here we use a Lambda expression
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a > b; // If a is greater than b, a should come first
    });

    std::cout << "Sorted (descending): ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}
3.2 Combining <span>vector</span> and <span>sort</span> to Handle Complex Data

This is where the true power of the STL is demonstrated: containers, algorithms, and iterators work seamlessly together.

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

struct Product {
    std::string name;
    double price;
};

int main() {
    std::vector<Product> products = {
        {"Laptop", 999.99},
        {"Mouse", 24.99},
        {"Keyboard", 49.99},
        {"Monitor", 299.99}
    };

    // Task: Sort by price from low to high
    std::sort(products.begin(), products.end(), [](const Product& a, const Product& b) {
        return a.price < b.price;
    });

    std::cout << "Products sorted by price (low to high):\n";
    for (const auto& prod : products) {
        std::cout << prod.name << ": $" << prod.price << std::endl;
    }

    return 0;
}

4. Comprehensive Case: Word Frequency Counter

Let’s use a comprehensive case to demonstrate the powerful combination of <span>vector</span>, <span>map</span>, and <span>sort</span>. This program counts the occurrences of each word in a text and outputs them in order of frequency from high to low.

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <sstream>
#include <cctype>

// Helper function: convert string to lowercase
std::string toLower(const std::string& str) {
    std::string result = str;
    for (char& c : result) {
        c = std::tolower(c);
    }
    return result;
}

int main() {
    std::string text = "C++ is a powerful language. C++ is widely used for system programming. Power and performance are key in C++.";

    std::map<std::string, int> wordCount;

    // Use string stream to split text into words
    std::istringstream iss(text);
    std::string word;

    while (iss >> word) {
        // Simple cleanup of words (remove punctuation, convert to lowercase)
        // Note: This is a simple example; real text processing is more complex
        if (!std::isalpha(word.back())) {
            word.pop_back(); // Remove trailing punctuation, e.g., '.'
        }
        word = toLower(word);

        // Count
        wordCount[word]++;
    }

    // To sort, we need to move elements (pairs) from map to vector
    // Because std::sort needs to work with random access iterators, and map's iterators are not
    std::vector<std::pair<std::string, int>> vec(wordCount.begin(), wordCount.end());

    // Sort vector by word frequency in descending order
    std::sort(vec.begin(), vec.end(),
              [](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) {
                  return a.second > b.second;
              });

    // Output results
    std::cout << "Word Frequency:\n";
    for (const auto& entry : vec) {
        std::cout << entry.first << ": " << entry.second << std::endl;
    }

    return 0;
}
/* Possible output:
Word Frequency:
c++: 3
is: 2
a: 1
powerful: 1
language: 1
widely: 1
...
*/

Conclusion

Through this chapter, we have seen how the STL maximizes the power of templates and generic programming:

  1. Containers (<span>vector</span>, <span>map</span>): As class templates, they are responsible for storing and managing collections of data in a generic way.
  2. Algorithms (<span>sort</span>): As function templates, they are responsible for operating on data in containers (such as sorting, searching, modifying). They do not depend on specific container types, only on the iterator concept.
  3. Iterators: As the glue between containers and algorithms, they provide a generic way to traverse container elements.

This design of “separating data structures and algorithms” is one of the core ideas of generic programming. It greatly enhances the reusability and flexibility of code. You can create containers for custom data types and immediately operate on them using the hundreds of algorithms already available in the STL without rewriting sorting, searching, and other logic. Mastering the STL is essential for becoming an efficient C++ programmer.

Leave a Comment