SrcForge

Lesson 52

STL Algorithms and Iterators (part 1)

Covers what STL algorithms and iterators are, iterator syntax and operations, iterator categories, the relationship between algorithms and iterators, and traversing containers.

Lesson content

Introduction to STL Algorithms and Iterators

The Standard Template Library (STL) is one of the most powerful features of C++. It provides a rich collection of containers, algorithms, iterators, and function objects that help developers write efficient and reusable code.

Among these components, algorithms and iterators work together as the backbone of the STL. Algorithms define what operation to perform, while iterators define where to perform it.

Imagine a library. Books represent data stored in containers. A librarian knows how to organize, search, or rearrange books (algorithms). A bookmark points to a specific book (iterator).

Instead of writing loops for every task, C++ lets you use ready-made algorithms with iterators, making your programs shorter, cleaner, and less error-prone.

Why do STL algorithms and iterators exist?

Before the STL, programmers often had to write repetitive code for tasks like searching for an element, sorting a collection, counting occurrences, reversing data, and copying values. This repetitive code increased development time and introduced bugs.

The STL solves this by providing generic algorithms that work with many container types through iterators.

Real-world use cases

  • Sorting student records
  • Searching customer databases
  • Filtering log files
  • Data analytics
  • Financial applications
  • Game development
  • Competitive programming
  • Machine learning preprocessing

Prerequisites

  • Variables and data types
  • Functions
  • Arrays
  • Loops (for, while)
  • Basic object-oriented programming
  • Templates (basic idea)
  • STL containers such as std::vector

What is the Standard Template Library (STL)?

The Standard Template Library (STL) is a collection of reusable C++ components designed to simplify common programming tasks. It consists of four main parts, all designed to work together seamlessly.

  • Containers – store data (vector, list, map, etc.)
  • Algorithms – perform operations (sort, find, count, etc.)
  • Iterators – access and traverse container elements
  • Function objects (functors) – custom behavior for algorithms

What are STL algorithms?

An algorithm is a pre-written function that performs a common operation on a range of elements, such as sorting, searching, counting, copying, reversing, transforming, or removing elements. Instead of writing custom loops, you can simply call an STL algorithm.

// Without STL
for (int i = 0; i < size; i++)
{
    if (arr[i] == value)
    {
        // Found
    }
}

// With STL
std::find(begin, end, value);

// The STL version is shorter, easier to read, and less prone to errors.

What are iterators?

An iterator is an object that points to an element within a container. It behaves much like a pointer and allows algorithms to work with different container types using a common interface.

Think of an iterator as a cursor in a text editor. The cursor doesn't hold the text; it simply points to a position, allowing you to move forward, backward, or access the current character.

Why use iterators?

Iterators provide a consistent way to access elements, traverse containers, and connect containers with algorithms. This means the same algorithm can work with different containers without modification.

Basic iterator syntax

Most STL containers provide member functions to obtain iterators.

container.begin();
container.end();

begin() returns an iterator to the first element. end() returns an iterator one position past the last element. Note that end() does not point to a valid element; it acts as a sentinel marking the end of the range.

Iterator operations

  • *it – access the element pointed to by the iterator
  • ++it – move to the next element
  • --it – move to the previous element (where supported)
  • it1 == it2 – compare iterators
  • it1 != it2 – check if iterators are different

Types of iterators

Different containers support different iterator capabilities.

  • Input iterator – can read, cannot write, forward only
  • Output iterator – cannot read, can write, forward only
  • Forward iterator – can read and write, forward only
  • Bidirectional iterator – can read and write, forward and backward
  • Random access iterator – can read and write, forward, backward, and supports random access

Containers and their iterator types

  • std::vector – random access
  • std::deque – random access
  • std::array – random access
  • std::list – bidirectional
  • std::set – bidirectional
  • std::map – bidirectional
  • std::forward_list – forward

Understanding iterator categories helps you know which algorithms and operations are supported.

The relationship between algorithms and iterators

Algorithms don't know anything about containers. Instead, they operate on ranges defined by iterators: begin() points to the first element, an algorithm operates across the range, and end() points one past the last element. This design allows the same algorithm to work with many different containers.

Working with iterator ranges

Most STL algorithms take two iterators.

algorithm(begin, end);

The range includes the element pointed to by begin() and all elements up to, but not including, end(). This is known as a half-open range: [begin, end).

Example 1: Traversing a vector with iterators

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> numbers =
    {
        10, 20, 30, 40, 50
    };

    std::vector<int>::iterator it;

    for (it = numbers.begin();
         it != numbers.end();
         ++it)
    {
        std::cout << *it << " ";
    }

    return 0;
}

// Output: 10 20 30 40 50

begin() returns an iterator to the first element, end() marks the end of the range, *it accesses the value pointed to by the iterator, and ++it moves to the next element.

Example 2: Using auto with iterators

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> values =
    {
        5, 15, 25, 35
    };

    for (auto it = values.begin();
         it != values.end();
         ++it)
    {
        std::cout << *it << " ";
    }

    return 0;
}

Iterator types can be long and difficult to write, such as std::vector<int>::iterator. Using auto makes the code cleaner and easier to maintain.

Example 3: Finding an element with std::find

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

int main()
{
    std::vector<int> numbers =
    {
        2, 4, 6, 8, 10
    };

    auto result = std::find(
        numbers.begin(),
        numbers.end(),
        6
    );

    if (result != numbers.end())
    {
        std::cout << "Found: "
                  << *result;
    }
    else
    {
        std::cout << "Not Found";
    }

    return 0;
}

// Output: Found: 6

std::find returns an iterator to the found element, or end() if the value isn't present. Always compare the returned iterator with end() before dereferencing it.