This document is part of the HTML publication "An Introduction to the Imperative Part of C++"
The original version was produced by Rob Miller at Imperial College London, September 1996.
Version 1.1 (modified by David Clark at Imperial College London, September 1997)
Version 1.2 (modified by Bob White at Imperial College London, September 1998)
Version 1.3, 1.4, 2.0, ..., 2.21, 3.1, ..., 3.3 (modified by William Knottenbelt at Imperial College London, September 1999-September 2026)
In this lecture we will look more closely at branch and loop statements such as "for" and "while" loops and "if ... else" statements. All these constructs involve the evaluation of one or more logical (or "Boolean") expressions, and so we begin by looking at Boolean values and expressions in C++.
C++ has a built-in Boolean data type "bool". A variable of type "bool" can have one of two values, "true" or "false". For example:
bool raining = true; bool finished = false;
Boolean values occur naturally as the results of comparison expressions. For example:
age >= 18 score == 100 x != y age >= 18 && score >= 60
Each of these expressions evaluates to either "true" or "false". The logical operators "&&" ("and"), "||" ("or") and "!" ("not") can be used to combine or negate Boolean expressions.
Functions can also return values of type "bool". The following example program takes a candidate's age and test score, and reports whether the candidate has passed the test. It uses the following criteria: candidates between 0 and 14 years old have a pass mark of 50%, 15 and 16 year olds have a pass mark of 55%, over 16's have a pass mark of 60%:
#include <iostream>
using namespace std;
bool acceptable(int age, int score);
/* START OF MAIN PROGRAM */
int main()
{
int candidate_age, candidate_score;
cout << "Enter the candidate's age: ";
cin >> candidate_age;
cout << "Enter the candidate's score: ";
cin >> candidate_score;
if (acceptable(candidate_age, candidate_score))
cout << "This candidate passed the test.\n";
else
cout << "This candidate failed the test.\n";
return 0;
}
/* END OF MAIN PROGRAM */
/* FUNCTION TO EVALUATE IF TEST SCORE IS ACCEPTABLE */
bool acceptable(int age, int score)
{
if (age <= 14 && score >= 50)
return true;
if (age <= 16 && score >= 55)
return true;
if (score >= 60)
return true;
return false;
}
/* END OF FUNCTION */
Notice that the function call "acceptable(candidate_age, candidate_score)" is itself a Boolean expression: it evaluates to either "true" or "false". It can therefore be used directly as the condition of the "if" statement. There is no need to write:
if (acceptable(candidate_age, candidate_score) == true)
and it is generally clearer simply to write:
if (acceptable(candidate_age, candidate_score))
Because the conditions inside the "acceptable(...)" function are themselves Boolean expressions, we could alternatively write the whole function as:
bool acceptable(int age, int score)
{
return (age <= 14 && score >= 50) ||
(age <= 16 && score >= 55) ||
score >= 60;
}
Although "bool" is a distinct built-in C++ type, C++ allows conversions between Boolean and integer values. "false" converts to the integer 0 and "true" converts to the integer 1. Conversely, the integer 0 converts to "false", while a non-zero integer converts to "true". In normal code, however, Boolean quantities should be represented using "bool", "true" and "false".
We have already been introduced to "for" loops in Lecture 2 and to "while" loops in Lecture 5. Notice that any "for" loop can be re-written as a "while" loop. For example, Program 2.2.2 from Lecture 2, which was:
#include <iostream>
using namespace std;
int main()
{
int number;
char character;
for (number = 32 ; number <= 126 ; number = number + 1) {
character = number;
cout << "The character '" << character;
cout << "' is represented as the number ";
cout << number << " in the computer.\n";
}
return 0;
}
can be written equivalently as
#include <iostream>
using namespace std;
int main()
{
int number;
char character;
number = 32;
while (number <= 126)
{
character = number;
cout << "The character '" << character;
cout << "' is represented as the number ";
cout << number << " in the computer.\n";
number++;
}
return 0;
}
Moreover, any "while" loop can be trivially re-written as a "for" loop - we could for example replace the line
while (number <= 126)
with the line
for ( ; number <= 126 ; )
in the program above.
There is a third kind of "loop" statement in C++ called a "do ... while" loop. This differs from "for" and "while" loops in that the statement(s) inside the {} braces are always executed once, before the repetition condition is even checked. "Do ... while" loops are useful, for example, to ensure that the program user's keyboard input is of the correct format:
...
...
do
{
cout << "Enter the candidate's score: ";
cin >> candidate_score;
if (candidate_score > 100 || candidate_score < 0)
cout << "Score must be between 0 and 100.\n";
}
while (candidate_score > 100 || candidate_score < 0);
...
...
Program Fragment 4.2.2a
This avoids the need to repeat the input prompt and statement, which would be necessary in the equivalent "while" loop:
...
...
cout << "Enter the candidate's score: ";
cin >> candidate_score;
while (candidate_score > 100 || candidate_score < 0)
{
cout << "Score must be between 0 and 100.\n";
cout << "Enter the candidate's score: ";
cin >> candidate_score;
}
...
...
Program Fragment 4.2.2b
We have already seen (Lecture 1) how "if" statements can be strung together to form a "multiway branch". Here's a simplified version of the previous example:
... ... if (total_test_score >=0 && total_test_score < 50) cout << "You are a failure - you must study much harder.\n"; else if (total_test_score < 60) cout << "You have just scraped through the test.\n"; else if (total_test_score < 80) cout << "You have done quite well.\n"; else if (total_test_score <= 100) cout << "Your score is excellent - well done.\n"; else cout << "Incorrect score - must be between 0 and 100.\n"; ... ...
Because multiple selection can sometimes be difficult to follow, C++ provides an alternative method of handling this concept, called the switch statement. "Switch" statements can be used when several options depend on the value of a single variable or expression. In the example above, the message printed depends on the value of "total_test_score". This can be any number between 0 and 100, but we can make things easier to handle by introducing an extra integer variable "score_out_of_ten", and adding the assignment:
score_out_of_ten = total_test_score / 10;
The programming task is now as follows: (i) if "score_out_of_ten" has value 0, 1, 2, 3 or 4, print "You are a failure - you must study much harder", (ii) if "score_out_of_ten" has value 5, print "You have just scraped through the test", (iii) if "score_out_of_ten" has value 6 or 7, print "You have done quite well", and finally (iv) if "score_out_of_ten" has value 8, 9 or 10, print "Your score is excellent - well done". Here's how this is achieved with a "switch" statement:
...
...
score_out_of_ten = total_test_score / 10;
switch (score_out_of_ten)
{
case 0:
case 1:
case 2:
case 3:
case 4: cout << "You are a failure - you ";
cout << "must study much harder.\n";
break;
case 5: cout << "You have just scraped through the test.\n";
break;
case 6:
case 7: cout << "You have done quite well.\n";
break;
case 8:
case 9:
case 10: cout << "Your score is excellent - well done.\n";
break;
default: cout << "Incorrect score - must be between ";
cout << "0 and 100.\n";
}
...
...
In general, the syntax of a "switch" statement is (approximately):
switch (selector)
{
case label1: <statements 1>
break;
...
...
...
case labelN: <statements N>
break;
default: <statements>
}
There are several things to note about such "switch" statements:
We have already seen how compound statements in C++ are delimited by "{}" braces. These braces have a special effect on variable declarations. A compound statement that contains one or more variable declarations is called a block, and the variables declared within the block have the block as their scope. In other words, the variables are "created" each time the program enters the block, and "destroyed" upon exit. If the same identifier has been used both for a variable inside and a variable outside the block, the variables are unrelated. While in the block, the program will assume by default that the identifier refers to the inner variable - it only looks outside the block for the variable if it can't find a variable declaration inside. Hence the program
#include <iostream>
using namespace std;
int integer1 = 1;
int integer2 = 2;
int integer3 = 3;
int main()
{
int integer1 = -1;
int integer2 = -2;
{
int integer1 = 10;
cout << "integer1 == " << integer1 << "\n";
cout << "integer2 == " << integer2 << "\n";
cout << "integer3 == " << integer3 << "\n";
}
cout << "integer1 == " << integer1 << "\n";
cout << "integer2 == " << integer2 << "\n";
cout << "integer3 == " << integer3 << "\n";
return 0;
}
produces the output
integer1 == 10 integer2 == -2 integer3 == 3 integer1 == -1 integer2 == -2 integer3 == 3
The use of variables local to a block can sometimes be justified because it saves on memory, or because it releases an identifier for re-use in another part of the program. The following program prints a series of "times tables" for integers from 1 to 10:
#include <iostream>
using namespace std;
int main()
{
int number;
for (number = 1 ; number <= 10 ; number++)
{
int multiplier;
for (multiplier = 1 ; multiplier <= 10 ; multiplier++)
{
cout << number << " x " << multiplier << " = ";
cout << number * multiplier << "\n";
}
cout << "\n";
}
...
...
However, we can achieve the same effect, and end up with a clearer program, by using a function:
#include <iostream>
using namespace std;
void print_times_table(int value, int lower, int upper);
int main()
{
int number;
for (number = 1 ; number <= 10 ; number++)
{
print_times_table(number,1,10);
cout << "\n";
}
...
...
}
void print_times_table(int value, int lower, int upper)
{
int multiplier;
for (multiplier = lower ; multiplier <= upper ; multiplier++)
{
cout << value << " x " << multiplier << " = ";
cout << value * multiplier << "\n";
}
}
or eliminate all variable declarations from "main()" using two functions:
#include <iostream>
void print_tables(int smallest, int largest);
void print_times_table(int value, int lower, int upper);
int main()
{
print_tables(1,10);
...
...
}
void print_tables(int smallest, int largest)
{
int number;
for (number = smallest ; number <= largest ; number++)
{
print_times_table(number,1,10);
cout << "\n";
}
}
void print_times_table(int value, int lower, int upper)
{
int multiplier;
for (multiplier = lower ; multiplier <= upper ; multiplier++)
{
cout << value << " x " << multiplier << " = ";
cout << value * multiplier << "\n";
}
}
The above "times table" programs illustrate how nested loop statements can be made more readable by the use of functional abstraction. By making the body of the loop into a function call, its design can be separated from the design of the rest of the program, and problems with scoping of variables and overloading of variable names can be avoided.
In this lecture we have seen how the built-in Boolean type "bool" and the values "true" and "false" can be used to represent conditions, and how functions which return "bool" can be used directly in branch and loop statements. We have discussed the relationship between "for", "while" and "do ... while" loops, and shown how chains of "if ... else" statements can in some cases be re-written as "switch" statements. We have also seen how local variables function within blocks delimited by "{}" braces. The material here is also covered in more detail in Savitch, Chapter 3.