Skip to Content
Course content

68: Iterators Explained

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing code for a developer who had built a really clean search utility. It worked perfectly for std::vector because he used a simple for loop with an integer index. But when the requirements changed and we had to switch the underlying storage to a std::list for better insertion performance, his entire search module broke. He spent three hours trying to figure out why list[i] didn't exist, only to realize that linked lists don't support random access. He had written logic tied to the implementation of the container, rather than the idea of the data.

That's exactly why iterators exist. They are the "glue" of the C++ Standard Template Library (STL). Think of an iterator as a generalized pointer. It doesn't care if your data is sitting in a contiguous block of memory like a vector, or scattered across the heap like a list; it provides a uniform way to move through the elements and access their values.

The Bridge Between Data and Logic

If you've used pointers, you're already 90% of the way there. An iterator behaves like a pointer: you can dereference it with * to get the value it's pointing to, and you can move it forward using ++. The magic is that the iterator knows the internal rules of the container it belongs to.

Take a look at this. Whether I'm using a vector or a list, the syntax for traversing the container remains identical:

std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
for (std::vector<std::string>::iterator it = names.begin(); it != names.end(); ++it) {
    std::cout < *it < " ";
}

I'll be honest: writing std::vector<std::string>::iterator is a mouthful. In the real world, we almost always use auto to let the compiler handle that type deduction. But understanding that it's a specific type tied to the container is crucial for when you start writing your own generic templates.

Understanding the Half-Open Range

One thing that consistently trips people up is the end() function. It's a common mistake to assume end() points to the last element in the collection. It doesn't. It points to the element one past the end.

C++ uses what we call a "half-open range," denoted as [begin, end). The square bracket means begin is included, and the parenthesis means end is excluded. This design is intentional. It makes loop conditions incredibly clean (it != names.end()) and allows you to represent an empty container simply by having begin() == end(). Just remember: never, ever dereference the iterator returned by end(). You'll be accessing memory you don't own, and your program will likely crash or, worse, behave unpredictably.

Why Iterators Beat Indices

You might be wondering why we bother with this when for(int i=0; i < size; ++i) is so intuitive. The answer is flexibility. Most of the powerful algorithms in the <algorithm> header—like std::sort, std::find, or std::accumulate—don't take containers as arguments. They take iterators.

By passing begin() and end() to an algorithm, you aren't telling the algorithm "here is a vector"; you're telling it "here is a range of things." This means the same std::find function can work on an array, a vector, a deque, or a list without needing a separate version for every single container type. It decouples the data structure from the logic that operates on it, which is the cornerstone of professional C++ architecture.




📋 Practical Task

Exercise: Filtering the Expired Medication Log

You are working on a pharmacy management system. You have a std::list of medication records. Some of these records are marked as "expired" and need to be removed. Because you are using a std::list, you cannot use indices; you must use iterators.

Your Goal: Write a function called removeExpired that takes a std::list<std::string>&. Iterate through the list and remove any string that contains the word "EXPIRED".

Crucial Hint: When you call list.erase(iterator), that iterator becomes invalid. To keep looping, you must use the return value of erase(), which returns the iterator pointing to the element immediately following the one you just removed.

#include <iostream>
#include <list>
#include <string>

void removeExpired(std::list<std::string>& meds) {
    // Your code here: Use an iterator to traverse the list
    // and erase elements containing "EXPIRED"
}

int main() {
    std::list<std::string> pharmacyInventory = {
        "Amoxicillin - OK", 
        "Ibuprofen - EXPIRED", 
        "Lisinopril - OK", 
        "Metformin - EXPIRED", 
        "Atorvastatin - OK"
    };

    removeExpired(pharmacyInventory);

    for (const auto& med : pharmacyInventory) {
        std::cout < med < "\n";
    }

    return 0;
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.