Iterators
-
An iterator is simply an object that can iterate over elements. An iterator represents a certain position in a container and allows to navigate from element to element. An iterator is a pure abstraction, so anything that behaves like an iterator is an iterator!
-
An iterator must support these fundamental operations that define its behavior:
-
Operator
*
“dereferences” the iterator and returns the element at the current position. -
Operator
++
moves the iterator to the next element. Most iterators also define the operator--
that moves the iterator to the previous element. -
Operators
==
and!=
return whether two iterators represent the same position within a container. -
Operator
=
assigns an iterator and sets the position of the element to which it refers.
As you can see, iterators’ behavior is very much like that of pointers. But iterators let you work with more complicated data structures. Their real behavior and implementation depend on the data structure over which they iterate.
-
-
All container classes provide the same basic methods (member functions) that allow you to use iterators to navigate over their elements. The most important ones are
begin
andend
.-
begin
returns an iterator that refers to the element at the beginning of the container. -
end
returns an iterator that represents the end of the container. This position is after the last element in the container.
As you can see,
begin
andend
define a half-open range that includes the first element but excludes the last. -
-
An implication of the behavior of
begin
andend
is that you can write for loops that look like this.vector<int> numbers = {1, 2, 3, 4}; for (vector<int>::iterator itr = numbers.begin(); itr != numbers.end(); ++itr) { cout << *itr << " "; } cout << endl;
The above code prints
1 2 3 4
With C++11, you can replace
vector<int>::iterator
withauto
and the type ofitr
will be automatically determined.vector<int> numbers = {1, 2, 3, 4}; for (auto itr = numbers.begin(); itr != numbers.end(); ++itr) { cout << *itr << " "; } cout << endl;
C++11 also introduced range-based for loops, so you can simplify your code even further as you iterate through elements in a container.
vector<int> numbers = {1, 2, 3, 4}; for (auto number : numbers) { cout << number << " "; } cout << endl;
-
Every STL container defines two iterator types.
-
container::iterator
provides read and write access to the container’s elements. -
container::const_iterator
provides read-only access to the container’s elements.
-
Iterator Categories
-
Iterators are divided into several categories based on their abilities. Each container class in the STL provides iterators that belong to one of the following three categories.
-
Forward iterators only have the basic abilities outlined in the beginning of this section. Iterators of the
forward_list
container class are forward iterators. The iterators of the container classesunordered_set
,unordered_multiset
,unordered_map
andunordered_multimap
are “at least” forward iterators. -
Bidirectional iterators are able to iterate both forward and backward. Iterators of the container classes
list
,set
,multiset
,map
andmultimap
are bidirectional iterators. -
Random-access iterators have all the abilities of bidirectional iterators in addition to being able to perform random access. You can add and subtract offsets, compute distance between two random-access iterators and compare them using relational operators, such as
<
and>
. Iterators ofvector
,deque
,array
andstring
are random-access iterators.
In addition to the above categories, there are two other categories.
-
Input iterators provide read-only access while iterating forward. Input stream iterators are an example thereof.
-
Output iterators provide write-only access while iterating forward. Output stream iterators are an example thereof.
-
Iterator Functions
-
The C++ library provides some auxiliary functions for dealing with iterators.
-
advance()
increments the position of an iterator passed as the argument. -
next()
andprev()
return an iterator at the position before or after the iterator passed as the argument. -
distance()
processes the difference between two iterators. -
iter_swap()
swaps the values to which two iterators refer.
Iterator Adapters
-
Remember that anything that behaves as an iterator is an iterator. Conveniently, STL provides some special iterators that allow algorithms to operate in reverse, in insert mode and with streams.
-
Here is an example that inserts the elements of one vector in the middle of another:
vector<int> numbers = {183, 203, 281, 370, 376}; vector<int> otherNumbers = {203, 280}; vector<int>::iterator it = find(numbers.begin(), numbers.end(), 281); insert_iterator<vector<int>> insertIt(numbers, it); copy(otherNumbers.begin(), otherNumbers.end(), insertIt);
Whereas normally the
std::copy
function overwrites existing elements in the container, using an insert iterator allows us to instead insert new elements without erasing the old ones. So after callingstd::copy
, thenumbers
vector contains the elements{183, 203, 203, 280, 281, 370, 376}
.
Reverse Iterators
-
Reverse iterators allow us to process elements in a container in reverse order.
-
Reverse iterators redefine increment and decrement operators so that they behave in reverse. Thus, if you use these iterators instead of ordinary iterators, algorithms process elements in reverse order.
-
All containers that provide bidirectional iterators also provide reverse iterators.
-
Here is an example of code that uses a reverse iterator to print the elements of a vector in reverse order:
vector<int> numbers = {183, 203, 280, 281, 370, 376}; reverse_iterator<vector<int>::iterator> reverseIt; for (reverseIt = numbers.rbegin(); reverseIt != numbers.rend(); ++reverseIt) { cout << *reverseIt << " "; }
The output is
376 370 281 280 203 183
.
Insert Iterators
-
Insert iterators, also called inserters, are iterator adapters that transform an assignment of a new value into an insertion of that new value. By using insert iterators, algorithms can insert values when they would normally overwrite old ones.
-
All insert iterators are considered output iterators, so they provide only the ability to assign new values.
Stream Iterators
-
A stream iterator allows you to use a stream as a source or destination of algorithms. In particular, an input stream iterator can be used to read elements from an input stream (such as
cin
or an input file stream), and an ostream iterator can be used to write values to an output stream (such ascout
or an output file stream). -
Below is some of the interface provided by istream iterators:
-
istream_iterator<T>(istream)
creates an istream iterator for an input stream (istream
) and reads the first value. -
istream_iterator<T>()
creates an end-of-stream iterator. -
*iter
returns the value that was read before. -
iter = value
writes value toostream
, followed bydelim
if set. This is equivalent toostream << value << delim
. -
++iter
reads the next value from the stream and returns its position. -
iter++
reads the next value but returns an iterator for the previous value.
-
-
Below is some of the interface provided by ostream iterators:
-
ostream_iterator<T>(ostream)
creates an ostream iterator for an output stream (ostream
). -
ostream_iterator<T>(ostream, delim)
creates an ostream iterator for an output streamostream
, withconst char* delim
as the delimiter between the values. -
iter = value
writes value toostream
, followed bydelim
if set. This is equivalent toostream << value << delim
.
-
-
Consider this snippet of code for example:
vector<int> inputNumbers; copy(istream_iterator<int>(cin), istream_iterator<int>(), back_inserter(inputNumbers));
It would read all integers from standard input and insert them in the
inputNumbers
vector. -
Here’s another example and combines input stream iterators and output stream iterators:
copy(istream_iterator<int>(cin), istream_iterator<int>(), ostream_iterator<int>(cout, "\n"));
This example reads all integers from standard input and prints them to standard output separated by a newline character.