C++ Iterators and Range Based For Loops

Learn how to iterate over containers in C++ using code that can work regardless of the specific type of container
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

124.jpg
Ryan McCombe
Ryan McCombe
Posted

When we have a collection of objects, one of the most common things we need to do in our code is access all the objects in that collection.

Our earlier lessons on arrays, vectors and linked lists showed ways we can do that. Unfortunately, the methods needed to be tailored to the type of container we were working with. With arrays, we could use the [] operator to access elements directly.

With linked lists, we didn’t have that ability, because they work differently. Yet more container types in the future will work differently again.

It would be nice if we could have a standardized way to iterate over our collections, regardless of what type of collection it is. This is where iterators come in.

Building Our Own Iterator

Iterators are objects that are designed to move through a container, providing access to all the items within that container as it passes through. Iterators can have many capabilities, but the most fundamental are:

  • The * operator to grant access to an element in the collection
  • The ++ operator, to move on to the next element

Pointers implement the * operator for access to the thing they are pointing at.

Additionally, when we’re using a data structure that keeps its items in contiguous memory, the ++ operator will update the pointer to the memory location of the next item in the structure.

As a result, simple pointers can be used as array iterators. We’ll call our iterator It for brevity:

#include <array>
#include <iostream>
using namespace std;

int main()
{
  array MyArray { 1, 2, 3 };
  int* It { &MyArray[0] };

  cout << *It;
  ++It;
  cout << *It;
  ++It;
  cout << *It;
}
123

We can enhance this to use a loop instead, which will allow it to work for any length of the array. We’ll start our iterator at the beginning of the array, and continue incrementing it until we reach the end:

#include <array>
#include <iostream>
using namespace std;

int main()
{
  array MyArray { 1, 2, 3 };
  int* Begin { &MyArray[0] };
  int* End { Begin + std::size(MyArray) };

  for (auto It { Begin }; It != End; ++It) {
    cout << *It;
  }
}
123

To simplify things, the standard library array class comes with begin and end methods, so we can use those rather than creating our own.

Standard Library Iterators

Let's update our previous code to use the standard library’s array methods instead:

#include <array>
#include <iostream>
using namespace std;

int main()
{
  array Nums { 1, 2, 3 };

  for (auto It { Nums.begin() }; It != Nums.end(); ++It) {
    cout << *It;
  }
}
123

Now, we can begin to benefit from standardisation. Even though a linked list doesn’t store its objects in the same way as an array, our code will still work if we switch out our array for a linked list. This is because, just like std::array, std::forward_list has a begin and end method, and those methods return an object that behaves exactly the same way as the one returned from the std::array version of those functions.

So, we can change our data structure, without changing our iteration code:

#include <forward_list>
#include <iostream>
using namespace std;

int main()
{
  forward_list Nums { 1, 2, 3 };

  for (auto It { Nums.begin() }; It != Nums.end(); ++It) {
    cout << *It;
  }
}
123

Almost all standard library containers we’ll see work this way, and we can set up our own custom types to work this way too.

This standardisation has many benefits, and we’ll see those in more detail in the algorithms chapter of this course. But, there is an immediate benefit we can start using now.

Range-Based For Loops

With containers that support begin and end, there is a more concise way to iterate over them. A range-based for loop is the preferred way to iterate over containers:

#include <array>
#include <iostream>
using namespace std;

int main()
{
  array Nums { 1, 2, 3 };

  for (auto Num : Nums) {
    cout << Num;
  }
}
123

By convention, we use auto when using a range-based for loop. Range-based for loops will copy the item into the body of the loop. As a result, this will still log out 123, as our first loop is modifying copies:

#include <array>
#include <iostream>
using namespace std;

int main()
{
  array Nums { 1, 2, 3 };

  for (auto Num : Nums) {
    ++Num;
  }
  for (auto Num : Nums) {
    cout << Num;
  }
}
123

Generally, we want to pass by reference instead, so we’re not making unnecessary copies of our objects. We can do that using the & token in the usual way:

#include <array>
#include <iostream>
using namespace std;

int main()
{
  array Nums { 1, 2, 3 };

  for (auto& Num : Nums) {
    ++Num;
  }
  for (auto Num : Nums) {
    cout << Num;
  }
}
234

When we don’t need to modify the items in the collection, we should mark our reference as const:

#include <array>
#include <iostream>
using namespace std;

int main()
{
  array Nums { 1, 2, 3 };
  
  for (const auto& Num : Nums) {
    cout << Num;
  }
}
123

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Posted
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Iterators and Algorithms
7a.jpg
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access!

This course includes:

  • 106 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Range Based For Loops with Custom Types

Learn how to add iterators to our custom classes, so they can be used with range-based for loops.
vb3.jpg
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved