You can find the wrox.com code downloads for this chapter on the Download Code tab at www.wrox.com/go/beginningvisualc. The code is in the Chapter 3 download and individually named according to the names throughout the chapter.
Unless you want to make decisions on a whim, you need a mechanism for comparing things. This involves some new operators called relational operators. Because all information in your computer is ultimately represented by numerical values (in the last chapter you saw how character information is represented by numeric codes), comparing numerical values is the essence of all decision making. You have six operators for comparing two values:
< |
less than | <= |
less than or equal to |
> |
greater than | >= |
greater than or equal to |
== |
equal to | != |
not equal to |
Each operator compares the values of two operands and returns one of the two possible values of type bool
: true
if the comparison is true, or false
if it is not. You can see how this works by having a look at a few simple examples. The operands can be variables, literals, or expressions. Suppose you have created integer variables i
and j
with the values 10 and –5, respectively. The expressions,
i > j i != j j > -8 i <= j + 15
all return the value true
.
Assume that you have defined the following variables:
char first {'A'}, last {'Z'};
Here are some examples of comparisons using these:
first == 65 first < last 'E' <= first first != last
All four expressions compare ASCII code values. The first expression returns true
because first
was initialized with 'A'
, which is the equivalent of decimal 65. The second expression checks whether the value of first
, which is 'A'
, is less than the value last
, which is 'Z'
. the ASCII codes for the capital letters are represented by an ascending sequence of numerical values from 65 to 90, 65 representing 'A'
and 90 representing 'Z'
, so this comparison also returns the value true
. The third expression returns the value false
because 'E'
is greater than the value of first
. The last expression returns true
because 'A'
is definitely not equal to 'Z'
.
Let’s consider some slightly more complicated comparisons with variables defined by the statements:
int i {-10}, j {20};
double x {1.5}, y {-0.25E-10};
Take a look at the following expressions:
-1 < y j < (10 - i) 2.0*x >= (3 + y)
Here you use expressions that result in numerical values as operands. The precedence table for operators that you saw in Chapter 2 shows that none of the parentheses are strictly necessary, but they do help to make the expressions clearer. The first comparison is true, and so returns the bool
value true
. The variable y
has a very small negative value, -0.000000000025, and so is greater than -1. The second comparison returns the value false
. The expression 10 - i
has the value 20, which is the same as j
. The third expression returns true
because the expression 3 + y
is slightly less than 3.
You can use relational operators to compare values of any of the fundamental types or of the enumeration types, so all you need now is a way of using the results of a comparison to modify the behavior of a program.
The basic if
statement allows your program to execute a single statement — or a block of statements enclosed within braces — if a given conditional expression evaluates to true
, or to skip the statement or block of statements if the condition evaluates to false
. This is illustrated in Figure 3-1.
A simple example of an if
statement is:
if('A' == letter)
cout << "The first capital, alphabetically speaking.";
The condition to be tested appears in parentheses immediately following the keyword, if
, and this is followed by the statement to be executed when the condition is true
. Note the position of the semicolon. It goes after the statement following the if
and the condition between paren-theses; there shouldn’t be a semicolon after the condition in parentheses because the two lines essentially make up a single statement. You also can see how the line following the if
is indented to indicate that it is only executed when the if
condition returns the value true
. The indentation is not essential, but it helps you to recognize the relationship between the if
condition and the statement that depends on it. The output statement in the code fragment is executed only if the variable letter
has the value 'A'
.
You could extend this example to change the value of letter
if it contains the value 'A'
:
if('A' == letter)
{
cout << "The first capital, alphabetically speaking.";
letter = 'a';
}
The block of statements that is controlled by the if
statement is delimited by the curly braces. You execute the statements in the block only if the condition ('A' == letter)
evaluates to true
. Without the braces, only the first statement would be the subject of the if
, and the statement assigning the value 'a'
to letter
would always be executed. Note that there is a semicolon after each of the statements in the block, but not after the closing brace at the end of the block. There can be as many statements as you like within a block. Now, as a result of letter
having the value 'A'
, you change its value to 'a'
after outputting the same message as before. If the condition returns false
, neither of these statements is executed.
The statement to be executed when the condition in an if
statement is true can also be an if
. This arrangement is called a nested if
. The condition for the inner if
is only tested if the condition for the outer if
is true
. An if
that is nested inside another can also contain a nested if
. You can generally continue nesting if
s one inside the other like this for as long as you still know what you are doing.
The if
statement that you have been using so far executes a statement if the condition specified returns true
. Program execution then continues with the next statement in sequence. You also have a version of the if
that allows one statement to be executed if the condition returns true
, and a different statement to be executed if the condition returns false
. Execution then continues with the next statement in sequence. As you saw in Chapter 2, a block of statements can always replace a single statement, so this also applies to these if
s.
The if-else
combination provides a choice between two options. The general logic of the if-else
is shown in Figure 3-2.
The arrows in the diagram indicate the sequence in which statements are executed, depending on whether the if
condition returns true
or false
.
As you have seen, you can nest if
statements within if
statements. You can also nest if-else
statements within if
s, if
s within if-else
statements, and if-else
statements within if-else
statements. This provides considerable room for confusion, so let’s take a look at a few examples. The following is an example of an if-else
nested within an if
:
if('y' == coffee)
if('y' == donuts)
cout << "We have coffee and donuts.";
else
cout << "We have coffee, but not donuts";
The test for donuts
is executed only if the result of the test for coffee
returns true
, so the messages reflect the correct situation in each case; however, it is easy to get this confused. If you write much the same thing with incorrect indentation, you can be trapped into the wrong conclusion:
if('y' == coffee)
if('y' == donuts)
cout << "We have coffee and donuts.";
else // This else is indented incorrectly
cout << "We have no coffee..."; // Wrong!
The mistake is easy to see here, but with more complicated if
structures you need to keep in mind the rule about which if
owns which else
.
NOTE An else
always belongs to the nearest preceding if
that is not already spoken for by another else
.
Whenever things look a bit complicated, you can apply this rule to sort things out. When you are writing your own programs you can always use braces to make the situation clearer. It isn’t essential but it’s a good idea to write the last example as follows:
if('y' == coffee)
{
if('y' == donuts)
cout << "We have coffee and donuts.";
else
cout << "We have coffee, but not donuts";
}
and it should be absolutely clear. Now that you know the rules, understanding the case of an if
nested within an if-else
becomes easy:
if('y' == coffee)
{
if('y' == donuts)
cout << "We have coffee and donuts.";
}
else
if('y' == tea)
cout << "We have tea, but not coffee";
Here, the braces are essential. If you leave them out, the else
would belong to the second if
, which is looking out for donuts
. In this kind of situation, it is easy to forget to include the braces and create an error that may be hard to find. A program with this kind of error compiles fine and even produces the right results some of the time.
If you removed the braces in this example, you get the correct results only as long as coffee
and donuts
are both equal to 'y'
so that the if('y' == tea)
check wouldn’t be executed.
if-else
statements nested in if-else
statements can get very messy, even with just one level of nesting:
if('y' == coffee)
if('y' == donuts)
cout << "We have coffee and donuts.";
else
cout << "We have coffee, but not donuts";
else
if('y' == tea)
cout << "We have no coffee, but we have tea, and maybe donuts...";
else
cout << "No tea or coffee, but maybe donuts...";
The logic here doesn’t look quite so obvious, even with the correct indentation. No braces are necessary. The rule you saw earlier verifies that each else
belongs to the correct if
, but it would be a lot clearer if you included them:
if('y' == coffee)
{
if('y' == donuts)
cout << "We have coffee and donuts.";
else
cout << "We have coffee, but not donuts";
}
else
{
if('y' == tea)
cout << "We have no coffee, but we have tea, and maybe donuts...";
else
cout << "No tea or coffee, but maybe donuts...";
}
There are much better ways of dealing with this kind of logic in a program. If you put enough nested if
s together, you can almost guarantee a mistake somewhere. The next section will help to simplify things.
As you have just seen, using if
s where you have two or more related conditions can be a bit cumbersome. We have tried our if
fy talents on looking for coffee and donuts, but in practice you may want to test much more complex conditions.
Logical operators provide a neat and simple solution. Using logical operators, you can combine a series of comparisons into a single logical expression, so you end up needing just one if
, virtually regardless of the complexity of the set of conditions, as long as the decision ultimately boils down to a choice between two possibilities — true or false.
You have just three logical operators:
&& |
Logical AND |
|| |
Logical OR |
! |
Logical negation (NOT) |
You would use the AND operator, &&
, where you have two conditions that must both be true
for a true
result. You want to be rich and healthy. Thus the &&
operator produces the result true
when both operands have the value true
, and false
otherwise.
You could use the &&
operator when you are testing a character to determine whether it’s an uppercase letter; the value being tested must be both greater than or equal to 'A'
and less than or equal to 'Z'
. Both conditions must return true
for the value to be a capital letter.
NOTE As before, the conditions you combine using logical operators may return numerical values. Remember that a non-zero value converts to the value true
; zero converts to false
.
Taking the example of a value stored in a char
variable letter
, you could replace the test that uses two if
s with one that uses only a single if
and the &&
operator:
if((letter >= 'A') && (letter <= 'Z'))
cout << "This is a capital letter.";
The parentheses inside the expression that is the if
condition ensure that there is no doubt that the comparison operations are executed first, which makes the statement clearer. Here, the output statement is executed only if both of the conditions that are combined by the &&
operator are true
.
NOTE If the left operand for the &&
operator is false
, the right operand will not be evaluated. This becomes significant if the right operand is an expression that can change something, such as an expression involving the ++
or --
operator. For example, in the expression x>=5 && ++n<10, n
will not be incremented if x
is less than 5
.
The OR operator, ||
, applies when you have two conditions where you want a true
result if either or both of them are true
. For example, you might be considered credit worthy for a loan from the bank if your income was at least $100,000 a year, or if you had $1,000,000 in cash. This could be tested using the following if
:
if((income >= 100000.00) || (capital >= 1000000.00))
cout << "How much would you like to borrow, Sir (grovel, grovel)?";
The ingratiating response emerges when either or both of the conditions are true
. (A better response might be, “Why do you want to borrow?” It’s strange how banks lend you money only if you don’t need it.)
You only get a false
result with the ||
operator when both operands are false
.
NOTE If the left operand for the ||
operator is true
, the right operand will not be evaluated. For example, in the expression x>=5 || ++n<10
, the variable n
will not be incremented if x
is greater than or equal to 5.
The third logical operator, !
, takes one operand of type bool
and inverts its value. So, if the value of a variable test
is true, !test
is false
; and if test
is false, !test
is true
. To take the example of a simple expression, if x
has the value 10, the expression !(x > 5)
evaluates to false
, because x > 5
is true
.
You could also apply the !
operator in an expression that was a favorite of Charles Dickens’:
!(income > expenditure)
If this expression is true
, the result is misery, at least as soon as the bank starts bouncing your checks.
Finally, you can apply the !
operator to other basic data types. Suppose you have a variable, rate
, that is of type float
and has the value 3.2. For some reason, you might want to verify that the value of rate
is non-zero, in which case you could use the expression:
!(rate)
The value 3.2 is non-zero and thus converts to the bool
value true
, so the result of this expression is false
.
You can combine conditional expressions and logical operators to any degree that you feel comfortable with. For example, using just a single if
, you could construct a test for whether a variable contained a letter. Let’s write it as a working example:
// Ex3_03.cpp
// Testing for a letter using logical operators
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int main()
{
char letter {}; // Store input in here
cout << endl
<< "Enter a character: ";
cin >> letter;
if(((letter >= 'A') && (letter <= 'Z')) ||
((letter >= 'a') && (letter <= 'z'))) // Test for alphabetic
cout << endl
<< "You entered a letter." << endl;
else
cout << endl
<< "You didn't enter a letter." << endl;
return 0;
}
This starts out in the same way as Ex3_01.cpp
, by reading a character after a prompt for input. The interesting part of the program is in the if
statement condition. This consists of two logical expressions combined with the ||
(OR) operator, so that if either is true
, the condition returns true
and the following message is displayed:
You entered a letter.
If both logical expressions are false
, the else
statement is executed, which displays this message:
You didn't enter a letter.
Each logical expression combines a pair of comparisons with the operator &&
(AND), so both comparisons must return true
if the expression is to be true
. The first logical expression returns true
if the input is an uppercase letter, and the second returns true
if the input is a lowercase letter.
The conditional operator is sometimes called the ternary operator because it involves three operands. It is best understood by looking at an example. Suppose you have two variables, a
and b
, and you want to assign the maximum of a
and b
to a third variable, c
. You can do this with the following statement:
c = a > b ? a : b; // Set c to the maximum of a or b
The first operand for the conditional operator must be an expression that results in a bool
value, true
or false
, and in this case it is a > b
. If this expression returns true
, the second operand — in this case a
— is selected as the value resulting from the operation. If the first argument returns false
, the third operand — in this case b
— is selected as the value that results from the operation. Thus, the result of a > b ? a : b
is a
, if a
is greater than b
, and b
otherwise. This value is stored in c
by the assignment operation. The use of the conditional operator in this assignment statement is equivalent to the if
statement:
if(a > b)
c = a;
else
c = b;
The conditional operator can be written generally as:
condition ? expression1 : expression2
If condition
evaluates to true
, the result is the value of expression1
, and if it evaluates to false
, the result is the value of expression2
.
A common use of the conditional operator is to control output based on the result of an expression or the value of a variable. You can vary a message by selecting one text string or another, depending on the condition specified:
// Ex3_04.cpp
// The conditional operator selecting output
#include <iostream>
using std::cout;
using std::endl;
int main()
{
int nCakes {1}; // Count of number of cakes
cout << endl
<< "We have " << nCakes << " cake" << ((nCakes > 1) ? "s." : ".")
<< endl;
++nCakes;
cout << endl
<< "We have " << nCakes << " cake" << ((nCakes > 1) ? "s." : ".")
<< endl;
return 0;
}
The output from this program is:
We have 1 cake.
We have 2 cakes.
You first create the nCakes
variable with the initial value 1; then you have an output statement that shows the number of cakes. The part that uses the conditional operator simply tests the variable to determine whether you have a singular cake or several cakes:
((nCakes > 1) ? "s." : ".")
This expression evaluates to "s."
if nCakes
is greater than 1, or "."
otherwise. This enables you to use the same output statement for any number of cakes and get grammatically correct output. You make use of this in the example by incrementing the nCakes
variable and repeating the output statement.
There are many other situations where you can apply this sort of mechanism; selecting between "is"
and "are"
, for example.
The switch
statement enables you to select from multiple choices based on a set of fixed values for a given expression. It operates like a physical rotary switch in that you can select one of a number of choices. Some washing machines provide a means of choosing an operation for processing your laundry in this way. There are a number of possible positions for the switch, such as cotton, wool, synthetic fiber, and so on, and you can select one of them by turning the knob to point to the option you want.
In the switch
statement, the selection is determined by the value of an expression that you specify. You define the possible switch
positions by one or more case values, a particular one being selected if the value of the switch
expression is the same as the particular case value. There is one case value for each possible choice in the switch
, and all the case values must be distinct.
The general form of the switch
statement is:
switch(expression)
{
case c1:
// One or more statements for c1...
break;
case c2:
// One or more statements for c2...
break;
// More case statements...
default:
// Statements for default case...
break;
}
Both switch
and case
are keywords. c1, c2
, and so on are integer constants, or expressions that the compiler can evaluate to produce an integer constant; that is, not an expression that has to be executed at run time. The cases can be in any sequence and each case value must be unique to allow the compiler to differentiate between them. When expression
evaluates to one of the case values, the statements following that case statement are executed.
If the value of the switch
expression does not match any of the case values, the switch
automatically selects the default
case. You can omit the default
case, in which case the default is to do nothing.
The break
statement at the end of each case statement causes execution to transfer to the statement following the switch
block after a case statement executes. If you leave it out, statements for the next case will execute. The break
at the end of the default
case is not necessary, but including it is a good idea to provide for the possibility that you add a case
statement after the default
case later. Let’s see it working.
You can examine how the switch
statement works with the following example:
// Ex3_05.cpp
// Using the switch statement
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int main()
{
int choice {}; // Store selection value here
cout << endl
<< "Your electronic recipe book is at your service." << endl
<< "You can choose from the following delicious dishes: "
<< endl
<< endl << "1 Boiled eggs"
<< endl << "2 Fried eggs"
<< endl << "3 Scrambled eggs"
<< endl << "4 Coddled eggs"
<< endl << endl << "Enter your selection number: ";
cin >> choice;
switch(choice)
{
case 1: cout << endl << "Boil some eggs." << endl;
break;
case 2: cout << endl << "Fry some eggs." << endl;
break;
case 3: cout << endl << "Scramble some eggs." << endl;
break;
case 4: cout << endl << "Coddle some eggs." << endl;
break;
default: cout << endl <<"You entered a wrong number, try raw eggs."
<< endl;
break;
}
return 0;
}
The stream output statement displays the input options, and then a selection number is read into the variable choice
. The switch
statement has the condition specified as simply choice
, in parentheses, immediately following the keyword switch
. The possible options in the switch
are enclosed between braces and are each identified by a case label. A case label is the keyword case
, followed by the value of choice
that corresponds to this option, and terminated by a colon.
As you can see, the statements to be executed for a particular case
follow the colon at the end of the case label, and are terminated by a break
statement. The break
transfers execution to the statement after the switch
. The break
isn’t mandatory, but if you don’t include it, execution continues with the statements for the case that follows, which isn’t usually what you want. You can demonstrate this by removing the break
statements from this example and seeing what happens.
You can put the statements to be executed for a particular case between braces and sometimes this is necessary. For example, if you create a variable within a case statement, you must include braces. The following statement will result in an error message:
switch(choice)
{
case 1:
int count {2};
cout << "Boil " << count
<< " eggs." << endl;
// Code to do something with count...
break;
default:
cout << endl <<"You entered a wrong number, try raw eggs." << endl;
break;
}
Because it is possible that count
may not get initialized within the block for the switch
, you get the following error message:
error C2360: initialization of 'count' is skipped by 'default' label
You can fix this by writing it as:
switch(choice)
{
case 1:
{
int count {2};
cout << "Boil " << count
<< " eggs." << endl;
// Code to do something with count...
break;
}
default:
cout << endl <<"You entered a wrong number, try raw eggs." << endl;
break;
}
If the value of choice
doesn’t correspond with any of the case values, the statements following the default
label are executed. A default
case isn’t essential. In its absence, if the value of the test expression doesn’t correspond to any of the cases, the switch
is exited, and the program continues with the next statement after the switch
.
Each of the case expressions in a switch
statement must be constant expressions that can be evaluated at compile time and must evaluate to a unique integer value. The reason that no two case values can be the same is that the compiler would have no way of knowing which case statement should be executed for that particular value; however, different cases don’t need to have a unique action. Several cases can share the same action, as shown here:
// Ex3_06.cpp
// Multiple case actions
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int main()
{
char letter {};
cout << endl
<< "Enter a small letter: ";
cin >> letter;
switch(letter*(letter >= 'a' && letter <= 'z'))
{
case 'a': case 'e': case 'i': case 'o': case 'u':
cout << endl << "You entered a vowel.";
break;
case 0:
cout << endl << "That is not a small letter.";
break;
default: cout << endl << "You entered a consonant.";
}
cout << endl;
return 0;
}
In this example, you have a more complex expression in the switch
. If the character entered isn’t a lowercase letter, the expression
(letter >= 'a' && letter <= 'z')
results in the value false
; otherwise it evaluates to true
. Because letter
is multiplied by this expression, the value of the logical expression is converted to an integer — 0 if the logical expression is false
and 1 if it is true
. Thus, the switch
expression evaluates to 0 if a lowercase letter was not entered, and to the value of letter
if it was. The statements following the case label case 0
are executed whenever the character code stored in letter
does not represent a lowercase letter.
If a lowercase letter was entered, the switch
expression evaluates to the same value as letter
; so, for all values corresponding to vowels, the output statement following the sequence of case labels that have vowels as values is executed. The same statement executes for any vowel because when any of these case labels is chosen, the following statements are executed until the break
statement is reached. You can see that a single action can be taken for a number of different cases by writing the case labels, one after the other, before the statements to be executed. If a lowercase letter that is a consonant is entered, the default
case is executed.
The if
statement provides you with the flexibility to choose to execute one set of statements or another, depending on a specified condition, so the statement execution sequence is varied, depending on the values of the data in the program. The goto
statement, in contrast, is a blunt instrument. It enables you to branch to a specified program statement unconditionally. The statement to be branched to must be identified by a statement label, which is an identifier defined according to the same rules as a variable name. This is followed by a colon and placed before the statement requiring labeling. Here is an example of a labeled statement:
myLabel: cout << "myLabel branch has been activated" << endl;
This statement has the label myLabel
, and an unconditional branch to this statement would be written as follows:
goto myLabel;
Whenever possible, you should avoid using goto
s in your program. They tend to encourage convoluted code that can be extremely difficult to follow.
NOTE Because the goto
is theoretically unnecessary in a program — there’s always an alternative approach to using goto
— a significant cadre of programmers say you should never use it. I don’t subscribe to such an extreme view. It is a legal statement, after all, and there are occasions when it can be convenient, such as when you must exit from a deeply nested set of loops (you learn about loops in the next section). I do, however, recommend that you only use it where you can see an obvious advantage over other options that are available; otherwise, you may end up with convoluted, error-prone code that is hard to understand and even harder to maintain.
The capability to repeat a group of statements is fundamental to most applications. Without this, an organization would need to modify the payroll program every time an extra employee was hired, and you would need to reload your favorite game every time you wanted to play. So, let’s first understand how a loop works.
A loop executes a sequence of statements subject to a given condition. You can write a loop with the statements that you have met so far. You just need an if
and the dreaded goto
. Look at the following example:
// Ex3_07.cpp
// Creating a loop with an if and a goto
#include <iostream>
using std::cout;
using std::endl;
int main()
{
int i {1}, sum {};
const int max {10};
loop:
sum += i; // Add current value of i to sum
if(++i <= max)
goto loop; // Go back to loop until i = 11
cout << endl
<< "sum = " << sum << endl
<< "i = " << i << endl;
return 0;
}
This example accumulates the sum of integers from 1 to 10. The first time through the sequence of statements, i
has the initial value 1 and is added to sum
, which starts out as zero. In the if, i
is incremented to 2 and, as long as it is less than or equal to max
, the unconditional branch to loop
occurs, and the value of i
, now 2, is added to sum
. This continues with i
being incremented and added to sum
each time, until finally, when i
is incremented to 11 in the if
, the branch back is not executed. If you run this example, you get the following output:
sum = 55
i = 11
This shows quite clearly how the loop works; however, it uses a goto
and introduces a label into the program, both of which you should avoid, if possible. You can achieve the same thing, and more, with the for
statement, which is specifically for writing a loop.
You can rewrite the last example using what is known as a for
loop:
// Ex3_08.cpp
// Summing integers with a for loop
#include <iostream>
using std::cout;
using std::endl;
int main()
{
int i {1}, sum {};
const int max {10};
for(i = 1; i <= max; i++) // Loop specification
sum += i; // Loop statement
cout << endl
<< "sum = " << sum << endl
<< "i = " << i << endl;
return 0;
}
If you compile and run this, you get exactly the same output as the previous example, but the code is much simpler here. The conditions determining the operation of the loop appear in parentheses after the keyword for
. There are three expressions that appear within the parentheses, separated by semicolons:
i
to 1.true
, the loop continues to execute; when it is false
, the loop ends, and execution continues with the statement that follows the loop. In this case, the loop statement on the following line is executed as long as i
is less than or equal to max
.i
at each iteration. After this expression has been evaluated, the second expression is evaluated once more to see whether the loop should continue.This loop is not exactly the same as the version in Ex3_07.cpp
. You can demonstrate this if you set the value of max
to 0 in both programs and run them again; then, you will find that the value of sum
is 1 in Ex3_07.cpp
and 0 in Ex3_08.cpp
, and the value of i
differs too. The reason for this is that the if
version of the program always executes the loop at least once, because you don’t check the condition until the end. The for
loop doesn’t do this, because the condition is checked at the beginning.
The general form of the for
loop is:
for (initializing_expression ; test_expression ; increment_expression)
loop_statement;
Of course, loop_statement
can be a single statement, or a block of statements between braces. The sequence of events in executing the for
loop is shown in Figure 3-3.
The expressions controlling the for
loop are very flexible. You can even write two or more expressions, separated by the comma operator, for each control expression. This gives you a lot of scope in what you can do with a for
loop.
NOTE The more mathematically minded will know that you can sum the first n integers without using a loop. The sum of the integers from 1 to n is given by the expression ½n(n+1). Using this wouldn’t teach you much about loops though.
Most of the time, the expressions in a for
loop are used in a fairly standard way: the first to initialize one or more loop counters, the second to test if the loop should continue, and the third to increment or decrement one or more loop counters. You are not obliged to use these expressions in this way, however, and quite a few variations are possible.
The initialization expression can also include a definition for a loop variable. In the previous example, you could have written the loop to include the definition for the loop counter i
in the first control expression.
for(int i {1}; i <= max; i++) // Loop specification
sum += i; // Loop statement
Naturally, the original definition for i
would need to be omitted. If you make this change to the last example, you will find that it does not compile because the loop variable, i
, ceases to exist after the loop, so you cannot refer to it in the output statement. A loop has a scope which extends from the for
expression to the end of the body of the loop, which of course can be a block of code between braces, as well as just a single statement. The counter i
is now defined within the loop scope, so you cannot refer to it in the output statement, which is outside the scope of the loop. If you need to use the value in the counter after the loop has executed, you must define the counter variable outside the scope of the loop.
You can omit the initialization expression altogether from the loop. Because i
has the initial value 1, you can write the loop as:
for(; i <= max; i++) // Loop specification
sum += i; // Loop statement
You still need the semicolon that separates the initialization expression from the test condition. In fact, both semicolons must always be present, regardless of whether any or all of the control expressions are omitted. If you omit the first semicolon, the compiler is unable to decide which expression has been omitted, or even which semicolon is missing.
The loop statement can be empty. For example, you could place the loop statement in the for
loop from the previous example inside the increment expression; in this case the loop becomes:
for(; i <= max; sum += i++); // The whole loop
You still need the semicolon after the closing parentheses, to indicate that the loop statement is now empty. If you omit this, the statement immediately following this line is interpreted as the loop statement. Sometimes you’ll see the empty loop statement written on a separate line, like the following:
for(; i <= max; sum += i++) // The whole loop
;
You can use the comma operator to include multiple counters in a for
loop. You can see this in operation in the following program:
// Ex3_09.cpp
// Using multiple counters to show powers of 2
#include <iostream>
#include <iomanip>
using std::cout;
using std::endl;
using std::setw;
int main()
{
const long max {10L};
for(long i {}, power {1L}; i <= max; i++, power += power)
cout << endl
<< setw(10) << i << setw(10) << power; // Loop statement
cout << endl;
return 0;
}
You create and initialize two variables in the initialization section of the for
loop and increment each of them in the increment section. You can create as many variables as you want here, as long as they are of the same type.
You can also specify multiple conditions, separated by commas, in the second expression that represents the test part of the for
loop that determines whether it should continue, but this is not generally useful because only the right-most condition affects when the loop ends.
For each increment of i
, the value of the variable power
is doubled by adding it to itself. This produces the powers of two that we are looking for, and so the program produces the following output:
0 1
1 2
2 4
3 8
4 16
5 32
6 64
7 128
8 256
9 512
10 1024
You use the setw()
manipulator that you saw in the previous chapter to align the output nicely. You have included the iomanip
header file and added a using declaration for the name in the std
namespace, so you can use setw()
without qualifying the name.
If you omit the second control expression that specifies the test condition for a for
loop, the value is assumed to be true
, so the loop continues indefinitely unless you provide some other means of exiting from it. In fact, you can omit all the expressions in the parentheses after for
. This may not seem to be useful, but the reverse is true. You will often come across situations where you want to execute a loop a number of times, but you do not know in advance how many iterations you will need. Have a look at the following:
// Ex3_10.cpp
// Using an indefinite for loop to compute an average
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int main()
{
double value {}; // Value entered stored here
double sum {}; // Total of values accumulated here
int i {}; // Count of number of values
char indicator {'n'}; // Continue or not?
for(;;) // Indefinite loop
{
cout << endl
<< "Enter a value: ";
cin >> value; // Read a value
++i; // Increment count
sum += value; // Add current input to total
cout << endl
<< "Do you want to enter another value (enter y or n)? ";
cin >> indicator; // Read indicator
if (('n' == indicator) || ('N' == indicator))
break; // Exit from loop
}
cout << endl
<< "The average of the " << i
<< " values you entered is " << sum/i << "."
<< endl;
return 0;
}
This program computes the average of an arbitrary number of values. After each value is entered, you must indicate whether you want to enter another value, by entering a single character, y
or n
. Typical output from executing this example is:
Enter a value: 10
Do you want to enter another value (enter y or n)? y
Enter a value: 20
Do you want to enter another value (enter y or n)? y
Enter a value: 30
Do you want to enter another value (enter y or n)? n
The average of the 3 values you entered is 20.
After defining and initializing the variables that you’re going to use, you start a for
loop with no expressions specified, so there is no provision for ending it here. The block immediately following is the subject of the loop that is to be repeated.
The loop block performs three basic actions:
cin
to sum
.The first action within the block is to prompt you for input and then read a value into the variable value
. The value that you enter is added to sum
, and the count of the number of values, i
, is incremented. After accumulating the value in sum
, you are asked if you want to enter another value, and prompted to enter 'y'
or 'n'
if you have finished. The character that you enter is stored in indicator
, for testing against 'n'
or 'N'
in the if
statement. If neither is found, the loop continues; otherwise, a break
is executed. The effect of break
in a loop is similar to its effect in the context of the switch
statement. It exits the loop immediately by transferring control to the statement following the closing brace of the loop block.
Finally, you output the count of the number of values entered and their average, which is calculated by dividing sum
by i
. Of course, i
is promoted to type double
before the calculation, as you remember from the casting discussion in Chapter 2.
You write the continue
statement simply as:
continue;
Executing continue
within a loop starts the next loop iteration immediately, skipping over any statements remaining in the body of the loop. I can show how this works with the following code:
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int main()
{
int value {}, product {1};
for(int i {1}; i <= 10; i++)
{
cout << "Enter an integer: ";
cin >> value;
if(0 == value) // If value is zero
continue; // skip to next iteration
product *= value;
}
cout << "Product (ignoring zeros): " << product
<< endl;
return 0;
}
This loop reads 10 values with the intention of producing the product of the values entered. The if
checks each value, and if it is zero, the continue
statement skips to the next iteration. This is so that you don’t end up with a zero product if one of the values is zero. Obviously, if a zero value occurred on the last iteration, the loop would end. There are other ways of achieving the same result, but continue
provides a very useful capability, particularly with complex loops where you may need to skip to the end of the current iteration from various points in the loop.
The effect of the break
and continue
statements on the logic of a for
loop is illustrated in Figure 3-4.
Obviously, in a real situation, you would use the break
and continue
statements with some condition-testing logic to determine when the loop should be exited, or when an iteration of the loop should be skipped. You can also use the break
and continue
statements with the other kinds of loop, which I’ll discuss later on in this chapter, where they work in exactly the same way.
So far, you have only used integers to count loop iterations. You are in no way restricted as to what type of variable you use to count iterations. Look at the following example:
// Ex3_11.cpp
// Display ASCII codes for alphabetic characters
#include <iostream>
#include <iomanip>
using std::cout;
using std::endl;
using std::hex;
using std::dec;
using std::setw;
int main()
{
for(char capital {'A'}, small {'a'}; capital <= 'Z'; capital++, small++)
{
cout << endl
<< " " << capital // Output capital as a character
<< hex << setw(10) << static_cast<int>(capital) // and as hexadecimal
<< dec << setw(10) << static_cast<int>(capital) // and as decimal
<< " " << small // Output small as a character
<< hex << setw(10) << static_cast<int>(small) // and as hexadecimal
<< dec << setw(10) << static_cast<int>(small); // and as decimal
}
cout << endl;
return 0;
}
Here we have using
declarations for the names of some new manipulators that are used in the program to affect how the output is presented.
The loop in this example is controlled by the char
variable capital
, which you declare along with the variable small
in the initializing expression. You increment both variables in the third control expression for the loop, so that the value of capital
varies from 'A'
to 'Z'
, and the value of small
correspondingly varies from 'a'
to 'z'
.
The loop contains just one output statement spread over seven lines. The first line is:
cout << endl
This starts a new line on the screen.
The next three lines are:
<< " " << capital // Output capital as a character
<< hex << setw(10) << static_cast<int>(capital) // and as hexadecimal
<< dec << setw(10) << static_cast<int>(capital) // and as decimal
After outputting a tab character on each iteration, the value of capital
is displayed three times: as a character, as a hexadecimal value, and as a decimal value.
Inserting the hex
manipulator into the cout
stream causes subsequent integer data values to be displayed as hexadecimal values, rather than the default decimal representation, so the second output of capital
is as a hexadecimal representation of the character code.
You then insert the dec
manipulator into the stream to cause succeeding values to be output as decimals once more. By default, a variable of type char
is interpreted by the stream as a character, not a numerical value. You get the char
variable capital
to output as a numerical value by casting its value to type int
, using the static_cast<>()
operator that you saw in the previous chapter.
The value of small
is output in a similar way by the next three lines of the output statement:
<< " " << small // Output small as a character
<< hex << setw(10) << static_cast<int>(small) // and as hexadecimal
<< dec << setw(10) << static_cast<int>(small); // and as decimal
As a result, the program generates the following output:
A 41 65 a 61 97
B 42 66 b 62 98
C 43 67 c 63 99
D 44 68 d 64 100
E 45 69 e 65 101
F 46 70 f 66 102
G 47 71 g 67 103
H 48 72 h 68 104
I 49 73 i 69 105
J 4a 74 j 6a 106
K 4b 75 k 6b 107
L 4c 76 l 6c 108
M 4d 77 m 6d 109
N 4e 78 n 6e 110
O 4f 79 o 6f 111
P 50 80 p 70 112
Q 51 81 q 71 113
R 52 82 r 72 114
S 53 83 s 73 115
T 54 84 t 74 116
U 55 85 u 75 117
V 56 86 v 76 118
W 57 87 w 77 119
X 58 88 x 78 120
Y 59 89 y 79 121
Z 5a 90 z 7a 122
You can use a floating-point value as a loop counter. Here’s an example of a for
loop with this kind of counter:
double a {0.3}, b {2.5};
for(double x {}; x <= 2.0; x += 0.25)
cout << "
x = " << x
<< " a*x + b = " << a*x + b;
This code fragment calculates the value of a*x + b
for values of x
from 0.0 to 2.0, in steps of 0.25; however, you need to take care when using a floating-point counter in a loop. Many decimal values cannot be represented exactly in binary floating-point form, so discrepancies can build up with cumulative values. This means that you should not code a for
loop such that ending the loop depends on a floating-point loop counter reaching a precise value. For example, the following poorly-designed loop never ends:
for(double x {}; x != 1.0; x += 0.1)
cout << x << endl;
The intention with this loop is to output the value of x
as it varies from 0.0 to 1.0; however, 0.1 has no exact representation as a binary floating-point value, so the value of x
is never exactly 1. Thus, the second loop control expression is always false
, and the loop continues indefinitely.
NOTE It’s easy to see why some decimal fractional values cannot be represented exactly as binary values. In a binary fraction, the digits to the right of the binary point are equivalent to the decimal fractions 1/2, 1/4, 1/8, 1/16, and so on. Thus any binary fraction as a decimal value is the sum of one or more of these decimal fractions. Decimal fractions such as 1/3 or 1/10 that have a denominator that is odd or has an odd factor can never by represented exactly by a sum of fractions that all have an even denominator.
A second kind of loop in C++ is the while
loop. Where the for
loop is primarily used to repeat a statement or a block for a prescribed number of iterations, the while
loop is used to execute a statement or block of statements as long as a specified condition is true
. The general form is:
while(condition)
loop_statement;
Here loop_statement
is executed repeatedly, as long as the condition
expression has the value true
. After the condition becomes false
, the program continues with the statement following the loop. As always, a block of statements between braces could replace loop_statement
.
The logic of the while
loop is shown in Figure 3-5.
You could rewrite the earlier example that computes averages (Ex3_10.cpp
) to use the while
loop.
// Ex3_12.cpp
// Using a while loop to compute an average
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int main()
{
double value {}; // Value entered stored here
double sum {}; // Total of values accumulated here
int i {}; // Count of number of values
char indicator {'y'}; // Continue or not?
while('y' == indicator ) // Loop as long as y is entered
{
cout << endl
<< "Enter a value: ";
cin >> value; // Read a value
++i; // Increment count
sum += value; // Add current input to total
cout << endl
<< "Do you want to enter another value (enter y or n)? ";
cin >> indicator; // Read indicator
}
cout << endl
<< "The average of the " << i
<< " values you entered is " << sum/i << "."
<< endl;
return 0;
}
For the same input, this version of the program produces the same output as before. One statement has been updated, and another has been added — they are highlighted in the code. The for
loop statement has been replaced by the while
statement, and the test for indicator
in the if
has been deleted, as this function is performed by the while
condition. You have to initialize indicator
with 'y'
, in place of the 'n'
which appeared previously — otherwise the while
loop terminates immediately. As long as the condition in the while
returns true
, the loop continues.
You can use any expression resulting in true
or false
as a while
loop condition. The example would be better if the loop condition was extended to allow 'Y'
to be entered to continue the loop, as well as 'y'
. You could modify the while
as follows to do the trick:
while(('y' == indicator) || ('Y' == indicator))
You can also create a while
loop that potentially executes indefinitely, by using a condition that is always true
. This can be written as follows:
while(true)
{
...
}
You could also write the loop control expression as the integer value 1, which would be converted to the bool
value true
. Naturally, the same requirement applies here as in the case of the indefinite for
loop: namely, you must provide some way of exiting the loop within the loop block. You’ll see other ways to use the while
loop in Chapter 4.
The do-while
loop is similar to the while
loop in that the loop continues as long as the specified loop condition remains true
. The main difference is that the condition is checked at the end of the loop — which contrasts with the while
loop and the for
loop, where the condition is checked at the beginning. Consequently, the do-while
loop statement is always executed at least once. The general form of the do-while
loop is:
do
{
loop_statements;
}while(condition);
The logic of this form of loop is shown in Figure 3-6.
You could replace the while
loop in the last version of the program to calculate an average with a do-while
loop:
do
{
cout << endl
<< "Enter a value: ";
cin >> value; // Read a value
++i; // Increment count
sum += value; // Add current input to total
cout << "Do you want to enter another value (enter y or n)?";
cin >> indicator; // Read indicator
} while(('y' == indicator) || ('Y' == indicator));
There’s little difference between the two versions of the loop, except that this version doesn’t depend on the initial value set in indicator
for correct operation. As long as you want to enter at least one value, which is not unreasonable for the calculation in question, this version is preferable.
I am introducing this loop here so all the loops that you have available appear together. The range-based for
loop enables you to iterate over each of the items in a collection of items in a very simple way. You have not met any collections to which you can apply this loop so far, but you will meet arrays in the next chapter where you can use this loop. I’ll discuss how the range-based for
loop works in more detail then, and you will learn about other kinds of collections that you can apply the range-based for
loop to in Chapter 10.
You can nest one loop inside another. The usual application for this will become apparent in Chapter 4 — it’s typically applied to repeating actions at different levels of classification. An example might be calculating the total marks for each student in a class, and then repeating the process for each class in a school.
You can see the effects of nesting one loop inside another by calculating the values of a simple formula. A factorial of an integer is the product of all the integers from 1 to the integer in question; the factorial of 3, for example, is 1 times 2 times 3, which is 6. The following program computes the factorial of integers that you enter (until you’ve had enough):
// Ex3_13.cpp
// Demonstrating nested loops to compute factorials
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int main()
{
char indicator {'n'};
long value {}, factorial {};
do
{
cout << endl << "Enter an integer value: ";
cin >> value;
factorial = 1L;
for(long i {2L}; i <= value; i++)
factorial *= i;
cout << "Factorial " << value << " is " << factorial;
cout << endl << "Do you want to enter another value (y or n)? ";
cin >> indicator;
} while(('y' == indicator) || ('Y' == indicator));
return 0;
}
If you compile and execute this example, the typical output produced is:
Enter an integer value: 5
Factorial 5 is 120
Do you want to enter another value (y or n)? y
Enter an integer value: 10
Factorial 10 is 3628800
Do you want to enter another value (y or n)? y
Enter an integer value: 13
Factorial 13 is 1932053504
Do you want to enter another value (y or n)? y
Enter an integer value: 22
Factorial 22 is -522715136
Do you want to enter another value (y or n)? n
Factorial values grow very fast. In fact, 12 is the largest input value for which this example produces a correct result. The factorial of 13 is actually 6,227,020,800, not 1,932,053,504 as the program tells you. If you run it with even larger input values, leading digits are lost in the result stored in the variable factorial
, and you may well get negative values for the factorial, as you do when you ask for the factorial of 22.
This situation doesn’t cause any error messages, so it is of paramount importance that you are sure that the values you’re dealing with in a program can be contained in the permitted range of the type of variable you’re using. You also need to consider the effects of incorrect input values. Errors of this kind, which occur silently, can be very hard to find.
The outer of the two nested loops is the do-while
loop, which controls when the program ends. As long as you keep entering y
or Y
at the prompt, the program continues to calculate factorial values. The factorial for the integer entered is calculated in the inner for
loop. This is executed value-1
times, to multiply the variable factorial
(with an initial value of 1) with successive integers from 2 to value
.
Nested loops can be a little confusing, so let’s try another example. This program generates a multiplication table of a given size:
// Ex3_14.cpp
// Using nested loops to generate a multiplication table
#include <iostream>
#include <iomanip>
using std::cout;
using std::endl;
using std::setw;
int main()
{
const int size {12}; // Size of table
int i {}, j {}; // Loop counters
cout << endl // Output table title
<< size << " by " << size << " Multiplication Table" << endl << endl;
cout << endl << " |";
for(i = 1; i <= size; i++) // Loop to output column headings
cout << setw(3) << i << " ";
cout << endl; // Newline for underlines
for(i = 0; i <= size; i++)
cout << "_____"; // Underline each heading
for(i = 1; i <= size; i++) // Outer loop for rows
{
cout << endl
<< setw(3) << i << " |"; // Output row label
for(j = 1; j <= size; j++) // Inner loop for the rest of the row
cout << setw(3) << i*j << " "; // End of inner loop
} // End of outer loop
cout << endl;
return 0;
}
The output from this example is:
12 by 12 Multiplication Table
| 1 2 3 4 5 6 7 8 9 10 11 12
_________________________________________________________________
1 | 1 2 3 4 5 6 7 8 9 10 11 12
2 | 2 4 6 8 10 12 14 16 18 20 22 24
3 | 3 6 9 12 15 18 21 24 27 30 33 36
4 | 4 8 12 16 20 24 28 32 36 40 44 48
5 | 5 10 15 20 25 30 35 40 45 50 55 60
6 | 6 12 18 24 30 36 42 48 54 60 66 72
7 | 7 14 21 28 35 42 49 56 63 70 77 84
8 | 8 16 24 32 40 48 56 64 72 80 88 96
9 | 9 18 27 36 45 54 63 72 81 90 99 108
10 | 10 20 30 40 50 60 70 80 90 100 110 120
11 | 11 22 33 44 55 66 77 88 99 110 121 132
12 | 12 24 36 48 60 72 84 96 108 120 132 144
The table title is produced by the first output statement. The next output statement, combined with the loop following it, generates the column headings. Each column is five characters wide, so the heading value is displayed in a field width of three, specified by the setw(3)
manipulator, followed by two spaces. The output statement preceding the loop outputs four spaces and a vertical bar above the first column, which contains the row headings. A series of underline characters is then displayed beneath the column headings.
The nested loop generates the main table contents. The outer loop repeats once for each row, so i
is the row number. The output statement
cout << endl
<< setw(3) << i << " |"; // Output row label
goes to a new line for the start of a row, and then outputs the row heading given by the value of i
in a field width of three, followed by a space and a vertical bar.
A row of values is generated by the inner loop:
for(j = 1; j <= size; j++) // Inner loop for the rest of the row
cout << setw(3) << i*j << " "; // End of inner loop
This loop outputs values i*j
, corresponding to the product of the current row value i
, and each of the column values in turn by varying j
from 1 to size
. So, for each iteration of the outer loop, the inner loop executes size
iterations. The values are positioned in the same way as the column headings. When the outer loop ends, a new line is printed and the return
is executed to end the program.
In this chapter, you learned all the essential mechanisms for making decisions in C++ programs. The ability to compare values and change the course of program execution is what differentiates a computer from a simple calculator. You need to be comfortable with all of the decision-making statements I have discussed because they are all used very frequently. You have also gone through all the facilities for repeating a group of statements. Loops are a fundamental programming technique that you will need to use in every program of consequence that you write. You will find you use the for
loop most often, closely followed by the while
loop.
cin
and then sums them, stopping when 0 has been entered. Construct three versions of this program, using the while, do-while
, and for
loops.switch
statement to count them.&
and |
) and a set of flags, devise a method to allow a single integer variable to be set to any combination of the two attributes. Write a program that sets such a variable and then decodes it, printing out its setting, for all possible combinations of the attributes.TOPIC | CONCEPT |
---|---|
Relational operators | The relational operators allow you to combine logical values or expressions that result in a logical value. They yield a bool value — true or false — as the result that you can use in an if statement. |
Decisions based on numerical values | You can make decisions based on conditions that return non-bool values. Any non-zero value is cast to true when a condition is tested; zero casts to false . |
Statements for decision-making | The if statement provides the primary decision-making capability in C++. Further flexibility is provided by the switch statement and the conditional operator. |
Loop statements | There are four basic methods for repeating a block of statements: the for loop, the while loop, the do-while loop, and the range-based for loop. The for loop allows the loop to repeat a given number of times. The while loop allows a loop to continue as long as a specified condition returns true . The do-while executes the loop at least once and allows continuation of the loop as long as a specified condition returns true . The range-based for loop iterates over the items in a collection. |
Nested loops | Any kind of loop may be nested within any other kind of loop. |
The continue keyword | The keyword continue allows you to skip the remainder of the current iteration in a loop and go straight to the next iteration. |
The break keyword | The keyword break provides an immediate exit from a loop. It also provides an exit from a switch at the end of statements in a case . |
18.118.218.84