Infinite For Loops II in C++


We also use for loops to iterate over sequences like strings and arrays. We can run into problems when we manipulate a sequence while iterating on it.

For example, if we append elements to an array while iterating on it:

vector<string> fruits = {"banana", "orange"};

for (auto fruit : fruits) {
  fruits.push_back("kivi");
}

Every time we enter this loop, we add a kivi item to the end of the array that we are iterating through.

As a result, we never make it to the end of the array. It keeps growing forever!

This is an infinite for loop. You can imagine that as programmers, we want to make sure we never write infinite loops as they make our program run forever and completely unusable.


Assignment
Follow the Coding Tutorial and let's practice with infinite for loops!


Hint
Look at the examples above if you get stuck.


Introduction

In this lesson, we will explore the concept of infinite for loops in C++. Infinite loops are loops that never terminate, causing the program to run indefinitely. This can happen when we manipulate a sequence while iterating over it, such as appending elements to an array within a loop. Understanding and avoiding infinite loops is crucial for writing efficient and functional code.

Understanding the Basics

Before diving into the details, let's understand the fundamental concepts of loops and sequences. A loop is a control structure that allows us to execute a block of code multiple times. Sequences, such as arrays and strings, are collections of elements that we can iterate over using loops.

Here is a simple example of a for loop iterating over an array:

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

int main() {
    vector<string> fruits = {"banana", "orange"};
    for (auto fruit : fruits) {
        cout << fruit << endl;
    }
    return 0;
}

In this example, the loop iterates over each element in the fruits array and prints it to the console.

Main Concepts

When we manipulate a sequence while iterating over it, we can unintentionally create an infinite loop. This happens because the loop's termination condition is never met. Let's revisit the problematic code:

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

int main() {
    vector<string> fruits = {"banana", "orange"};
    for (auto fruit : fruits) {
        fruits.push_back("kivi");
    }
    return 0;
}

In this code, we append "kivi" to the fruits array during each iteration. As a result, the array keeps growing, and the loop never terminates.

Examples and Use Cases

Let's look at a few examples to understand how to avoid infinite loops:

Example 1: Using a Fixed Size

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

int main() {
    vector<string> fruits = {"banana", "orange"};
    size_t size = fruits.size();
    for (size_t i = 0; i < size; ++i) {
        fruits.push_back("kivi");
    }
    return 0;
}

In this example, we store the initial size of the array and use it as the loop's termination condition. This prevents the loop from running indefinitely.

Example 2: Using a Separate Collection

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

int main() {
    vector<string> fruits = {"banana", "orange"};
    vector<string> newFruits = {"kivi"};
    for (auto fruit : fruits) {
        newFruits.push_back(fruit);
    }
    fruits.insert(fruits.end(), newFruits.begin(), newFruits.end());
    return 0;
}

In this example, we use a separate collection to store the new elements and then merge it with the original array after the loop.

Common Pitfalls and Best Practices

Here are some common mistakes to avoid and best practices to follow:

Advanced Techniques

For more advanced scenarios, consider using iterators or range-based for loops. These techniques provide more control over the iteration process and can help avoid infinite loops.

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

int main() {
    vector<string> fruits = {"banana", "orange"};
    for (auto it = fruits.begin(); it != fruits.end(); ++it) {
        // Do something with *it
    }
    return 0;
}

Code Implementation

Here is a well-commented code snippet demonstrating the correct use of loops without causing infinite loops:

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

int main() {
    vector<string> fruits = {"banana", "orange"};
    
    // Store the initial size of the array
    size_t size = fruits.size();
    
    // Iterate over the array using the initial size
    for (size_t i = 0; i < size; ++i) {
        // Append "kivi" to the array
        fruits.push_back("kivi");
    }
    
    // Print the final array
    for (auto fruit : fruits) {
        cout << fruit << endl;
    }
    
    return 0;
}

Debugging and Testing

When debugging code related to loops, consider the following tips:

Example test case:

#include <cassert>
#include <vector>
using namespace std;

void testLoop() {
    vector<string> fruits = {"banana", "orange"};
    size_t size = fruits.size();
    for (size_t i = 0; i < size; ++i) {
        fruits.push_back("kivi");
    }
    assert(fruits.size() == 4); // Initial size + 2 new elements
}

int main() {
    testLoop();
    return 0;
}

Thinking and Problem-Solving Tips

When approaching problems related to loops, consider the following strategies:

Conclusion

In this lesson, we explored the concept of infinite for loops in C++ and how to avoid them. We discussed the importance of having a clear termination condition and provided examples and best practices to follow. By mastering these concepts, you can write efficient and functional code that avoids common pitfalls.

Additional Resources

For further reading and practice problems, consider the following resources: