Chapter 3 - Lecture Notes
Chapter 3 - Lecture Notes
3
CONTROL STATEMENTS
INTRODUCTION
When a program is run, the CPU begins execution at the top of main(), executes some number of statements,
and then terminates at the end of main(). The sequence of statements that the CPU executes is called the
program’s execution path (or path, for short). Most of the programs you have seen so far have
been straight-line programs. Straight-line programs have sequential flow -- that is, they take the same
path (execute the same statements) every time they are run (even if the user input changes).
However, often this is not what we desire. For example, if we ask the user to make a selection, and the user
enters an invalid choice, ideally we’d like to ask the user to make another choice. This is not possible in a
straight-line program. Alternatively, there are cases where we need to do something a number of times, but
we don’t know how many times at compile time. For example, if we wanted to print all of the integers from
0 to some number the user entered, we couldn’t do that until we know what number the user entered.
Fortunately, C++ provides control flow statements (also called flow control statements), which allow the
programmer to change the CPU’s path through the program.
Halt
The most basic control flow statement is the halt, which tells the program to quit running immediately. In
C++, a halt can be accomplished through use of the exit() function that is defined in the cstdlib header. The
exit function takes an integer parameter that is returned to the operating system as an exit code, much like
the return value of main().
Note that exit() works no matter what function it’s called from (even a function other than main). Also note
that exit() terminates the program immediately with minimal cleanup. Therefore, before calling exit(), you
should consider whether any manual cleanup is required (e.g. saving the user’s game to disk).
Most often, exit() is used to immediately terminate the program when some catastrophic, unrecoverable
error occurs.
1
COMPUTING II TEMB 1202 CHAPTER 3
Jumps
The next most basic flow control statement is the jump. A jump unconditionally causes the CPU to jump
to another statement. The goto, break, and continue keywords all cause different types of jumps.
Function calls also cause jump-like behavior. When a function call is executed, the CPU jumps to the top
of the function being called. When the called function ends, execution returns to the statement after the
function call.
Conditional branches
A conditional branch is a statement that causes the program to change the path of execution based on the
value of an expression. The most basic conditional branch is an if statement, which you have seen in
previous examples. Consider the following program:
int main()
{
// do A
if (expression)
// do B
else
// do C
// do D
}
This program has two possible paths. If expression evaluates to true, the program will execute A, B, and D.
If expression evaluates to false, the program will execute A, C, and D. As you can see, this program is no
longer a straight-line program -- its path of execution depends on the value of expression.
The switch keyword also provides a mechanism for doing conditional branching.
Loops
A loop causes the program to repeatedly execute a series of statements until a given condition is false. For
example:
int main()
{
// do A
// loop on B, 0 or more times
// do C
}
This program might execute as ABC, ABBC, ABBBC, ABBBBC, or even AC. Again, you can see that this
program is no longer a straight-line program -- its path of execution depends on how many times (if any)
the looped portion executes.
C++ provides 3 types of loops: while, do while, and for loops. C++11 added support for a new kind of loop
called a for each loop.
2
COMPUTING II TEMB 1202 CHAPTER 3
Exceptions
Finally, exceptions offer a mechanism for handling errors that occur in a function. If an error occurs in a
function that the function cannot handle, the function can trigger an exception. This causes the CPU to jump
to the nearest block of code that handles exceptions of that type.
Conclusion
By using program flow statements, you can affect the path the CPU takes through the program and control
under what conditions it will terminate. Prior to this point, the number of things you could have a program
do was fairly limited. Being able to control the flow of your program makes any number of interesting
things possible, such as displaying a menu repeatedly until the user makes a valid choice, printing every
number between x and y, or determining the factors of a number.
Once you understand program flow, the things you can do with a C++ program really open up.
IF STATEMENTS
The most basic kind of conditional branch in C++ is the if statement. An if statement takes the form:
if (expression)
statement
or
if (expression)
statement
else
statement2
The expression is called a conditional expression. If the expression evaluates to true (non-zero), the
statement executes. If the expression evaluates to false, the else statement is executed if it exists.
#include <iostream>
int main()
{
std::cout << "Enter a number: ";
int x;
std::cin >> x;
if (x > 10)
std::cout << x << "is greater than 10\n";
else
std::cout << x << "is not greater than 10\n";
return 0;
}
3
COMPUTING II TEMB 1202 CHAPTER 3
Note that the if statement only executes a single statement if the expression is true, and the else only executes
a single statement if the expression is false. In order to execute multiple statements, we can use a block:
#include <iostream>
int main()
{
std::cout << "Enter a number: ";
int x;
std::cin >> x;
if (x > 10)
{
// both statements will be executed if x > 10
std::cout << "You entered " << x << "\n";
std::cout << x << "is greater than 10\n";
}
else
{
// both statements will be executed if x <= 10
std::cout << "You entered " << x << "\n";
std::cout << x << "is not greater than 10\n";
}
return 0;
}
Implicit blocks
If the programmer does not declare a block in the statement portion of an if statement or else statement, the
compiler will implicitly declare one. Thus:
if (expression)
statement
else
statement2
if (expression)
{
statement
}
else
{
statement2
}
Most of the time, this doesn’t matter. However, new programmers sometimes try to do something like this:
#include <iostream>
int main()
4
COMPUTING II TEMB 1202 CHAPTER 3
{
if (1)
int x = 5;
else
int x = 6;
std::cout << x;
return 0;
}
This won’t compile, with the compiler generating an error that identifier x isn’t defined. This is because the
above example is the equivalent of:
#include <iostream>
int main()
{
if (1)
{
int x = 5;
} // x destroyed here
else
{
int x = 6;
} // x destroyed here
std::cout << x; // x isn't defined here
return 0;
}
In this context, it’s clearer that variable x has block scope and is destroyed at the end of the block. By the
time we get to the std::cout line, x doesn’t exist.
Chaining if statements
#include <iostream>
int main()
{
std::cout << "Enter a number: ";
int x;
std::cin >> x;
if (x > 10)
std::cout << x << "is greater than 10\n";
else if (x < 10)
std::cout << x << "is less than 10\n";
else
std::cout << x << "is exactly 10\n";
return 0;
}
The above code executes identically to the following (which may be easier to understand):
#include <iostream>
int main()
5
COMPUTING II TEMB 1202 CHAPTER 3
{
std::cout << "Enter a number: ";
int x;
std::cin >> x;
if (x > 10)
std::cout << x << "is greater than 10\n";
else
{
if (x < 10)
std::cout << x << "is less than 10\n";
else
std::cout << x << "is exactly 10\n";
}
return 0;
}
First, x > 10 is evaluated. If true, then the “is greater” output executes and we’re done. Otherwise, the else
statement executes. That else statement is a nested if statement. So we then check if x < 10. If true, then the
“is less than” output executes and we’re done. Otherwise, the nested else statement executes. In that case,
we print the “is exactly” output, and we’re done.
In practice, we typically don’t nest the chained-ifs inside blocks, because it makes the statements harder to
read (and the code quickly becomes nested if there are lots of chained if statements).
#include <iostream>
int main()
{
std::cout << "Enter a positive number between 0 and
9999: ";
int x;
std::cin >> x;
if (x < 0)
std::cout << x << " is negative\n";
else if (x < 10)
std::cout << x << " has 1 digit\n";
else if (x < 100)
std::cout << x << " has 2 digits\n";
else if (x < 1000)
std::cout << x << " has 3 digits\n";
else if (x < 10000)
std::cout << x << " has 4 digits\n";
else
std::cout << x << " was larger than 9999\n";
return 0;
}
Nesting if statements
6
COMPUTING II TEMB 1202 CHAPTER 3
#include <iostream>
int main()
{
std::cout << "Enter a number: ";
int x;
std::cin >> x;
if (x >= 10) // outer if statement
// it is bad coding style to nest if statements this way
if (x <= 20) // inner if statement
std::cout << x << "is between 10 and 20\n";
// which if statement does this else belong to?
else
std::cout << x << "is greater than 20\n";
return 0;
}
The above program introduces a source of potential ambiguity called a dangling else problem. Is the else
statement in the above program matched up with the outer or inner if statement?
The answer is that an else statement is paired up with the last unmatched if statement in the same block.
Thus, in the program above, the else is matched up with the inner if statement.
To avoid such ambiguities when nesting complex statements, it is generally a good idea to enclose the
statement within a block. Here is the above program written without ambiguity:
#include <iostream>
int main()
{
std::cout << "Enter a number: ";
int x;
std::cin >> x;
if (x >= 10)
{
if (x <= 20)
std::cout << x << "is between 10 and 20\n";
else // attached to inner if statement
std::cout << x << "is greater than 20\n";
}
return 0;
}
Now it is much clearer that the else statement belongs to the inner if statement.
Encasing the inner if statement in a block also allows us to explicitly attach an else to the outer if statement:
#include <iostream>
int main()
{
std::cout << "Enter a number: ";
int x;
std::cin >> x;
if (x >= 10)
{
7
COMPUTING II TEMB 1202 CHAPTER 3
if (x <= 20)
std::cout << x << "is between 10 and 20\n";
}
else // attached to outer if statement
std::cout << x << "is less than 10\n";
return 0;
}
The use of a block tells the compiler that the else statement should attach to the if statement before the
block. Without the block, the else statement would attach to the nearest unmatched if statement, which
would be the inner if statement.
You can also have if statements check multiple conditions together by using the logical operators:
#include <iostream>
int main()
{
std::cout << "Enter an integer: ";
int x;
std::cin >> x;
std::cout << "Enter another integer: ";
int y;
std::cin >> y;
if (x > 0 && y > 0) // && is logical and -- checks if both conditions are true
std::cout << "both numbers are positive\n";
else if (x > 0 || y > 0) // || is logical or -- checks if either condition is true
std::cout << "One of the numbers is positive\n";
else
std::cout << "Neither number is positive\n";
return 0;
}
If statements are commonly used to do error checking. For example, to calculate a square root, the value
passed to the square root function should be a non-negative number:
#include <iostream>
#include <cmath> // for sqrt()
void printSqrt(double value)
{
if (value >= 0.0)
std::cout << "The square root of " << value << " is " << sqrt(value) << "\n";
else
std::cout << "Error: " << value << " is negative\n";
}
If statements can also be used to do early returns, where a function returns control to the caller before the
end of the function. In the following program, if the parameter value is negative, the function returns a
symbolic constant or enumerated value error code to the caller right away.
8
COMPUTING II TEMB 1202 CHAPTER 3
#include <iostream>
enum class ErrorCode
{
ERROR_SUCCESS = 0,
ERROR_NEGATIVE_NUMBER = -1
};
ErrorCode doSomething(int value)
{
// if value is a negative number
if (value < 0)
// early return an error code
return ErrorCode::ERROR_NEGATIVE_NUMBER;
// Do whatever here
return ErrorCode::ERROR_SUCCESS;
}
int main()
{
std::cout << "Enter a positive number: ";
int x;
std::cin >> x;
if (doSomething(x) == ErrorCode::ERROR_NEGATIVE_NUMBER)
{
std::cout << "You entered a negative number!\n";
}
else
{
std::cout << "It worked!\n";
}
return 0;
}
If statements are also commonly used to do simple math functionality, such as a min() or max() function
that returns the minimum or maximum of its parameters:
Null statements
It is possible to omit the statement part of an if statement. A statement with no body is called a null
statement, and it is declared by using a single semicolon in place of the statement. For readability purposes,
9
COMPUTING II TEMB 1202 CHAPTER 3
the semicolon of a null statement is typically placed on its own line. This indicates that the use of a null
statement was intentional, and makes it harder to overlook the use of the null statement.
if (x > 10)
; // this is a null statement
Null statements are typically used when the language requires a statement to exist but the programmer
doesn’t need one.
Null statements are rarely used in conjunction with if statements. However, they often unintentionally cause
problems for new or careless programmers. Consider the following snippet:
if (x == 0);
x = 1;
In the above snippet, the user accidentally put a semicolon on the end of the if statement. This unassuming
error actually causes the above snippet to execute like this:
if (x == 0)
; // the semicolon acts as a null statement
x = 1; // and this line always gets executed!
Warning: Make sure you don’t accidentally “terminate” your if statements with a semicolon.
Just a reminder that inside your if statement conditional, you should be using operator== when testing for
equality, not operator= (which is assignment). Consider the following program:
#include <iostream>
int main()
{
std::cout << "Enter 0 or 1: ";
int x;
std::cin >> x;
if (x = 0) // oops, we used an assignment here instead of a test for equality
std::cout << "You entered 0";
else
std::cout << "You entered 1";
return 0;
}
This program will compile and run, but will always produce the result “You entered 1” even if you enter 0.
This happens because x = 0 first assigns 0 to x, then evaluates to the value 0, which is boolean false. Since
the conditional is always false, the else statement always executes.
SWITCH STATEMENTS
Although it is possible to chain many if-else statements together, this is difficult to read. Consider the
following program:
#include <iostream>
enum Colors
{
COLOR_BLACK,
10
COMPUTING II TEMB 1202 CHAPTER 3
COLOR_WHITE,
COLOR_RED,
COLOR_GREEN,
COLOR_BLUE
};
void printColor(Colors color)
{
if (color == COLOR_BLACK)
std::cout << "Black";
else if (color == COLOR_WHITE)
std::cout << "White";
else if (color == COLOR_RED)
std::cout << "Red";
else if (color == COLOR_GREEN)
std::cout << "Green";
else if (color == COLOR_BLUE)
std::cout << "Blue";
else
std::cout << "Unknown";
}
int main()
{
printColor(COLOR_GREEN);
return 0;
}
Because doing if-else chains on a single variable testing for equality is so common, C++ provides an
alternative conditional branching operator called a switch. Here is the same program as above in switch
form:
11
COMPUTING II TEMB 1202 CHAPTER 3
}
}
The overall idea behind switch statements is simple: the switch expression is evaluated to produce a value,
and each case label is tested against this value for equality. If a case label matches, the statements after the
case label are executed. If no case label matches the switch expression, the statements after the default label
are executed (if it exists).
Because of the way they are implemented, switch statements are typically more efficient than if-else chains.
Starting a switch
We start a switch statement by using the switch keyword, followed by the expression that we would like to
evaluate. Typically this expression is just a single variable, but it can be something more complex like nX
+ 2 or nX - nY. The one restriction on this expression is that it must evaluate to an integral type (that is,
char, short, int, long, long long, or enum). Floating point variables and other non-integral types may not be
used here.
Following the switch expression, we declare a block. Inside the block, we use labels to define all of the
values we want to test for equality. There are two kinds of labels.
Case labels
The first kind of label is the case label, which is declared using the case keyword and followed by a constant
expression. A constant expression is one that evaluates to a constant value -- in other words, either a literal
(such as 5), an enum (such as COLOR_RED), or a constant variable (such as x, when x has been defined
as a const int).
The constant expression following the case label is tested for equality against the expression following the
switch keyword. If they match, the code under the case label is executed.
It is worth noting that all case label expressions must evaluate to a unique value. That is, you can not do
this:
switch (x)
{
case 4:
case 4: // illegal -- already used value 4!
case COLOR_BLUE: // illegal, COLOR_BLUE evaluates to 4!
};
It is possible to have multiple case labels refer to the same statements. The following function uses multiple
cases to test if the ‘c’ parameter is an ASCII digit.
bool isDigit(char c)
{
switch (c)
{
case '0': // if c is 0
case '1': // or if c is 1
case '2': // or if c is 2
12
COMPUTING II TEMB 1202 CHAPTER 3
case '3': // or if c is 3
case '4': // or if c is 4
case '5': // or if c is 5
case '6': // or if c is 6
case '7': // or if c is 7
case '8': // or if c is 8
case '9': // or if c is 9
return true; // then return true
default:
return false;
}
}
In the case where c is an ASCII digit, the first statement after the matching case statement is executed,
which is “return true”.
The second kind of label is the default label (often called the “default case”), which is declared using
the default keyword. The code under this label gets executed if none of the cases match the switch
expression. The default label is optional, and there can only be one default label per switch statement. It is
also typically declared as the last label in the switch block, though this is not strictly necessary.
In the isDigit() example above, if c is not an ASCII digit, the default case executes and returns false.
One of the trickiest things about case statements is the way in which execution proceeds when a case is
matched. When a case is matched (or the default is executed), execution begins at the first statement
following that label and continues until one of the following termination conditions is true:
Note that if none of these termination conditions are met, cases will overflow into subsequent cases!
Consider the following snippet:
switch (2)
{
case 1: // Does not match
std::cout << 1 << '\n'; // skipped
case 2: // Match!
std::cout << 2 << '\n'; // Execution begins here
case 3:
std::cout << 3 << '\n'; // This is also executed
case 4:
std::cout << 4 << '\n'; // This is also executed
default:
13
COMPUTING II TEMB 1202 CHAPTER 3
2
3
4
5
This is probably not what we wanted! When execution flows from one case into another case, this is
called fall-through. Fall-through is almost never desired by the programmer, so in the rare case where it
is, it is common practice to leave a comment stating that the fall-through is intentional.
break statements
A break statement (declared using the break keyword) tells the compiler that we are done with this switch
(or while, do while, or for loop). After a break statement is encountered, execution continues with the
statement after the end of the switch block.
Let’s look at our last example with break statements properly inserted:
switch (2)
{
case 1: // Does not match -- skipped
std::cout << 1 << '\n';
break;
case 2: // Match! Execution begins at the next statement
std::cout << 2 << '\n'; // Execution begins here
break; // Break terminates the switch statement
case 3:
std::cout << 3 << '\n';
break;
case 4:
std::cout << 4 << '\n';
break;
default:
std::cout << 5 << '\n';
break;
}
// Execution resumes here
Now, when case 2 matches, the integer 2 will be output, and the break statement will cause the switch to
terminate. The other cases are skipped.
Warning: Forgetting the break statement at the end of the case statements is one of the most common C++
mistakes made!
With if statements, you can only have a single statement after the if-condition, and that statement is
considered to be implicitly inside a block:
if (x > 10)
std::cout << x << " is greater than 10\n"; // this line implicitly considered to be inside a block
14
COMPUTING II TEMB 1202 CHAPTER 3
However, with switch statements, you are allowed to have multiple statements after a case label, and they
are not considered to be inside an implicit block.
switch (1)
{
case 1:
std::cout << 1;
foo();
std::cout << 2;
break;
default:
std::cout << "default case\n";
break;
}
In the above example, the 4 statements between the case label and default label are part of case 1, but not
considered to be inside an implicit block.
You can declare (but not initialize) variables inside the switch, both before and after the case labels:
switch (1)
{
int a; // okay, declaration is allowed before the case labels
int b = 5; // illegal, initialization is not allowed before the case labels
case 1:
int y; // okay, declaration is allowed within a case
y = 4; // okay, this is an assignment
break;
case 2:
y = 5; // okay, y was declared above, so we can use it here too
break;
case 3:
int z = 4; // illegal, initialization is not allowed within a case
break;
default:
std::cout << "default case" << std::endl;
break;
}
Note that although variable y was defined in case 1, it was used in case 2 as well. Because the statements
under each case are not inside an implicit block, that means all statements inside the switch are part of the
same scope. Thus, a variable defined in one case can be used in another case, even if the case in which the
variable is defined is never executed!
This may seem a bit counter-intuitive, so let’s examine why. When you define a local variable like “int y;”,
the variable isn’t created at that point -- it’s actually created at the start of the block it’s declared in.
However, it is not visible (in scope) until the point of declaration. The declaration statement doesn’t need
to execute -- it just tells the compiler that the variable can be used past that point. So with that in mind, it’s
15
COMPUTING II TEMB 1202 CHAPTER 3
a little less weird that a variable declared in one case statement can be used in another cases statement, even
if the case statement that declares the variable is never executed.
However, initialization of variables directly underneath a case label is disallowed and will cause a compile
error. This is because initializing a variable does require execution, and the case statement containing the
initialization may not be executed!
If a case needs to define and/or initialize a new variable, best practice is to do so inside a block underneath
the case statement:
switch (1)
{
case 1:
{ // note addition of block here
int x = 4; // okay, variables can be initialized inside a block inside a case
std::cout << x;
break;
}
default:
std::cout << "default case" << std::endl;
break;
}
Rule: If defining variables used in a case statement, do so in a block inside the case (or before the switch
if appropriate)
GOTO STATEMENTS
The goto statement is a control flow statement that causes the CPU to jump to another spot in the code.
This spot is identified through use of a statement label. The following is an example of a goto statement
and statement label:
#include <iostream>
#include <cmath> // for sqrt() function
int main()
{
double x;
tryAgain: // this is a statement label
std::cout << "Enter a non-negative number";
std::cin >> x;
if (x < 0.0)
goto tryAgain; // this is the goto statement
std::cout << "The sqrt of " << x << " is " << sqrt(x) << std::endl;
return 0;
}
In this program, the user is asked to enter a non-negative number. However, if a negative number is entered,
the program utilizes a goto statement to jump back to the tryAgain label. The user is then asked again to
enter a new number. In this way, we can continually ask the user for input until he or she enters something
valid.
There are some restrictions on the use of goto statements. For example, you can’t jump forward over a
variable that’s initialized in the same block as the goto:
16
COMPUTING II TEMB 1202 CHAPTER 3
int main()
{
goto skip; // invalid forward jump
int x = 5;
skip:
x += 3; // what would this even evaluate to?
return 0;
}
In general, use of goto is shunned in C++ (and most other high level languages as well). Edsger W.
Dijkstra, a noted computer scientist, laid out the case in a famous but difficult to read paper called Go To
Statement Considered Harmful. The primary problem with goto is that it allows a programmer to cause
the point of execution to jump around the code arbitrarily. This creates what is not-so-affectionately known
as spaghetti code. Spaghetti code is code that has a path of execution that resembles a bowl of spaghetti
(all tangled and twisted), making it extremely difficult to follow the logic of such code.
As Dijkstra says somewhat humorously, “the quality of programmers is a decreasing function of the density
of go to statements in the programs they produce”.
Goto statements are common in some older languages, such as Basic or Fortran, and even used in C.
However, in C++, goto statements are almost never used, as almost any code written using a goto statement
can be more clearly written using other constructs in C++, such as loops, exception handlers, or destructors
(all of which we’ll cover in future lessons).
WHILE STATEMENTS
The while statement is the simplest of the four loops that C++ provides, and it has a definition very similar
to that of an if statement:
while (expression)
statement;
A while statement is declared using the while keyword. When a while statement is executed, the expression
is evaluated. If the expression evaluates to true (non-zero), the statement executes.
However, unlike an if statement, once the statement has finished executing, control returns to the top of the
while statement and the process is repeated.
Let’s take a look at a simple while loop. The following program prints all the numbers from 0 to 9:
#include <iostream>
int main()
{
int count = 0;
while (count < 10)
{
std::cout << count << " ";
++count;
}
std::cout << "done!";
return 0;
}
17
COMPUTING II TEMB 1202 CHAPTER 3
This outputs:
0 1 2 3 4 5 6 7 8 9 done!
Let’s take a closer look at what this program is doing. First, count is initialized to 0. 0 < 10 evaluates to
true, so the statement block executes. The first statement prints 0, and the second increments count to 1.
Control then returns back to the top of the while statement. 1 < 10 evaluates to true, so the code block is
executed again. The code block will repeatedly execute until count is 10, at which point 10 < 10 will
evaluate to false, and the loop will exit.
It is possible that a while statement executes 0 times. Consider the following program:
#include <iostream>
int main()
{
int count = 15;
while (count < 10)
{
std::cout << count << " ";
++count;
}
std::cout << "done!";
return 0;
}
The condition 15 < 10 immediately evaluates to false, so the while statement is skipped. The only thing this
program prints is done!.
Infinite loops
On the other hand, if the expression always evaluates to true, the while loop will execute forever. This is
called an infinite loop. Here is an example of an infinite loop:
#include <iostream>
int main()
{
int count = 0;
while (count < 10) // this condition will never be false
std::cout << count << " "; // so this line will repeatedly execute
return 0; // this line will never execute
}
Because count is never incremented in this program, count < 10 will always be true. Consequently, the loop
will never terminate, and the program will print "0 0 0 0 0 ..." forever.
18
COMPUTING II TEMB 1202 CHAPTER 3
Programs that run until the user decides to stop them sometimes intentionally use an infinite loop along
with a return, break, or exit statement to terminate the loop. It is common to see this kind of loop in web
server applications that run continuously and service web requests.
Loop variables
Often, we want a loop to execute a certain number of times. To do this, it is common to use a loop variable,
often called a counter. A loop variable is an integer variable that is declared for the sole purpose of counting
how many times a loop has executed. In the examples above, the variable count is a loop variable.
Loop variables are often given simple names, such as i, j, or k. However, naming variables i, j, or k has one
major problem. If you want to know where in your program a loop variable is used, and you use the search
function on i, j, or k, the search function will return half your program! Many words have an i, j, or k in
them. Consequently, a better idea is to use iii, jjj, or kkk as your loop variable names. Because these names
are more unique, this makes searching for loop variables much easier, and helps them stand out as loop
variables. An even better idea is to use "real" variable names, such as count, or a name that gives more
detail about what you're counting.
It is best practice to use signed integers for loop variables. Using unsigned integers can lead to unexpected
issues. Consider the following code:
#include <iostream>
int main()
{
unsigned int count = 10;
// count from 10 down to 0
while (count >= 0)
{
if (count == 0)
std::cout << "blastoff!";
else
std::cout << count << " ";
--count;
}
return 0;
}
Take a look at the above example and see if you can spot the error. It's not very obvious.
It turns out, this program is an infinite loop. It starts out by printing "10 9 8 7 6 5 4 3 2 1 blastoff!" as
desired, but then goes off the rails, and starts counting down from 4294967295. Why? Because the loop
condition count >= 0 will never be false! When count is 0, 0 >= 0 is true. Then --count is executed, and
count overflows back to 4294967295. And since 4294967295 is >= 0, the program continues. Because
count is unsigned, it can never be negative, and because it can never be negative, the loop won't terminate.
Iteration
19
COMPUTING II TEMB 1202 CHAPTER 3
Because the loop body is typically a block, and because that block is entered and exited with each iteration,
any variables declared inside the loop body are created and then destroyed with each iteration. In the
following example, variable x will be created and destroyed 5 times:
#include <iostream>
int main()
{
int count = 1;
int sum = 0; // sum is declared up here because we need it later (beyond the loop)
while (count <= 5) // iterate 5 times
{
int x; // x is created here with each iteration
std::cout << "Enter integer #" << count << ':';
std::cin >> x;
sum += x;
// increment the loop counter
++count;
} // x is destroyed here with each iteration
std::cout << "The sum of all numbers entered is: " << sum;
return 0;
}
For fundamental variables, this is fine. For non-fundamental variables (such as structs and classes) this may
cause performance issues. Consequently, you may want to consider defining non-fundamental variables
before the loop. This is another one of the cases where you might declare a variable well before its first
actual use.Note that variable count is declared outside the loop. This is necessary because we need the value
to persist across iterations (not be destroyed with each iteration).
Often, we want to do something every n iterations, such as print a newline. This can easily be done by using
the modulus operator on our counter:
#include <iostream>
// Iterate through every number between 1 and 50
int main()
{
int count = 1;
while (count <= 50)
{
// print the number (pad numbers under 10 with a leading 0 for formatting purposes)
if (count < 10)
std::cout << "0" << count << " ";
else
std::cout << count << " ";
// if the loop variable is divisible by 10, print a newline
if (count % 10 == 0)
std::cout << "\n";
// increment the loop counter
++count;
}
return 0;
}
20
COMPUTING II TEMB 1202 CHAPTER 3
01 02 03 04 05 06 07 08 09 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
Nested loops
It is also possible to nest loops inside of other loops. In the following example, the inner loop and outer
loops each have their own counters. However, note that the loop expression for the inner loop makes use
of the outer loop's counter as well!
#include <iostream>
// Loop between 1 and 5
int main()
{
int outer = 1;
while (outer <= 5)
{
// loop between 1 and outer
int inner = 1;
while (inner <= outer)
std::cout << inner++ << " ";
// print a newline at the end of each row
std::cout << "\n";
++outer;
}
return 0;
}
This program prints:
1
12
123
1234
12345
DO WHILE STATEMENTS
One interesting thing about the while loop is that if the loop condition is initially false, the while loop will
not execute at all. It is sometimes the case that we know we want a loop to execute at least once, such as
when displaying a menu. To help facilitate this, C++ offers the do-while loop:
do
statement
while (condition);
The statement in a do-while loop always executes at least once. After the statement has been executed, the
do-while loop checks the condition. If the condition is true, the path of execution jumps back to the top of
the do-while loop and executes it again.
21
COMPUTING II TEMB 1202 CHAPTER 3
Here is an example of using a do-while loop to display a menu to the user and wait for the user to make a
valid choice:
#include <iostream>
int main()
{
// selection must be declared outside do/while loop
int selection;
do
{
std::cout << "Please make a selection: \n";
std::cout << "1) Addition\n";
std::cout << "2) Subtraction\n";
std::cout << "3) Multiplication\n";
std::cout << "4) Division\n";
std::cin >> selection;
}
while (selection != 1 && selection != 2 &&
selection != 3 && selection != 4);
// do something with selection here
// such as a switch statement
std::cout << "You selected option #" << selection << "\n";
return 0;
}
One interesting thing about the above example is that the selection variable must be declared outside of the
do block. Why do you think that is?
If the selection variable were to be declared inside the do block, it would be destroyed when the do block
terminates, which happens before the while conditional is executed. But we need the variable to use in the
while conditional -- consequently, the selection variable must be declared outside the do block.
Generally it is good form to use a do-while loop instead of a while loop when you intentionally want the
loop to execute at least once, as it makes this assumption explicit -- however, it’s not that big of a deal either
way.
FOR STATEMENTS
By far, the most utilized looping statement in C++ is the for statement. The for statement (also called a for
loop) is ideal when we know exactly how many times we need to iterate, because it lets us easily define,
initialize, and change the value of loop variables after each iteration.
The easiest way to understand a for loop is to convert it into an equivalent while loop:
22
COMPUTING II TEMB 1202 CHAPTER 3
{
statement;
end-expression;
}
} // variables defined inside the loop go out of scope here
The variables defined inside a for loop have a special kind of scope called loop scope. Variables with loop
scope exist only within the loop, and are not accessible outside of it.
1. The init-statement is evaluated. Typically, the init-statement consists of variable definitions and
initialization. This statement is only evaluated once, when the loop is first executed.
2. The condition-expression is evaluated. If this evaluates to false, the loop terminates immediately.
If this evaluates to true, the statement is executed.
3. After the statement is executed, the end-expression is evaluated. Typically, this expression is used
to increment or decrement the variables declared in the init-statement. After the end-expression has
been evaluated, the loop returns to step 2.
Let’s take a look at a sample for loop and discuss how it works:
for (int count=0; count < 10; ++count)
std::cout << count << " ";
First, we declare a loop variable named count, and assign it the value 0.
Second, count < 10 is evaluated, and since count is 0, 0 < 10 evaluates to true. Consequently, the statement
executes, which prints 0.
Third, ++count is evaluated, which increments count to 1. Then the loop goes back to the second step.
Now, 1 < 10 is evaluated to true, so the loop iterates again. The statement prints 1, and count is incremented
to 2. 2 < 10 evaluates to true, the statement prints 2, and count is incremented to 3. And so on. Eventually,
count is incremented to 10, 10 < 10 evaluates to false, and the loop exits.
0123456789
For loops can be hard for new programmers to read -- however, experienced programmers love them
because they are a very compact way to do loops of this nature. For the sake of example, let's uncompact
the above for loop by converting it into an equivalent while loop:
23
COMPUTING II TEMB 1202 CHAPTER 3
That doesn't look so bad, does it? Note that the outer braces are necessary here, because count goes out of
scope when the loop ends.
This is a straightforward incrementing for loop, with count looping from 0 up to (but excluding) exponent.
If exponent is 0, the for loop will execute 0 times, and the function will return 1.
If exponent is 1, the for loop will execute 1 time, and the function will return 1 * base.
If exponent is 2, the for loop will execute 2 times, and the function will return 1 * base * base.
Although most for loops increment the loop variable by 1, we can decrement it as well:
9876543210
Alternately, we can change the value of our loop variable by more than 1 with each iteration:
97531
Off-by-one errors
One of the biggest problems that new programmers have with for loops (and other kinds of loops) is off-
by-one errors. Off-by-one errors occur when the loop iterates one too many or one too few times. This
generally happens because the wrong relational operator is used in the conditional-expression (eg. > instead
of >=). These errors can be hard to track down because the compiler will not complain about them -- the
program will run fine, but it will produce the wrong result.
When writing for loops, remember that the loop will execute as long as the conditional-expression is true.
Generally it is a good idea to test your loops using known values to make sure that they work as expected.
A good way to do this is to test your loop with known inputs that cause it to iterate 0, 1, and 2 times. If it
works for those, it will likely work for any number of iterations.
24
COMPUTING II TEMB 1202 CHAPTER 3
Rule: Test your loops with known inputs that cause it to iterate 0, 1, and 2 times.
Omitted expressions
It is possible to write for loops that omit any or all of the expressions. For example, in the following
example, we'll omit the init-statement and end-expression:
int count=0;
for ( ; count < 10; )
{
std::cout << count << " ";
++count;
}
This for loop produces the result:
0123456789
Rather than having the for loop do the initialization and incrementing, we've done it manually. We have
done so purely for academic purposes in this example, but there are cases where not declaring a loop
variable (because you already have one) or not incrementing it (because you're incrementing it some other
way) are desired.
Although you do not see it very often, it is worth noting that the following example produces an infinite
loop:
for (;;)
statement;
The above example is equivalent to:
while (true)
statement;
This might be a little unexpected, as you'd probably expect an omitted condition-expression to be treated
as "false". However, the C++ standard explicitly (and inconsistently) defines that an omitted condition-
expression in a for loop should be treated as "true".
We recommend avoiding this form of the for loop altogether and using while(true) instead.
Multiple declarations
Although for loops typically iterate over only one variable, sometimes for loops need to work with multiple
variables. When this happens, the programmer can make use of the comma operator in order to assign (in
the init-statement) or change (in the end-statement) the value of multiple variables:
09
25
COMPUTING II TEMB 1202 CHAPTER 3
18
27
36
45
54
63
72
81
90
This is the only place in C++ where the comma operator typically gets used.
In older versions of C++, variables defined as part of the init-statement did not get destroyed at the end of
the loop. This meant that you could have something like this:
for (int count=0; count < 10; ++count) // count defined here
std::cout << count << " ";
Like other types of loops, for loops can be nested inside other loops. In the following example, we're nesting
a for loop inside another for loop:
#include <iostream>
int main()
{
for (char c = 'a'; c <= 'e'; ++c) // outer loop on letters
{
std::cout << c; // print our letter first
26
COMPUTING II TEMB 1202 CHAPTER 3
a012
b012
c012
d012
e012
Here's some more detail on what's happening here. The outer loop runs first, and char c is initialized to 'a'.
Then c <= 'e' is evaluated, which is true, so the loop body executes. Since c is set to 'a', this first prints 'a'.
Next the inner loop executes entirely (which prints '0', '1', and '2'). Then a newline is printed. Now the outer
loop body is finished, so the outer loop returns to the top, c is incremented to 'b', and the loop condition is
re-evaluated. Since the loop condition is still true the next iteration of the outer loop begins. This prints
("b012\n"). And so on.
Conclusion
For statements are the most commonly used loop in the C++ language. Even though its syntax is typically
a bit confusing to new programmers, you will see for loops so often that you will understand them in no
time at all!
Although you have already seen the break statement in the context of switch statements, it deserves a fuller
treatment since it can be used with other types of loops as well. The break statement causes a do, for, switch,
or while statement to terminate.
Breaking a switch
In the context of a switch statement, a break is typically used at the end of each case to signify the case is
finished (which prevents fall-through):
switch (ch)
{
case '+':
doAddition(x, y);
break;
case '-':
doSubtraction(x, y);
break;
case '*':
doMultiplication(x, y);
break;
case '/':
doDivision(x, y);
break;
}
27
COMPUTING II TEMB 1202 CHAPTER 3
Breaking a loop
In the context of a loop, a break statement can be used to cause the loop to terminate early:
#include <iostream>
int main()
{
int sum = 0;
// Allow the user to enter up to 10 numbers
for (int count=0; count < 10; ++count)
{
std::cout << "Enter a number to add, or 0 to exit: ";
int num;
std::cin >> num;
// exit loop if user enters 0
if (num == 0)
break;
// otherwise add number to our sum
sum += num;
}
std::cout << "The sum of all the numbers you entered is " << sum << "\n";
return 0;
}
This program allows the user to type up to 10 numbers, and displays the sum of all the numbers entered at
the end. If the user enters 0, the break causes the loop to terminate early (before 10 numbers have been
entered).
#include <iostream>
int main()
{
while (true) // infinite loop
{
std::cout << "Enter 0 to exit or anything else to continue: ";
int num;
std::cin >> num;
// exit loop if user enters 0
if (num == 0)
break;
}
std::cout << "We're out!\n";
return 0;
}
Break vs return
New programmers often have trouble understanding the difference between break and return. A break
statement terminates the switch or loop, and execution continues at the first statement beyond the switch or
loop. A return statement terminates the entire function that the loop is within, and execution continues at
point where the function was called.
28
COMPUTING II TEMB 1202 CHAPTER 3
#include <iostream>
int breakOrReturn()
{
while (true) // infinite loop
{
std::cout << "Enter 'b' to break or 'r' to return: ";
char ch;
std::cin >> ch;
if (ch == 'b')
break; // execution will continue at the first statement beyond the loop
if (ch == 'r')
return 1; // return will cause the function to immediately return to the caller (in this case, main())
}
// breaking the loop causes execution to resume here
std::cout << "We broke out of the loop\n";
return 0;
}
int main()
{
int returnValue = breakOrReturn();
std::cout << "Function breakOrReturn returned " << returnValue << '\n';
return 0;
}
Continue
The continue statement provides a convenient way to jump to the end of the loop body for the current
iteration. This is useful when we want to terminate the current iteration early.
In the case of a for loop, the end-statement of the for loop still executes after a continue (since this happens
after the end of the loop body).
Be careful when using a continue statement with while or do-while loops. Because these loops typically
increment the loop variables in the body of the loop, using continue can cause the loop to become infinite!
Consider the following program:
int count(0);
while (count < 10)
29
COMPUTING II TEMB 1202 CHAPTER 3
{
if (count == 5)
continue; // jump to end of loop body
std::cout << count << " ";
++count;
// The continue statement jumps to here
}
This program is intended to print every number between 0 and 9 except 5. But it actually prints:
01234
and then goes into an infinite loop. When count is 5, the if statement evaluates to true, and the loop jumps
to the bottom. The count variable is never incremented. Consequently, on the next pass, count is still 5,
the if statement is still true, and the program continues to loop forever.
int count(0);
do
{
if (count == 5)
continue; // jump to end of loop body
std::cout << count << " ";
// The continue statement jumps to here
} while (++count < 10); // this still executes since it's outside the loop body
This prints:
012346789
Many textbooks caution readers not to use break and continue, both because it causes the execution flow to
jump around and because it can make the flow of logic harder to follow. For example, a break in the middle
of a complicated piece of logic could either be missed, or it may not be obvious under what conditions it
should be triggered.
However, used judiciously, break and continue can help make loops more readable by keeping the number
of nested blocks down and reducing the need for complicated looping logic.
#include <iostream>
int main()
{
int count(0); // count how many times the loop iterates
bool keepLooping { true }; // controls whether the loop ends or not
while (keepLooping)
{
std::cout << "Enter 'e' to exit this loop or any other character to continue: ";
char ch;
std::cin >> ch;
if (ch == 'e')
30
COMPUTING II TEMB 1202 CHAPTER 3
keepLooping = false;
else
{
++count;
std::cout << "We've iterated " << count << " times\n";
}
}
return 0;
}
This program uses a boolean variable to control whether the loop continues or not, as well as a nested block
that only runs if the user doesn’t exit.
#include <iostream>
int main()
{
int count(0); // count how many times the loop iterates
while (true) // loop until user terminates
{
std::cout << "Enter 'e' to exit this loop or any other character to continue: ";
char ch;
std::cin >> ch;
if (ch == 'e')
break;
++count;
std::cout << "We've iterated " << count << " times\n";
}
return 0;
}
In this version, by using a single break statement, we’ve avoided the use of a boolean variable (and having
to understand both what its intended use is, and where it is set), an else statement, and a nested block.
Minimizing the number of variables used and keeping the number of nested blocks down both improve
code understandability more than a break or continue harms it. For that reason, we believe judicious use of
break or continue is acceptable.
31