Controlling Program Flow - Getting Started with C++ Programming - C++ For Dummies (2014)

C++ For Dummies (2014)

Part I

Getting Started with C++ Programming

Chapter 5

Controlling Program Flow

In This Chapter

arrow Controlling the flow through the program

arrow Executing a group of statements repetitively

arrow Avoiding infinite loops

The simple programs that appear in Chapters 1 through 4 process a fixed number of inputs, output the result of that calculation, and quit. However, these programs lack any form of flow control. They cannot make tests of any sort. Computer programs are all about making decisions. If the user presses a key, the computer responds to the command.

For example, if the user presses Ctrl+C, the computer copies the currently selected area to the Clipboard. If the user moves the mouse, the pointer moves on the screen. If the user clicks the right mouse button with the Windows key depressed, the computer crashes. The list goes on and on. Programs that don’t make decisions are necessarily pretty boring.

Flow-control commands allow the program to decide what action to take based on the results of the C++ logical operations performed (see Chapter 4). There are basically three types of flow-control statements: the branch, the loop, and the switch.

Controlling Program Flow with the Branch Commands

The simplest form of flow control is the branch statement. This instruction allows the program to decide which of two paths to take through C++ instructions, based on the results of a logical expression (see Chapter 4 for a description of logical expressions).

In C++, the branch statement is implemented using the if statement:

if (m > n)
{
// Path 1
// ...instructions to be executed if
// m is greater than n
}
else
{
// Path 2
// ...instructions to be executed if not
}

First, the logical expression m > n is evaluated. If the result of the expression is true, control passes down the path marked Path 1 in the previous snippet. If the expression is false, control passes to Path 2. The else clause is optional. If it is not present, C++ acts as if it is present but empty.

image Actually, the braces are not required if there’s only one statement to execute as part of the if. Originally, braces were only used if there were two or more statements that you wanted to treat as one. However, people quickly realized that it was cleaner and less error prone if you used braces every time, no matter how many statements there are.

The following program demonstrates the if statement (note all the lovely braces):

// BranchDemo - input two numbers. Go down one path of the
// program if the first argument is greater
// than the first or the other path if not
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

int main(int nNumberofArgs, char* pszArgs[])
{
// input the first argument...
int nArg1;
cout << "Enter arg1: ";
cin >> nArg1;

// ...and the second
int nArg2;
cout << "Enter arg2: ";
cin >> nArg2;

// now decide what to do:
if (nArg1 > nArg2)
{
cout<< "Argument 1 is greater than argument 2"
<< endl;
}
else
{
cout<< "Argument 1 is not greater than argument 2"
<< endl;
}

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

Here the program reads two integers from the keyboard and compares them. If nArg1 is greater than nArg2, control flows to the output statement cout << “Argument 1 is greater than argument 2”. If nArg1 is not greater than nArg2, control flows to the else clause where the statement cout << “Argument 1 is not greater than argument 2\n” is executed. Here’s what that operation looks like:

Enter arg1: 5
Enter arg2: 6
Argument 1 is not greater than argument 2
Press Enter to continue...

image Notice how the instructions within the if blocks are indented slightly. This is strictly for human consumption because C++ ignores whitespace (spaces, tabs, and newlines). It may seem trivial, but a clear coding style increases the readability of your C++ program. The Code::Blocks editor can enforce this style or any one of several other coding styles for you. Select Settings⇒Editor, then click on the Source Formatter selection from the scrolled list on the left. I use the ANSI bracket style with four spaces per indent.

Executing Loops in a Program

Branch statements allow you to direct the flow of a program’s execution down one path or another. This is a big improvement but still not enough to write full-strength programs.

Consider the problem of updating the computer display. The typical PC must update well over a thousand pixels for each row as it paints an image from left to right. It repeats this process for each of the thousand or so rows on the display. It does this by executing the same small number of instructions, millions of times — once for each pixel.

Looping while a condition is true

The simplest form of looping statement is the while loop. Here’s what the while loop looks like:

while(condition)
{
// ...repeatedly executed as long as condition is true
}

The condition is tested. This condition could be if var > 10 or if var1 == var2 or any other expression you might think of as long as it returns a value of true or false. If the condition is true, the statements within the braces are executed. Upon encountering the closed brace, C++ returns control to the beginning, and the process starts over. If the condition is false, control passes to the first statement after the closed brace. The effect is that the C++ code within the braces is executed repeatedly as long as the condition is true. (Kind of reminds me of how I get to walk around the yard with my dog until she … well, until we’re done.)

If the condition were true the first time, what would make it be false in the future? Consider the following example program:

// WhileDemo - input a loop count. Loop while
// outputting astring arg number of times.
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

int main(int nNumberofArgs, char* pszArgs[])
{
// input the loop count
int nLoopCount;
cout << "Enter loop count: ";
cin >> nLoopCount;

// now loop that many times
while (nLoopCount > 0)
{
nLoopCount = nLoopCount - 1;
cout << "Only " << nLoopCount
<< " loops to go" << endl;
}

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

WhileDemo begins by retrieving a loop count from the user, which it stores in the variable nLoopCount. The program then executes a while loop. The while first tests nLoopCount. If nLoopCount is greater than 0, the program enters the body of the loop (the body is the code between the braces), where it decrements nLoopCount by 1 and outputs the result to the display. The program then returns to the top of the loop to test whether nLoopCount is still positive.

When executed, the program WhileDemo outputs the results shown in this next snippet. Here I entered a loop count of 5. The result is that the program loops five times, each time outputting a countdown:

Enter loop count: 5
Only 4 loops to go
Only 3 loops to go
Only 2 loops to go
Only 1 loops to go
Only 0 loops to go
Press Enter to continue...

If the user enters a negative loop count, the program skips the loop entirely. That’s because the specified condition is never true, so control never enters the loop. In addition, if the user enters a very large number, the program loops for a long time before completing.

A separate, less frequently used version of the while loop known as the do … while appears identical except the condition isn’t tested until the bottom of the loop:

do
{
// ...the inside of the loop
} while (condition);

Because the condition isn’t tested until the end, the body of the do … while is always executed at least once.

image The condition is checked only at the beginning of the while loop or at the end of the do … while loop. Even if the condition ceases to be true at some time during the execution of the loop, control does not exit the loop until the condition is retested.

Using the autoincrement/autodecrement feature

Programmers very often use the autoincrement ++ or the autodecrement - - operators with loops that count something. Notice from the following snippet extracted from the WhileDemo example that the program decrements the loop count by using assignment and subtraction statements, like this:

// now loop that many times
while (nLoopCount > 0)
{
nLoopCount = nLoopCount - 1;
cout << "Only " << nLoopCount
<< " loops to go" << endl;
}

A more compact version uses the autodecrement feature, which does what you may well imagine:

while (nLoopCount > 0)
{
nLoopCount--;
cout << "Only " << nLoopCount
<< " loops to go" << endl;
}

The logic in this version is the same as in the original. The only difference is the way that nLoopCount is decremented.

Because the autodecrement both decrements its argument and returns its value, the decrement operation can be combined with the while loop. In particular, the following version is the smallest loop yet:

while (nLoopCount-- > 0)
{
cout << "Only " << nLoopCount
<< " loops to go" << endl;
}

Believe it or not, nLoopcount-- > 0 is the version that most C++ programmers would use. It’s not that C++ programmers like being cute (although they do). In fact, the more compact version (which embeds the autoincrement or autodecrement feature in the logical comparison) is easier to read, especially as you gain experience.

image Both nLoopCount-- and --nLoopCount expressions decrement nLoopCount. The former expression, however, returns the value of nLoopCount before being decremented; the latter expression does so after being decremented.

How often should the autodecrement version of WhileDemo execute when the user enters a loop count of 1? If you use the pre-decrement version, the value of --nLoopCount is 0, and the body of the loop is never entered. With the post-decrement version, the value of nLoopCount is 1, and control enters the loop.

Beware thinking that the version of the program with the autodecrement command executes faster than the simple “- 1” version (since it contains fewer statements). It probably executes exactly the same. Modern compilers are good at getting the number of machine-language instructions down to a minimum, no matter which of the decrement instructions shown here you actually use.

Using the for loop

The most common form of loop is the for loop. The for loop is preferred over the more basic while loop because it’s generally easier to read (there’s really no other advantage).

The for loop has the following format:

for (initialization; conditional; increment)
{
// ...body of the loop
}

The for loop is equivalent to the following while loop:

{
initialization;
while(conditional)
{
{
// ...body of the loop
}
increment;
}
}

Execution of the for loop begins with the initialization clause, which got its name because it’s normally where counting variables are initialized. The initialization clause is executed only once, when the for loop is first encountered.

Execution continues with the conditional clause. This clause works just like the while loop: As long as the conditional clause is true, the for loop continues to execute.

After the code in the body of the loop finishes executing, control passes to the increment clause before returning to check the conditional clause — thereby repeating the process. The increment clause normally houses the autoincrement or autodecrement statements used to update the counting variables.

The for loop is best understood by example. The following ForDemo1 program is nothing more than the WhileDemo converted to use the for loop construct:

// ForDemo1 - input a loop count. Loop while
// outputting astring arg number of times.
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

int main(int nNumberofArgs, char* pszArgs[])
{
// input the loop count
int nLoopCount;
cout << "Enter loop count: ";
cin >> nLoopCount;

// count up to the loop count limit
for (; nLoopCount > 0;)
{
nLoopCount = nLoopCount - 1;
cout << "Only " << nLoopCount
<< " loops to go" << endl;
}

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

The program reads a value from the keyboard into the variable nloopCount. The for starts out comparing nloopCount to 0. Control passes into the for loop if nloopCount is greater than 0. Once inside the for loop, the program decrements nloopCount and displays the result. That done, the program returns to the for loop control. Control skips to the next line after the for loop as soon as nloopCount has been decremented to 0.

image All three sections of a for loop may be empty. An empty initialization or increment section does nothing. An empty comparison section is treated like a comparison that returns true.

This for loop has two small problems. First, it’s destructive — not in the sense of what my puppy does to a slipper, but in the sense that it changes the value of nloopCount, “destroying” the original value. Second, this for loop counts backward from large values down to smaller values. These two problems are addressed by adding a dedicated counting variable to the for loop. Here’s what it looks like:

// ForDemo2 - input a loop count. Loop while
// outputting astring arg number of times.
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

int main(int nNumberofArgs, char* pszArgs[])
{
// input the loop count
int nLoopCount;
cout << "Enter loop count: ";
cin >> nLoopCount;

// count up to the loop count limit
for (int i = 1; i <= nLoopCount; i++)
{
cout << "We've finished " << i
<< " loops" << endl;
}

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

This modified version of ForDemo loops the same as it did before. Instead of modifying the value of nLoopCount, however, this ForDemo2 version uses a new counter variable.

This for loop declares a counter variable i and initializes it to 0. It then compares this counter variable to nLoopCount. If i is less than nLoopCount, control passes to the output statement within the body of the for loop. Once the body has completed executing, control passes to the increment clause where i is incremented and compared to nLoopCount again, and so it goes.

The following shows example output from the program:

Enter loop count: 5
We've finished 1 loops
We've finished 2 loops
We've finished 3 loops
We've finished 4 loops
We've finished 5 loops
Press Enter to continue...

image When declared within the initialization portion of the for loop, the index variable is known only within the for loop itself. Nerdy C++ programmers say that the scope of the variable is limited to the for loop. In the ForDemo2 example just given, the variable i is not accessible from the return statement because that statement is not within the loop.

Avoiding the dreaded infinite loop

An infinite loop is an execution path that continues forever. An infinite loop occurs any time the condition that would otherwise terminate the loop can’t occur — usually the result of a coding error.

Consider the following minor variation of the earlier loop:

while (nLoopCount > 0)
{
cout << "Only " << nLoopCount
<< " loops to go" << endl;
}

The programmer forgot to decrement the variable nLoopCount. The result is a loop counter that never changes. The test condition is either always false or always true. The program executes in a never-ending (infinite) loop.

image I realize that nothing’s infinite. Eventually the power will fail, the computer will break, Microsoft will go bankrupt, and dogs will sleep with cats… . Either the loop will stop executing, or you won’t care anymore. But an infinite loop will continue to execute until something outside the control of the program makes it stop.

You can create an infinite loop in many more ways than shown here, most of which are a lot more difficult to spot than this was.

For each his own

image New for 2011 is a form of the for statement commonly known as the “for each” or the “range-based for loop.” In this for loop, the counting variable is followed by a list of values, as shown in the following demo program:

image The ForEach does not work on the Macintosh version of Code::Blocks as of this writing.

// ForEachDemo - C++ includes a form of the "for each"
// which iterates through each member of
// a list
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

int main(int nNumberofArgs, char* pszArgs[])
{
cout << "The primes less than 20 are:" << endl;
for(int n : {1, 2, 3, 5, 7, 11, 13, 17, 19})
{
cout << n << ", ";
}
cout << endl;

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

The values within the braces are known as a list. The variable n is assigned each value in the list: 1 the first time through the loop, then the value 2, then 3, then 5, and so on. The for loop terminates when the list is exhausted. The output of this program appears as follows:

The primes less than 20 are:
1, 2, 3, 5, 7, 11, 13, 17, 19,
Press Enter to continue...

I touch on initializer lists again in Chapter 7 and discuss in detail in Chapter 26.

image The range-based loop example shown here does not work on the Macintosh version of Code::Blocks/gcc. The array-based examples in Chapter 7 do work correctly on the Mac, however.

Applying special loop controls

C++ defines two special flow-control commands known as break and continue. Sometimes the condition for terminating a loop occurs at neither the beginning nor the end of the loop, but in the middle. Consider a program that accumulates numbers of values entered by the user. The loop terminates when the user enters a negative number.

The challenge with this problem is that the program can’t exit the loop until the user has entered a value but must exit before the value is added to the sum.

For these cases, C++ defines the break command. When encountered, the break causes control to exit the current loop immediately. Control passes from the break statement to the statement immediately following the closed brace at the end of the loop.

The format of the break commands is as follows:

while(condition) // break works equally well in for loop
{
if (some other condition)
{
break; // exit the loop
}
} // control passes here when the
// program encounters the break

Armed with this new break command, my solution to the accumulator problem appears as the program BreakDemo:

// BreakDemo - input a series of numbers.
// Continue to accumulate the sum
// of these numbers until the user
// enters a negative number.
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

int main(int nNumberofArgs, char* pszArgs[])
{
// input the loop count
int accumulator = 0;
cout << "This program sums values from the user\n"
<< "Terminate by entering a negative number"
<< endl;

// loop "forever"
for(;;)
{
// fetch another number
int nValue = 0;
cout << "Enter next number: ";
cin >> nValue;

// if it's negative...
if (nValue < 0)
{
// ...then exit
break;
}

// ...otherwise add the number to the accumulator
accumulator += nValue;
}

// now that we've exited the loop
// output the accumulated result
cout << "\nThe total is "
<< accumulator
<< endl;

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

After explaining the rules to the user (entering a negative number to terminate and so on), the program enters what looks like an infinite for loop. Once within the loop, BreakDemo retrieves a number from the keyboard. Only after the program has read the number can it test to see whether that number matches the exit criteria. If the input number is negative, control passes to the break, causing the program to exit the loop. If the input number is not negative, control skips over the break command to the expression that sums the new value into the accumulator. After the program exits the loop, it outputs the accumulated value and then exits.

image When performing an operation on a variable repeatedly in a loop, make sure that the variable is initialized properly before entering the loop. In this case, the program zeros accumulator before entering the loop where nValue is added to it.

The result of an example run appears as follows:

This program sums values from the user
Terminate by entering a negative number
Enter next number: 1
Enter next number: 2
Enter next number: 3
Enter next number: -1

The total is 6
Press Enter to continue...

The similar continue command is used less frequently. When the program encounters the continue command, it immediately moves back to the top of the loop. The rest of the statements in the loop are ignored for the current iteration.

The following example snippet ignores negative numbers that the user might input. Only a 0 terminates this version (the complete program appears on the website as ContinueDemo):

while(true)// this while() has the same effect as for(;;)
{
// input a value
cout << "Input a value:";
cin >> nValue;

// if the value is negative...
if (nValue < 0)
{
// ...output an error message...
cout << "Negative numbers are not allowed\n";

// ...and go back to the top of the loop
continue;
}

// ...continue to process input like normal
}

Nesting Control Commands

Return to our PC-screen-repaint problem. Surely it must need a loop structure of some type to write each pixel from left to right on a single line. (Do Middle Eastern terminals scan from right to left? I have no idea.) What about repeatedly repainting each scan line from top to bottom? (Do PC screens in Australia scan from bottom to top?) For this particular task, you need to include a left-to-right scan loop within the top-to-bottom scan loop.

A loop command within another loop is known as a nested loop. As an example, I have modified the BreakDemo program to accumulate any number of sequences. In this NestedDemo program, the inner loop sums numbers entered from the keyboard until the user enters a negative number. The outer loop continues accumulating sequences until the sum is 0. Here’s what it looks like:

// NestedDemo - input a series of numbers.
// Continue to accumulate the sum
// of these numbers until the user
// enters a 0. Repeat the process
// until the sum is 0.
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

int main(int nNumberofArgs, char* pszArgs[])
{
// the outer loop
cout << "This program sums multiple series\n"
<< "of numbers. Terminate each sequence\n"
<< "by entering a negative number.\n"
<< "Terminate the series by entering two\n"
<< "negative numbers in a row\n";

// continue to accumulate sequences
int accumulator;
for(;;)
{
// start entering the next sequence
// of numbers
accumulator = 0;
cout << "Start the next sequence\n";

// loop forever
for(;;)
{
// fetch another number
int nValue = 0;
cout << "Enter next number: ";
cin >> nValue;

// if it's negative...
if (nValue < 0)
{
// ...then exit
break;
}

// ...otherwise add the number to the
// accumulator
accumulator += nValue;
}

// exit the loop if the total accumulated is 0
if (accumulator == 0)
{
break;
}

// output the accumulated result and start over
cout << "The total for this sequence is "
<< accumulator << endl << endl;
}

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

Notice the inner for loop looks like the earlier accumulator example. Immediately after that loop, however, is an added test. If accumulator is equal to 0, the program executes a break statement that exits the outer loop. Otherwise, the program outputs the accumulated value and starts over.

Switching to a Different Subject?

One last control statement is useful in a limited number of cases. The switch statement resembles a compound if statement by including a number of different possibilities rather than a single test:

switch(expression)
{
case c1:
// go here if the expression == c1
break;
case c2:
// go here if expression == c2
break;
default:
// go here if there is no match
}

The value of expression must be an integer (int, long, or char). The case values must be constants.

image As of the ’14 standard, they can also be a constant expression. I don't describe constant expressions until Chapter 10.

When the switch statement is encountered, the expression is evaluated and compared to the various case constants. Control branches to the case that matches. If none of the cases matches, control passes to the default clause.

Consider the following example code snippet:

int choice;
cout << "Enter a 1, 2 or 3:";
cin >> choice;

switch(choice)
{
case 1:
// do "1" processing
break;

case 2:
// do "2" processing
break;

case 3:
// do "3" processing
break;

default:
cout << "You didn't enter a 1, 2 or 3\n";
}

Once again, the switch statement has an equivalent; in this case, multiple if statements. However, when there are more than two or three cases, the switch structure is easier to understand.

image The break statements are necessary to exit the switch command. Without the break statements, control falls through from one case to the next. (Look out below!)