Start learning free

LESSON 114 OF 409 · FOR LOOPS - LOOPING OVER ARRAYS

Infinite For Loops II in C++

Lesson Read the full explanation

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 a vector 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 vector that we are iterating through, so we would never make it to the end of the vector.

In C++ this is even worse than an infinite loop: it is undefined behavior. The range-based for loop holds iterators into the vector, and push_back may move the elements to a bigger block of memory, which leaves those iterators pointing at memory that was freed. The program might stop after the original items, print garbage, run forever or crash.

Changing a collection while looping over it is always a bug. And when nothing stops the loop, the collection keeps growing forever: an infinite loop. 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.


STEP 1 OF 1
    Ctrl + Enter to check

    C++ · FOR LOOPS - LOOPING OVER ARRAYS

    Infinite For Loops 2 in C++, explained

    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:

    • Avoid modifying the sequence you are iterating over within the loop.
    • Use a fixed size or a separate collection to store new elements.
    • Always ensure that your loop has a clear termination condition.

    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:

    • Use print statements to track the loop's progress and identify where it might be getting stuck.
    • Check the loop's termination condition to ensure it will eventually be met.
    • Write test cases to verify the loop's behavior with different input sizes and conditions.

    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:

    • Break down the problem into smaller parts and solve each part individually.
    • Think about the loop's termination condition and ensure it will be met.
    • Practice with different coding exercises and projects to improve your understanding of loops.

    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:

    Keep going with C++About 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free