Loops
You’ve already learned how to express sequential logic in C++ (a series of operations written as a sequence of lines of code that execute from top to bottom) and conditional logic (using if
, else if
, and else
statements). Now it’s time to explore another key type of logic in C++: repetition logic.
Repetition logic allows an operation or set of operations to be performed repeatedly as long as a specified condition is met. For example, you might write a program that prints “Hello” ten times, a program that asks the user for 100 numbers and calculates their sum, or a terminal-based chess game that continues until there is a win or a draw. These scenarios all involve executing a set of operations repeatedly until a specific condition is satisfied or no longer valid.
In C++, repetition logic is implemented using loops. A loop is a block of code that executes repeatedly as long as a specified boolean expression evaluates to true. C++ provides three types of loops: while
loops, do
-while
loops, and for
loops. While any of these loops can theoretically handle any repetition logic problem, each type is often better suited to particular scenarios.
While Loops
The simplest type of loop in C++ is the while
loop. It functions similarly to an if
statement but with one key difference: after the body of a while
loop finishes executing, the control flow returns to the loop’s condition. If the condition is still true
, the body will execute again, and this process repeats. When the condition evaluates to false
, the body is skipped, and the program continues beyond the loop.
The syntax for a while
loop is nearly identical to an if
statement, except that while
is used instead of if
. However, unlike an if
statement, a while
loop cannot include else if
or else
branches. The general syntax is:
while (<condition>) { <loop body>}
Here, <condition>
is a boolean expression that determines whether the loop should continue executing. The <loop body>
contains one or more statements to be executed repeatedly.
When a program encounters a while
loop, it evaluates the condition. If the condition is false
, the loop body is skipped, and the program proceeds with the next statement after the loop. If the condition is true
, the loop executes its body. After executing the body, the condition is evaluated again. This process repeats until the condition becomes false
.
The condition in a while
loop often depends on variables whose values change during the loop’s execution. Without such changes, a condition that evaluates to true
will remain true
, resulting in an infinite loop. An infinite loop occurs when the condition never becomes false
, causing the program to run endlessly. Infinite loops are almost always a bug since most programs are expected to terminate at some point.
Suppose you want to write a program that asks the user for numbers until they choose to stop. After each entry, the program asks: “Would you like to enter another number? Type ‘y' for yes or ‘n' for no.”
The program keeps a running sum of the numbers entered and displays the total at the end. Here’s how you could implement this using a while loop:
#include <iostream>
/* * Function: prompt_for_number * Description: Prompts the user to enter a number. * Returns (double): The number provided by the user. */double prompt_for_number() { std::cout << "Enter a number: "; double user_number; std::cin >> user_number; return user_number;}
/* * Function: prompt_for_go_again * Description: Prompts the user to decide whether to continue. * Returns (char): 'y' if the user wants to continue, 'n' otherwise. */char prompt_for_go_again() { std::cout << "Would you like to enter another number? Type 'y' for yes or 'n' for no: "; char response; std::cin >> response; return response;}
int main() { char go_again_response = 'y'; double running_sum = 0.0;
while (go_again_response == 'y') { double number = prompt_for_number(); running_sum += number;
go_again_response = prompt_for_go_again(); // Update loop condition (Line A) }
std::cout << "The sum of the numbers you entered is: " << running_sum << std::endl;}
Here’s an explanation of the program:
- Loop Condition: The condition
go_again_response == 'y'
ensures the loop continues as long as the user wants to input more numbers. This condition is a boolean expression, similar to those used inif
statements. - Updating the Condition: The variable
go_again_response
is updated within the loop body at Line A to reflect the user’s input. This is essential to allow the loop condition to eventually becomefalse
, preventing an infinite loop. Without this update, the program would keep running indefinitely, and any code after the loop would be unreachable (dead code). - Initialization: The variable
go_again_response
is initialized to'y'
before the loop starts. This ensures the loop executes at least once, even before any user input is provided. This “artificial initialization” effectively tricks the loop into running initially. In the next section, you’ll learn about a different type of loop that avoids this kind of setup.
Do-While Loops
Sometimes, you need a loop that always executes at least once. This was the case in the previous example: the user must always enter at least one number, so it’s imperative that the loop executes at least once.
There are two common strategies for ensuring a while loop runs at least once:
- Artificial Initialization: Initialize the condition variable to a value that guarantees the loop condition is
true
on the first evaluation. In the previous example,go_again_response
was initialized to'y'
before the loop started. - Duplicate the Loop Body: Replicate the loop’s logic outside the loop to ensure the body runs once before checking the condition.
Both strategies have drawbacks. The first approach often involves extra variables to “trick” the loop into executing. The second violates the DRY (Don’t Repeat Yourself) principle, as it duplicates code unnecessarily. While you could extract the duplicated code into a function, this still results in an unnecessary function call.
Thankfully, C++ provides the do-while
loop, a construct specifically designed for cases where a loop must run at least once. The key difference between while
and do-while
loops lies in their execution order:
- While Loop: The condition is evaluated first. If the condition is
true
, the loop body is executed. This repeats until the condition becomesfalse
. - Do-While Loop: The loop body is executed first. After the body executes, the condition is evaluated. If the condition is
true
, the body executes again. This continues until the condition becomesfalse
.
This distinction is reflected in their syntax. A while
loop places the condition before the loop body, while a do-while
loop places the body before the condition:
do { <loop body>} while (<condition>);
A semicolon is required after the closing parenthesis of a do-while
loop’s condition.
Let’s revisit the previous example, where the program asked the user to input numbers until they chose to stop. Using a do-while
loop, we can simplify the code:
#include <iostream>
/* * Function: prompt_for_number * Description: Prompts the user to enter a number. * Returns (double): The number entered by the user. */double prompt_for_number() { std::cout << "Enter a number: "; double user_number; std::cin >> user_number; return user_number;}
/* * Function: prompt_for_go_again * Description: Prompts the user to decide whether to continue. * Returns (char): 'y' to continue, 'n' to stop. */char prompt_for_go_again() { std::cout << "Would you like to enter another number? Type 'y' for yes or 'n' for no: "; char response; std::cin >> response; return response;}
int main() { char go_again_response; double running_sum = 0.0;
do { double number = prompt_for_number(); running_sum += number;
go_again_response = prompt_for_go_again(); // Update loop condition } while (go_again_response == 'y');
std::cout << "The sum of the numbers you entered is: " << running_sum << std::endl;}
The do-while
loop eliminates the need to initialize go_again_response
to 'y'
before the loop. Since the loop body executes before the condition is evaluated, the user’s response is always captured before the condition is checked. Note that go_again_response
is still declared outside the loop, otherwise it won’t be accessible from the condition.
The structure is more intuitive because the logic aligns with the problem’s requirements—a body that executes before the condition is checked.
We can streamline the code by taking advantage of the fact that assignment operations are expressions. Instead of updating go_again_response
inside the loop body, we can include the assignment directly in the condition:
do { double number = prompt_for_number(); running_sum += number;} while ((go_again_response = prompt_for_go_again()) == 'y');
This approach removes the need to update go_again_response
explicitly in the loop body. However, it wouldn’t work in a while
loop because the condition is evaluated before the body executes, disrupting the order of prompts.
If the condition variable (go_again_response
) is only used within the condition and not elsewhere in the program, it can be removed entirely:
do { double number = prompt_for_number(); running_sum += number;} while (prompt_for_go_again() == 'y');
This works because the function prompt_for_go_again()
directly provides the value needed for the condition. However, if the variable is required elsewhere, this simplification is not viable.
do-while
loops are ideal when you need a loop that always executes at least once. For scenarios where the loop may not execute at all, depending on the initial condition, a while
loop is more appropriate.
For Loops
While while
and do-while
loops are excellent for repetition logic of the form “repeat these operations as long as this condition is satisfied,” they are not ideal for count-based repetition. Count-based repetition follows the pattern “repeat these operations exactly N times,” where N is a value that can be easily determined before the loop starts.
A common solution to count-based repetition involves creating a counter variable (typically of type int) that increments during each iteration of the loop body. The loop condition is built around this counter, ensuring that once the counter reaches a certain value, the loop terminates.
Suppose you want to write a program that prints “Hello, World!” exactly 100 times. This is a classic count-based repetition problem. Using a while loop, the solution might look like this:
#include <iostream>
int main() { int counter = 0; while (counter < 100) { std::cout << "Hello, World!" << std::endl; counter++; }}
Here’s what happens during execution:
- On the first iteration, counter is
0
, and the condition0 < 100
istrue
. The program prints“Hello, World!”
and increments counter to1
. - On the second iteration, counter is
1
, and1 < 100
istrue
. The program prints“Hello, World!”
again and increments counter to2
. - This process repeats until counter reaches
100
, at which point the conditioncounter < 100
evaluates tofalse
, and the loop terminates.
In this example, the loop executes exactly 100 times, as intended. Indeed, the total number of values within 0
, 1
, 2
, …, 99
is 100.
While the while
loop works for count-based repetition, it has two key drawbacks:
- Risk of Errors: In a large loop body, the counter variable’s increment operation (
counter++
) can be overlooked, duplicated, or omitted entirely. This could result in:- A loop that executes too few times.
- An infinite loop if the counter is never incremented.
- Readability: If the counter increment is buried deep in a complex loop body, it can be difficult to quickly determine how many times the loop will execute.
Count-based loops are extremely common, so these issues have influenced the design of virtually all high-level imperative programming languages.
C++ provides a for
loop, a specialized construct for count-based repetition. A for
loop is designed to consolidate all the logic related to the counter variable into a single location—at the beginning of the loop. This design reduces the risk of errors and improves readability, regardless of the complexity of the loop body.
A for
loop in C++ is similar to a while
loop but uses a specialized syntax optimized for count-based repetition. Its structure is as follows:
for (<initialization statement>; <condition>; <post-operation>) { <loop body>}
This syntax can seem confusing at first, so here’s a detailed breakdown:
- The
<initialization statement>
is executed only once, right before the loop starts. It typically initializes a counter variable, which can also be declared within this statement. For example, rewriting the count-basedwhile
loop from earlier as afor
loop might use the initialization statementint counter = 0
. - The
<condition>
determines whether the loop will execute. If the condition evaluates totrue
, the loop body runs; iffalse
, the loop terminates. The condition is evaluated before the loop body runs for the first time, making afor
loop more like awhile
loop than ado-while
loop. If the condition is initiallyfalse
, the loop will not execute at all. In our earlier example, the condition would becounter < 100
. - The
<post-operation>
executes immediately after each iteration of the loop body and before re-evaluating the condition. This is often used to increment/decrement the counter variable. For example, the post-operation might becounter++
. Importantly, when using a for loop, ensure the counter increment occurs in the post-operation and not in the loop body to avoid duplication or errors.
Here’s how the count-based while
loop example can be rewritten as a for
loop:
#include <iostream>
int main() { for (int counter = 0; counter < 100; counter++) { std::cout << "Hello, World!" << std::endl; }}
One major difference is variable scope. In the for
loop example above, the counter variable counter
is declared within the initialization statement, meaning its scope is limited to the loop itself. Once the loop finishes, counter is no longer accessible.
For example, the following code will fail to compile because counter is out of scope after the loop:
#include <iostream>
int main() { for (int counter = 0; counter < 100; counter++) { std::cout << "Hello, World!" << std::endl; } std::cout << "The loop terminated when the counter reached: " << counter << std::endl;}
To resolve this, declare the counter variable outside the loop if you need to use it afterward:
#include <iostream>
int main() { int counter; for (counter = 0; counter < 100; counter++) { std::cout << "Hello, World!" << std::endl; } std::cout << "The loop terminated when the counter reached: " << counter << std::endl;}
When executed, this program prints “Hello, World!”
100 times, followed by: The loop terminated when the counter reached: 100
.
The counter variable can be used within the loop body to modify the operations performed in each iteration. For example, suppose you want to print all integers from 0 to 99, separated by commas. Here’s how you could do it:
#include <iostream>
int main() { for (int counter = 0; counter < 100; counter++) { if (counter > 0) { std::cout << ", "; } std::cout << counter; }}
This program prints the numbers 0 to 99 in a single line, separated by commas. The if
statement ensures that commas are only printed before the second number onward, avoiding a leading comma.
The <initialization statement>
and <post-operation>
in a for loop can be any valid C++ statements, not just those involving a counter variable. However, the most common pattern for simple count-based loops includes:
- Initialization: Setting the counter to 0 or N.
- Condition: Comparing the counter to the target value using a less-than operator
<
or greater-then operator>
. - Post-Operation: Incrementing or decrementing the counter by 1 (
counter++
orcounter--
).
This pattern is widely used because of its simplicity, but variations exist depending on the problem. These will be explored in future discussions.
Counter Name
Since the primary purpose of a for
loop is to handle count-based problems, almost every for
loop declares or initializes a counter variable in its initialization statement. Because this use case is so common, it has become standard practice to name the counter variable i
.
In previous examples, we used the name counter
to explicitly indicate its role. However, over time, i
has become the most widely recognized name for counter variables. It’s so universally understood among programmers that its meaning is immediately clear. While it’s generally discouraged to use single-letter or heavily abbreviated variable names, this is a well-established exception.
The use of i
as a counter variable originates from mathematical notation, where i
is often used to index elements of a vector or rows of a matrix. In programming, for
loops are frequently used for similar tasks, such as iterating over elements in strings, arrays, or other data structures. We’ll explore these applications in future lectures.
Similarly, in nested loops (see below), it is common to use i
, j
, and k
as counter variables for the outer, middle, and inner loops, respectively.
Header Tricks
As we’ve mentioned, the initialization statement and post-operation in a for
loop header can be any valid C++ statement. In most examples, we follow a common pattern for simple count-based loops:
- Initialize the counter variable to 0.
- Use a less-than operator to compare the counter against the target number of iterations.
- Increment the counter by 1 in the post-operation.
for (int i = 0; i < n; i++) { ...}
In this code, n
specifies how many times the loop should execute.
While the above pattern is common, it can be unintuitive in some scenarios. Consider a situation where you need to print all integers from 7 to 54, inclusive. This involves printing 48 numbers, but figuring that out isn’t immediately obvious—you might need to calculate it manually. Once you determine that n = 48
, you could set up the loop header as follows:
for (int i = 0; i < 48; i++) { if (i > 0) { std::cout << ", "; } std::cout << (i + 7);}
While the above solution works, it’s not entirely intuitive. The loop’s purpose is to print numbers from 7 to 54, yet the counter runs from 0 to 47. In such cases, breaking the common pattern often makes the code cleaner. By directly iterating the counter over the desired range, the logic becomes clearer:
for (int i = 7; i <= 54; i++) { if (i > 7) { std::cout << ", "; } std::cout << i;}
This approach removes the need for offsetting and directly iterates over the values to be printed, making the code more straightforward and easier to understand.
The flexibility of for
loops allows for creative variations. For instance, suppose you want to print numbers backward from 54 to 7, skipping every other number (e.g., 54, 52, 50, …, 8). This requires adjusting the loop header and the comma placement logic:
for (int i = 54; i >= 7; i -= 2) { if (i < 54) { std::cout << ", "; } std::cout << i;}
Here, the counter starts at 54, decrements by 2 after each iteration, and stops when it’s less than 7. The if
statement ensures commas are placed appropriately.
Break and Continue
In C++, two special keywords, break
and continue
, are designed for use within loops. While they can technically be used on their own, they are most effective when placed inside an if
statement nested within the loop. Here’s what each does:
break
: Immediately exits the entire loop, skipping any remaining iterations and continuing execution with the code that follows the loop.continue
: Immediately ends the current iteration of the loop, skipping the remaining code in the loop body and jumping to the next iteration.
If the loop condition becomes false
during the current iteration, continue
might behave similarly to break
, as it also ends the loop. However, if the loop condition or post-operation has side effects, break
and continue
will behave differently.
Both statements can simplify handling special cases within loops but should be used sparingly. The primary purpose of choosing among the three types of loops (for
, while
, do-while
) is to clearly define the loop’s logic in the header. Overusing break
and continue
can obscure the intended behavior of a loop, making the code harder to read and maintain.
This for
loop allows the user to make up to 10 guesses for a “magic number” (777). If the user guesses correctly, the break statement exits the loop early:
for (int i = 10; i > 0; i--) { std::cout << "You have " << i << " guesses left." << std::endl; std::cout << "Guess the magic number: "; int guess; std::cin >> guess;
if (guess == 777) { std::cout << "You got it!" << std::endl; // Exit the loop immediately since the user guessed correctly. break; }}
If break
was used outside the if
statement, it would execute unconditionally, causing the loop to terminate after a single iteration. This would defeat the purpose of the loop, making subsequent code within the loop unreachable (dead code).
In the next example, this while
loop asks the user for 10 positive integers and calculates their sum. If the user enters a non-positive integer, the continue statement skips the current iteration, prompting the user again without adding the invalid input to the sum or incrementing the counter.
int num_positive_values_entered = 0;int sum_of_positive_values = 0;
while (num_positive_values_entered < 10) { std::cout << "Enter a positive integer: "; int value; std::cin >> value;
if (value <= 0) { std::cout << "I said to enter a POSITIVE integer!" << std::endl; // Skip to the next iteration to prompt the user again. continue; }
// Add the valid input to the sum and increment the counter. sum_of_positive_values += value; num_positive_values_entered++;}
Nested Loops
Loops in C++ can be nested, meaning one loop can exist inside another. The outer loop is called the nesting loop, and the inner loop is referred to as the nested loop. Nested loops can add complexity but are useful for solving multidimensional problems or handling repeated tasks within other repeated tasks.
By convention:
- The outermost loop often uses
i
as the counter variable name. - The next level of nesting typically uses
j
. - If a third nested loop exists (which is rare and often discouraged),
k
is used.
Although these conventions are widely recognized, deeply nested loops can become hard to read. In such cases, consider breaking some loops into separate functions and using meaningful parameter names to improve clarity.
If a break
or continue
statement is used inside a nested loop, it only applies to the innermost loop containing the statement. For example:
- If a
break
is executed in afor
loop nested inside awhile
loop, only thefor
loop will terminate. - The outer
while
loop will continue running, potentially restarting thefor
loop in the next iteration.
How many times do you think the following program will print “Hello”
?
// nested_loop_1.cppfor (int i = 0; i < 10; i++) { for (int j = 0; j < 5; j++) { std::cout << "Hello" << std::endl; }}
- The outer loop (
i
) runs 10 iterations. - For each iteration of the outer loop, the inner loop (
j
) runs 5 iterations. - During each iteration of the inner loop,
“Hello”
is printed.
Hence, “Hello”
is printed .
Here’s a slightly harder example. How many times will the following program print “Hello”
, and how many times will it print “Goodbye”
?
// nested_loop_2.cppfor (int i = 0; i < 10; i++) { for (int j = 0; j < 5; j++) { std::cout << "Hello" << std::endl; } std::cout << "Goodbye" << std::endl;}
- The outer loop (
i
) runs 10 iterations. - The inner loop (
j
) runs 5 iterations for each iteration of the outer loop. - After the inner loop finishes, “Goodbye” is printed once.
Hence, “Hello”
is printed times and “Goodbye”
is printed once per outer loop iteration, for a total of 10 times.
Here’s an even harder example, still using nested for loops:
// nested_loop_variable.cppfor (int i = 1; i <= 9; i++) { for (int j = 0; j < i; j++) { std::cout << "Hello" << std::endl; }}
- The outer loop (
i
) runs 9 iterations, withi
taking values from 1 to 9. - The inner loop (
j
) runsi
iterations for each iteration of the outer loop. For example:- When
i = 1
, the inner loop runs 1 iteration. - When
i = 2
, the inner loop runs 2 iterations. - …
- When
i = 9
, the inner loop runs 9 iterations.
- When
Hence, the number of “Hello”
s printed is the sum of the integers from 1 to 9:
Let’s do one more nested for loop example. What does the following program do? Note that \t
is a “tab” character, so printing it will print a tab indentation to the terminal.
// nested_loop_3.cppfor (int i = 1; i <= 10; i++) { for (int j = 1; j <= 10; j++) { std::cout << (i * j) << "\t"; } std::cout << "\n";}
- Outer loop (
i
): Runs 10 times, iterating from 1 to 10. - Inner loop (
j
): Runs 10 times for each iteration of the outer loop, also iterating from 1 to 10. - During each iteration of the inner loop, the product of
i
andj
is printed, followed by a tab\t
for alignment. - At the end of each outer loop iteration, a newline
\n
moves to the next row.
This nested loop prints a multiplication table:
1 2 3 4 5 6 7 8 9 102 4 6 8 10 12 14 16 18 203 6 9 12 15 18 21 24 27 304 8 12 16 20 24 28 32 36 405 10 15 20 25 30 35 40 45 506 12 18 24 30 36 42 48 54 607 14 21 28 35 42 49 56 63 708 16 24 32 40 48 56 64 72 809 18 27 36 45 54 63 72 81 9010 20 30 40 50 60 70 80 90 100
Finally, let’s do a crude, academic example that pulls a lot of concepts together, including nested loops, break
and continue
statements, and scope rules. How many times will the following code print “Hello”?
// nested_loop_4.cppint i = 0;while (true) { if (i >= 10) { break; } for (int i = 0; i < 5; i++) { if (i < 2) { continue; } if (i >= 4) { break; } std::cout << "Hello" << std::endl; } i++;}
This one is tricky. Let’s break it down:
- The
while
loop should run indefinitely because the conditiontrue
is always true. However:- Runs until
i >= 10
because of thebreak
statement. - Starts with
i = 0
and incrementsi
at the end of each iteration. - The while loop executes a total of 10 full iterations (the 11th iteration terminates early due to break).
- Runs until
- The
for
loop initializes a separate counter variable (int i
), which shadows thewhile
loop’si
.- Runs up to
5
iterations for eachwhile
loop iteration. - Behavior within the for loop:
continue
skips the first two iterations (i < 2
).break
exits the loop on the 5th iteration (i >= 4
).- Only the 3rd and 4th iterations of the for loop print
"Hello"
.
- Runs up to
The int i
in the for
loop is a separate variable from the i
in the while loop. The while loop’s i
is unaffected by the for
loop’s operations. Once the for
loop ends, the while loop’s i
is incremented.
To summarize, the while
loop executes 10
full iterations, and during each iteration, the for
loop prints “Hello” 2
times (on the 3rd and 4th iterations). Hence, "Hello"
is printed times.