C++ Iterators
Understanding pointers to container elements for traversal
🔄 What are Iterators?
Iterators are objects that act like pointers to elements in containers. They provide a uniform way to traverse and access elements in different container types like vectors, sets, and maps.
#include <vector>
#include <iostream>
using namespace std;
int main() {
vector<int> nums = {10, 20, 30};
// Using iterator to traverse
for (auto it = nums.begin(); it != nums.end(); ++it) {
cout << *it << " "; // Output: 10 20 30
}
return 0;
}
Types of Iterators
Forward Iterator
Move forward through container
++it; // Move to next element
Bidirectional Iterator
Move forward and backward
++it; --it; // Both directions
Random Access Iterator
Jump to any position directly
it + 5; it[3]; // Jump anywhere
Reverse Iterator
Traverse container backwards
rbegin(), rend() // Reverse
🔹 Basic Iterator Usage
Iterators act as pointers to container elements, enabling traversal and access without indexing.
They come in types like forward, bidirectional, and random-access, each supporting different operations. Common
usage includes looping with begin() and end(), dereferencing to get values, and
incrementing to move. Iterators are foundational for standard algorithms, allowing functions like
find() or sort() to work uniformly across various containers.
#include <vector>
#include <iostream>
using namespace std;
int main() {
vector<string> fruits = {"apple", "banana", "cherry"};
// Method 1: Traditional iterator loop
cout << "Method 1 - Traditional iterator:" << endl;
for (vector<string>::iterator it = fruits.begin(); it != fruits.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// Method 2: Auto keyword (recommended)
cout << "Method 2 - Auto iterator:" << endl;
for (auto it = fruits.begin(); it != fruits.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// Method 3: Range-based for loop (easiest)
cout << "Method 3 - Range-based:" << endl;
for (const string& fruit : fruits) {
cout << fruit << " ";
}
return 0;
}
Output:
apple banana cherry
Method 2 - Auto iterator:
apple banana cherry
Method 3 - Range-based:
apple banana cherry
🔹 Iterator Operations
Key iterator operations include dereferencing, incrementing, decrementing, and comparison.
Random-access iterators (for vectors and deques) also support arithmetic like addition and subtraction.
Bidirectional iterators (for lists) allow forward and backward movement. Operations ensure safe navigation: always
check against end() before dereferencing. Using these operations correctly prevents undefined behavior
and is crucial for implementing robust, efficient loops and algorithms.
#include <vector>
#include <iostream>
using namespace std;
int main() {
vector<int> numbers = {10, 20, 30, 40, 50};
// Get iterators
auto begin_it = numbers.begin(); // Points to first element
auto end_it = numbers.end(); // Points past last element
cout << "First element: " << *begin_it << endl; // 10
// Move iterator
++begin_it;
cout << "Second element: " << *begin_it << endl; // 20
// Iterator arithmetic (for random access iterators)
auto third_it = numbers.begin() + 2;
cout << "Third element: " << *third_it << endl; // 30
// Distance between iterators
auto distance = end_it - numbers.begin();
cout << "Container size: " << distance << endl; // 5
// Modify through iterator
*third_it = 99;
cout << "Modified third element: " << numbers[2] << endl; // 99
return 0;
}
Output:
Second element: 20
Third element: 30
Container size: 5
Modified third element: 99
🔹 Reverse Iterators
Reverse iterators traverse a container from the last element to the first, simplifying reverse-order
processing. Obtained via rbegin() and rend(), they internally adapt normal
iterators. Incrementing a reverse iterator moves backward through the sequence. They are useful for algorithms that
need to process data in opposite order, like searching from the end or displaying a list backwards, without manually
managing indices or decrement operations.
#include <vector>
#include <iostream>
using namespace std;
int main() {
vector<char> letters = {'A', 'B', 'C', 'D', 'E'};
cout << "Forward traversal: ";
for (auto it = letters.begin(); it != letters.end(); ++it) {
cout << *it << " ";
}
cout << endl;
cout << "Reverse traversal: ";
for (auto rit = letters.rbegin(); rit != letters.rend(); ++rit) {
cout << *rit << " ";
}
cout << endl;
// Access last element using reverse iterator
cout << "Last element: " << *letters.rbegin() << endl; // E
cout << "First element: " << *letters.begin() << endl; // A
return 0;
}
Output:
Reverse traversal: E D C B A
Last element: E
First element: A
🔹 Iterators with Different Containers
Iterators behave differently based on the container type, affecting their capabilities and
performance. Associative containers (e.g., std::set, std::map) provide
bidirectional iterators that traverse in sorted order. Sequence containers offer random-access (vector, deque) or
bidirectional (list) iterators. Unordered containers provide forward iterators. Understanding these distinctions
ensures you choose the right container and iterator for efficient data access and manipulation in your C++ programs.
#include <set>
#include <map>
#include <iostream>
using namespace std;
int main() {
// Iterator with set
set<int> numbers = {30, 10, 50, 20};
cout << "Set elements (sorted): ";
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// Iterator with map
map<string, int> ages = {{"Alice", 25}, {"Bob", 30}, {"Charlie", 22}};
cout << "Map key-value pairs:" << endl;
for (auto it = ages.begin(); it != ages.end(); ++it) {
cout << it->first << ": " << it->second << endl;
}
// Find specific element
auto found = ages.find("Bob");
if (found != ages.end()) {
cout << "Found Bob, age: " << found->second << endl;
}
return 0;
}
Output:
Map key-value pairs:
Alice: 25
Bob: 30
Charlie: 22
Found Bob, age: 30
🔹 Iterator Best Practices
To use iterators effectively, always validate them before dereferencing and beware of invalidation.
Operations like insert() or erase() can invalidate iterators for some containers
(especially vectors and deques). Use the return values of these functions to obtain valid iterators. Prefer
range-based for loops for simple traversal, and use algorithm functions like std::for_each to avoid
manual iterator management where possible, reducing errors.
Iterator Guidelines:
- Use auto: Let compiler deduce iterator type
- Prefer range-based loops: Simpler and safer
- Check validity: Always compare with end() before dereferencing
- Don't modify during iteration: Can invalidate iterators
- Use const_iterator: When you don't need to modify elements
Common Iterator Functions:
- begin(), end(): Forward iteration range
- rbegin(), rend(): Reverse iteration range
- cbegin(), cend(): Const forward iterators
- advance(it, n): Move iterator n positions
- distance(it1, it2): Count elements between iterators