The Standard Template Library (STL) in C++ provides powerful tools for efficient programming. One of its key components is Iterators. Iterators act as a bridge between containers (like vectors, lists, and maps) and algorithms in the STL. They allow traversal, manipulation, and access of container elements without worrying about the underlying data structure.
This chapter covers STL Iterators in detail, from basic concepts to advanced usage, with in-depth explanations, real-world examples, and outputs. By the end of this chapter, you will have a comprehensive understanding of iterators and how to use them effectively.
An iterator is an object that points to an element in a container. It behaves like a pointer and allows you to traverse through the container’s elements sequentially. Iterators abstract away the complexity of accessing elements from different types of containers.
vector
, list
, map
, etc.).find
, sort
, copy
, etc.).Iterators in C++ can be divided into several types based on their behavior and functionality.
Iterator Type | Description | Examples |
---|---|---|
Input Iterator | Read-only access; one-way traversal. | Reading data from input streams. |
Output Iterator | Write-only access; one-way traversal. | Writing data to output streams. |
Forward Iterator | Read/write access; can move forward. | forward_list or basic containers. |
Bidirectional Iterator | Read/write access; can move forward and backward. | list , map , set . |
Random Access Iterator | Read/write access; can move freely (forward/backward) and jump to positions. | vector , array , deque . |
Let’s take a closer look at each iterator type with real examples.
An Input Iterator allows you to read values sequentially from a container.
#include
#include
#include // Required for iterator
int main() {
std::vector numbers = {1, 2, 3, 4, 5};
// Create an input iterator
std::vector::iterator it;
std::cout << "Using Input Iterator to print values: ";
for (it = numbers.begin(); it != numbers.end(); ++it) {
std::cout << *it << " "; // Dereference iterator to get value
}
return 0;
}
// Output
Using Input Iterator to print values: 1 2 3 4 5
An Output Iterator is used for writing values sequentially.
#include
#include
#include // For ostream_iterator
#include // For copy()
int main() {
std::vector numbers = {10, 20, 30, 40, 50};
std::cout << "Using Output Iterator: ";
std::copy(numbers.begin(), numbers.end(), std::ostream_iterator(std::cout, " "));
return 0;
}
// Output
Using Output Iterator: 10 20 30 40 50
A Forward Iterator allows forward traversal with both reading and writing.
#include
#include
int main() {
std::forward_list fl = {1, 2, 3, 4, 5};
std::cout << "Using Forward Iterator: ";
for (auto it = fl.begin(); it != fl.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
// Output
Using Forward Iterator: 1 2 3 4 5
A Bidirectional Iterator allows both forward and backward traversal.
#include
#include
int main() {
std::list numbers = {1, 2, 3, 4, 5};
std::cout << "Using Bidirectional Iterator: ";
for (auto it = numbers.rbegin(); it != numbers.rend(); ++it) {
std::cout << *it << " ";
}
return 0;
}
// Output
Using Bidirectional Iterator: 5 4 3 2 1
A Random Access Iterator supports free movement, forward, backward, and indexed access.
#include
#include
int main() {
std::vector vec = {10, 20, 30, 40, 50};
std::cout << "Using Random Access Iterator: ";
for (size_t i = 0; i < vec.size(); ++i) {
std::cout << vec[i] << " "; // Random access using indexing
}
return 0;
}
// Output
Using Random Access Iterator: 10 20 30 40 50
C++ provides several built-in iterator functions:
Function | Description |
---|---|
begin() | Returns an iterator pointing to the start. |
end() | Returns an iterator pointing after the last element. |
rbegin() | Returns a reverse iterator to the last element. |
rend() | Returns a reverse iterator before the first element. |
In this section, we will explore advanced concepts related to iterators in C++. These topics help developers use iterators more effectively for complex scenarios, ensuring flexibility, efficiency, and maintainability in their programs.
A const iterator is an iterator that provides read-only access to elements of a container. It ensures that you cannot modify the values of the container elements while iterating over them.
Using const iterators is essential when you want to enforce immutability and make it clear that the container’s elements must not be changed.
To declare a const iterator:
cbegin()
and cend()
to get const iterators.const_iterator
explicitly.
#include
#include
int main() {
const std::vector numbers = {10, 20, 30, 40, 50};
// Using const_iterator to iterate over the vector
std::cout << "Using const iterators: ";
for (std::vector::const_iterator it = numbers.cbegin(); it != numbers.cend(); ++it) {
std::cout << *it << " "; // Read-only access
}
// Uncommenting the below line will cause an error
// *it = 100; // Error: cannot modify elements through const_iterator
return 0;
}
// Output
Using const iterators: 10 20 30 40 50
cbegin()
and cend()
return const iterators.*it = 100
) will result in a compile-time error.A reverse iterator allows you to traverse a container backward, starting from the last element and moving towards the first.
Reverse iterators are particularly useful when:
C++ provides rbegin()
and rend()
to create reverse iterators:
rbegin()
: Points to the last element of the container.rend()
: Points before the first element of the container.
#include
#include
int main() {
std::vector numbers = {1, 2, 3, 4, 5};
// Using reverse iterators to print elements in reverse
std::cout << "Using reverse iterators: ";
for (std::vector::reverse_iterator it = numbers.rbegin(); it != numbers.rend(); ++it) {
std::cout << *it << " ";
}
return 0;
}
// Output
Using reverse iterators: 5 4 3 2 1
rbegin()
points to the last element of the container (5
).rend()
marks the position before the first element.A move iterator enables “move semantics” when iterating over a container. Instead of copying elements during operations, move iterators transfer ownership of the resources.
This can significantly improve performance for containers holding large objects or objects that are expensive to copy.
Move iterators can be created using std::make_move_iterator
.
#include
#include
#include // For copy
int main() {
std::vector source = {"one", "two", "three"};
std::vector destination;
// Move elements from source to destination using move iterators
std::move(source.begin(), source.end(), std::back_inserter(destination));
// Print the destination vector
std::cout << "Destination vector: ";
for (const auto& str : destination) {
std::cout << str << " ";
}
// Print the source vector after move
std::cout << "\nSource vector after move: ";
for (const auto& str : source) {
std::cout << str << " "; // Elements in source are now empty
}
return 0;
}
// Output
Destination vector: one two three
Source vector after move:
std::move
transfers ownership of the strings from the source
vector to the destination
vector.source
vector’s strings become empty because their resources have been moved.Stream iterators connect STL containers with input/output streams. They allow reading from or writing to streams like cin
, cout
, or files.
#include
#include
#include
int main() {
std::vector numbers;
std::cout << "Enter 5 integers: ";
std::copy_n(std::istream_iterator(std::cin), 5, std::back_inserter(numbers));
std::cout << "You entered: ";
for (const auto& num : numbers) {
std::cout << num << " ";
}
return 0;
}
// Input
10 20 30 40 50
// Output
You entered: 10 20 30 40 50
std::move
transfers ownership of the strings from the source
vector to the destination
vector.source
vector’s strings become empty because their resources have been moved.
#include
#include
#include
#include
int main() {
std::vector numbers = {1, 2, 3, 4, 5};
std::cout << "Output Stream Iterator: ";
std::copy(numbers.begin(), numbers.end(), std::ostream_iterator(std::cout, " "));
return 0;
}
// Output
Output Stream Iterator: 1 2 3 4 5
Custom iterators allow you to define your own iterator for custom data structures. While the C++ Standard Template Library (STL) provides built-in iterators for its containers (like vector
, list
, etc.), custom iterators become essential when you need to iterate through non-standard data structures or implement a special type of traversal logic.
Custom iterators are needed when:
For example, if you create a custom linked list, you need an iterator to traverse through it, just like you use iterators with std::vector
or std::list
.
To create a custom iterator, you must implement the following components:
*
): Returns the current element.++
): Moves the iterator to the next element.==
, !=
): Checks if two iterators are equal or not.Additionally, iterators are often implemented as classes that encapsulate these functionalities.
Let’s go step-by-step to understand the creation of a custom iterator.
Here, we will create a simple dynamic array class that requires a custom iterator.
#include
#include // For std::size_t
class MyArray {
private:
int* data; // Pointer to dynamically allocated array
std::size_t size;
public:
MyArray(std::size_t s) : size(s) {
data = new int[size]; // Allocate memory
for (std::size_t i = 0; i < size; ++i) {
data[i] = i + 1; // Initialize array elements
}
}
~MyArray() {
delete[] data; // Free allocated memory
}
int& operator[](std::size_t index) {
return data[index]; // Allow access to elements
}
std::size_t getSize() const { return size; }
// Forward declaration of custom iterator
class Iterator;
// Function to return an iterator pointing to the start of the array
Iterator begin();
// Function to return an iterator pointing to the end of the array
Iterator end();
};
Now, let’s define the Iterator
class within MyArray
. It will contain all the required functionalities like dereferencing, incrementing, and comparison.
class MyArray::Iterator {
private:
int* ptr; // Pointer to the current element
public:
// Constructor: Takes a pointer to initialize
Iterator(int* p) : ptr(p) {}
// Dereference operator: Returns the current element
int& operator*() {
return *ptr;
}
// Increment operator: Move to the next element
Iterator& operator++() {
++ptr;
return *this;
}
// Equality operator: Check if two iterators are equal
bool operator==(const Iterator& other) const {
return ptr == other.ptr;
}
// Inequality operator: Check if two iterators are not equal
bool operator!=(const Iterator& other) const {
return !(*this == other);
}
};
begin()
and end()
FunctionsNow we define begin()
and end()
in the MyArray
class. These functions return the custom iterator pointing to the start and end of the array.
MyArray::Iterator MyArray::begin() {
return Iterator(data); // Start of the array
}
MyArray::Iterator MyArray::end() {
return Iterator(data + size); // Past the last element
}
Now that we have defined the custom iterator and its functionalities, we can use it in a range-based for
loop or with STL algorithms.
int main() {
MyArray arr(5); // Create a MyArray object of size 5
std::cout << "Using Custom Iterator to traverse MyArray:\n";
for (MyArray::Iterator it = arr.begin(); it != arr.end(); ++it) {
std::cout << *it << " "; // Use the custom iterator
}
std::cout << "\n";
return 0;
}
Using Custom Iterator to traverse MyArray:
1 2 3 4 5
MyArray
is a simple dynamic array class that holds integers.operator*()
provides access to the current element.operator++()
moves to the next element.operator==()
and operator!=()
compare iterators.begin()
and end()
: These functions return iterators pointing to the start and end of the array.for
loop uses the custom iterator to traverse the MyArray
elements.Advanced iterators like const iterators, reverse iterators, move iterators, and stream iterators extend the flexibility and power of the STL. They allow developers to perform efficient, clean, and high-performance operations on containers, streams, and custom data structures. Happy coding !❤️