STL iterators¶

Iterators are data types (or variables, depending on the context) you can use to examine and modify the elements stored in a container. You can think of the iterator as a bookmark that remembers the location of one element in a container.

Their idea is to offer a uniform interface that allows you to use the same operations on all the elements within containers, despite the exact type of the container. In other words, the operations are created to be able to handle the elements that the iterator(s) is(/are) pointing to, despite the type of the container. (To be precise, the iterator does not point to the element, it points to the space between two elements.)

A frequent problem you will encounter is how to go through all the elements within a container. In case of a vector, it is not difficult, because the elements of a vector can be indexed through with a loop. This is not the case with all containers, because elements in certain containers do not have an index number.

If one iterator allows you to mark the location of one element, two iterators can be used to show a range of elements in the container: every element between two elements. You will later see how STL algorithms utilize this characteristic.

All the container types in C++ offer the programmer a group of iterator types to be used to handle the containers. The most useful of them are

container_type<element_type>::iterator
container_type<element_type>::const_iterator

The const_iterator type allows the programmer to examine the elements in the container, not edit them. You need this kind of iterators if the container variable has been defined as a constant with the keyword const. For example:

const vector<string> MONTHS = { "January", "February", ... };

We will now implement a very simple program that examines the elements of a vector with an iterator and then prints their values to the screen:

#include <iostream>
#include <vector>

using namespace std;

int main() {
    vector<double> numbers = {1.0, 1.5, 2.0, 2.75};
    vector<double>::iterator veciter;
    veciter = numbers.begin();
    while ( veciter != numbers.end() ) {
        cout << *veciter << " ";
        ++veciter;
    }
    cout << endl;
}

The return value of the method begin shows the location of the first element in the container. The method end returns the iterator pointing to the end of the container: end does not point to any specific value in the container. It is a kind of ending symbol.

Often, the iterator end is used as a return value depicting an error or a failure of the function returning that iterator.

The following picture illustrates the iterators begin and end:

../../_images/iteraattorit_en.png

The element of a container, pointed to by the iterator, can be handled by targeting the operator * at it. For example:

cout << *veciter << endl;
*veciter = 0.0;
*veciter += 2.5;
function(*veciter);

If you want to target with methods at the object the iterator is pointing to, you should use the -> operator.

You can use the ++ operator to make the iterator point to the element that is next in line, or the -- operator to point it to the previous element. You can use the == and != operators to test whether two iterators are pointing to the same element or to different ones.

If all you want to do is go through all the elements within the container (it can be used with other containers than vector as well), you can use the for structure:

#include <iostream>
#include <vector>

using namespace std;

int main() {
    vector<double> numbers = {1.0, 1.5, 2.0, 2.75};

    for ( auto element: numbers ) {
        cout << element << " ";
    }
    cout << endl;
}

It works analogically to the Python structure:

numbers = [ 1.0 1.5 2.0 2.75 ]
for element in numbers:
    print(element, end=" ")
print()

A version of for that goes through the C++ container elements behind the scenes uses iterators, but that is not visible to the programmer.

The iterating variable of the abovementioned for structure (in our example, element) is, by default, a copy of the element next in turn in the container being worked on (copy semantics).

However, if we want to be able to change the container’s elements in the body of the for structure, we should define the iterating variable as a reference:

#include <iostream>
#include <vector>

using namespace std;

int main() {
    vector<double> numbers = {1.0, 1.5, 2.0, 2.75};

    for ( auto& element: numbers ) {
        element *= 2;
    }

    // At this point the elements of numbers vector
    // have been doubled: 2.0, 3.0, 4.0 5.5
}

Attention

An important detail you should keep in mind: if you add elements to a container or delete them from it, the values of the iterators that were set to point to its original elements will not be valid anymore.

The word auto used in the examples is a reserved word in C++. You can use it as the type of the variable you are defining in cases where the compiler is able to deduce the type from the initializing value:

auto amount = 0;              // int
auto temperature = 12.1;      // double
auto iter = numbers.begin();  // vector<double>::iterator

We are, however, not using auto in all the examples where it would be possible, because it is good for you to learn the type defining mechanisms of a statically typed language yourself.