C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
68: Iterators Explained
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;
}There are no comments for now.