Courses/CS 2114/Lab Manual/Repetition

From A-State Computer Science Wiki
Jump to: navigation, search

Repetition

Introduction

This lab will illustrate looping and the use of the C++ while and for control structures.

Topics Covered in this Lab:
  • count-controlled looping
  • event-controlled looping
  • sentinels
  • while control structure
  • for control structure
  • declaring variables in for control structures
  • problems with using double-type looping variables
Questions Answered in this Lab:
  • How are floating point variables used in stopping conditions?
  • How is an input sentinel chosen?
  • Why do programs sometimes fail to halt?
  • What are the parts of a for control structure header?
  • What is the connection between the for control structure and the while control structure?
  • What are some common errors in constructing for loops?
Demonstrable Skills Acquired in this Lab:
  • understand stopping conditions for both count- and event-controlled loops
  • ability to construct while loops
  • ability to construct appropriate for loops
  • understanding of when to use for loops


Three Types of Program Control

There are three programming constructs sufficient for the solution of any programming problem: sequence, selection, and repetition. The “hello, world” type of program illustrates the sequence construct; one statement executes after another in the order written. The previous lab examined selections with the if and if-else control structures. This lab introduces repetition, which is what allows the speed of a computer to be exploited by performing the same task many times.


Two Types of Repetition

From a logical standpoint, there are two types of repetition: event-controlled and count-controlled. Event-controlled repetition continues until some specific event occurs. When a telephone number is looked up in a directory, the search stops when the corresponding name is either found or determined not to be in the directory; the specific quantity of names and numbers to be examined will not be known beforehand. A program may be designed to execute until the user indicates “finished” with a ‘y’ (yes) or ‘n’ (no). (Incidentally, the indication in this case is a sentinel value and this type of event-controlled loop is often referred to as a sentinel-controlled loop.) Count-controlled loops execute a specific number of times which is known in advance of the loop's start. Many mathematical formulae involve summations or products of a known number of values; for example, Horner's method for evaluating a polynomial of degree n requires one multiplication and one addition for each of the n degrees. Other more mundane count-controlled loops may be based on seven days in a week, twelve items in a dozen, or a known number of students enrolled in a course.


Three Syntax Structures for Looping in C++

With respect to C++ syntax, there are three looping structures: while, for, and do-while. Each of these looping structures has a purpose depending on the context where repetition occurs in a program; each of these structures can be used for either event- or count-controlled repetition.

The three C++ loops are categorized as pre-test and post-test based on whether they perform their test before or after the body of the loop. The while control structure is a pre-test loop and the do-while control structure is a post-test loop; these two structures are typically associated with event-controlled iteration. The for control structure is another pre-test loop, but it is typically used for count-controlled iteration. In this lab the while and for control structures will be examined; the do-while control structure will be considered in a subsequent lab.

The while Control Structure

The basic syntax of the while control structure is as follows:

Information Icon.pngSyntax
   while (CONDITION) 
      STATEMENT;  // body

The condition given by CONDITION may utilize any combination of relational, logical, arithmetic, and/or other operators in C++. The while control structure will repeat the “body” of the loop (the STATEMENT) as long as the condition is true (non-zero). If the condition ever becomes false (zero), the repetition ends and execution continues at the statement following the while loop. In the example above, the loop body is only one statement. Generally, it is desired to repeat more than one statement; to do so, use a compound statement, or block of code, for the loop body. Recall that a compound statement uses the curly braces { and } to enclose any number of statements. Count-controlled and event-controlled examples will be given in subsequent sections.


Count-Controlled while Loops

When it is known in advance how many times a task is to be performed, a count-controlled loop is used. Much as a child might count on its fingers to keep track of something, a computer uses a counter variable. Count-controlled loops have three parts, each of which involves the counter: initialization of the counter, testing of the counter, and update of the counter.

The extended syntax for the while control structure shown below incorporates the three parts of the count-controlled loop.

Information Icon.pngSyntax
   INITIALIZE;
   while (CONDITION)
   { 
      BODY;
      UPDATE;
   }

Consider the following example.

Examplecode.pngExample Code
   int factorial = 1;
   int i = 1;     // c-c loop pt1: initialize
   while (i <= 4)  // c-c loop pt2: test
   {
      factorial = factorial * i;
      i++;        // c-c loop pt3: update (increment)
   }
   cout << factorial << endl;


Here, the while control structure will continue repeating the body of the loop as long as the value of i is less than four. The loop body is the compound statement including the statements factorial = factorial * i and i++. If the above statements are executed, what is the value of factorial when the execution ends? Carefully “step” through this loop to discover what happens.

step execution i factorial
1 factorial = 1 - 1
2 i = 1 1 1
3 Is (i <= 4)? Yes 1 1
4 factorial = factorial * i 1 1
5 i++ 2 1
6 Is (i <= 4)? Yes 2 1
7 factorial = factorial * i 2 2
8 i++ 3 2
9 Is (i <= 4)? Yes 3 2
10 factorial = factorial * i 3 6
11 i++ 4 6
12 Is (i <= 4)? Yes 4 6
13 factorial = factorial * i 4 24
14 i++ 5 24
15 Is (i <= 4)? No 5 24


Notice that the loop body was executed four times and the condition was tested five times for this repetition. In general, the condition is tested exactly one more time than the body of the loop is executed. If the test condition is changed to i <= n, what is a general formula for the value of factorial?

Createfile.pngBegin a C++ program in file sp05t.cpp.

Construct a function main that will get input from the user to replace the constant value 4 used in the loop condition above and then display the value of factorial. Execute the program for different small input values: 5, 6, 7, etc.; this will help in working out the formula.

Event-Controlled while Loops

When it is not known in advance how many times a task is to be performed, an event-controlled loop is used. As an example, consider finding the next perfect square following a given number. For example, the next perfect square after 7 is 9, the next after 11 is 16, and the next after 24 is 25. Notice in each case that the distance from the given number to the perfect square is different; it is this characteristic that suggests the use of an event-controlled loop. In order to find the next perfect square, one might start simplistically at one and count upwards, squaring numbers until a square is found bigger than the given value. Add the following code segment to function main and verify that it behaves as expected.

Verbatimcode.pngCode Illustration
   cout << "Enter a number for which to find the next largest perfect square: ";
   int n;
   cin >> n;
   int sqRoot = 1;            // where would be another place to start?
   while (sqRoot*sqRoot < n)  // sqRoot is too small
      sqRoot++;               // get ready to try the next number
   cout << sqRoot*sqRoot << endl;


Pay particular attention to the fact that a variable in the while's test (sqRoot) is modified in the body of the loop (sqRoot++). This observation must hold true for every event-controlled loop (and count-controlled loops, too); otherwise, the loop will never terminate. (This problem will be considered in more detail shortly.)

The next smallest perfect square is defined similarly. Examples would include a result of 4 for 7, a result of 9 for 15, and a result of 16 for 17. One could start at a large value and try successive smaller values until the desired result is found.


Sentinel-Controlled while Loops

An event-controlled loop which repeats until a particular value is encountered in the data being read is commonly referred to as a sentinel-controlled loop. The particular value is the sentinel value; the sentinel value should be a value that would not be considered a valid data value for the data being read.

Consider the following syntax change from a count-controlled loop to a sentinel-controlled loop.

Information Icon.pngSyntax
INITIALIZE;                                    PROMPT AND READ FIRST DATA VALUE;
while (CONDITION)               =>             while (DATA != SENTINEL VALUE)
{                                              {
   BODY;                                          BODY;
   UPDATE;                                        PROMPT AND READ NEXT DATA VALUE;
}                                              }

The initialization step for a sentinel-controlled loop is to prompt the user for data and get the keyboard input. (If the input was from a file, the prompt would not be needed, only the read.) The CONDITION is the verification that the data read was not the sentinel value. The UPDATE becomes the prompt and subsequent read of the next data value.

Sentinel-controlled loops often have the following format.

Pseudocode.pngPseudocode
   cout << "Enter data (sentinel value to end): ";
   cin >> data;
   while (data != sentinel value)
   {
      // process data
      
      cout << "Enter data (sentinel value to end): ";
      cin >> data;
   }


Note that a value is read prior to the initial test of the while loop; subsequent values are read at the bottom of the loop, immediately prior to the return to the top of the loop and another test. All data is tested prior to being used in any calculations. The last piece of data read is not processed; consequently, n+1 reads are required for n meaningful pieces of data. Beginning students are often troubled by the repetition of the cout-cin pair, sometimes trying to replace them with logic which is inevitably more complicated. Suffice it to say that there simply is no more succinct manner in which to accomplish this task.

As an example of a sentinel-controlled loop, add the following to function main to read a series of test scores and determine their average.

Verbatimcode.pngCode Illustration
   int sum = 0;            // total of all scores
   int numberOfScores = 0; // quantity of scores
   int score;              // individual exam
   cout << "Enter a test score (-1 to end): ";
   cin >> score;
   while (score != -1)     // test for sentinel
   {
      sum += score;        // running total
      numberOfScores++;    // count

      cout << "Enter a test score (-1 to end): ";
      cin >> score;
   }
   double average = static_cast <double> (sum) / numberOfScores;


The sentinel value for this loop is -1, chosen because exam scores are generally non-negative.

The variables sum and numberOfScores are both naturally integers; simple division with them in C++ would result in an integer quotient as well. However, the average is by its nature a floating-point quantity. Consequently, the numerator is cast to become a floating-point dividend prior to what must then be a floating-point division and quotient. Execute the program with some values for which the results are easily predicable: {71, 72, 73} or {60, 70, 80, 90}.

The pre-test nature of the while loop allows for the possibility that the loop body may never be executed at all. In the example at hand, the first score entered could be -1. In this case, there will be no meaningful average; worse, numberOfScores remains zero which will result in an undefined quotient. Any time division involves a variable as divisor, the division should be protected from a divisor of zero. Replace the last line of the preceding segment with the following.

Verbatimcode.pngCode Illustration
   if (numberOfScores > 0)
   {
      double average = static_cast <double> (sum) / numberOfScores;
      cout << "The average of the " << numberOfScores << " scores is " 
           << average << ".\n";
   }
   else
      cout << "No scores were entered!\n";

Execute the program again to test this addition.

Modify the program to read the exam scores from a file.

An example data file might contain the following

Data icon.pngTest your code with the following data:
89
90
78
67
-1

Or it might contain

Data icon.pngTest your code with the following data:
89 90 78 67 -1


Infinite Loops

One problem with looping is the so-called infinite loop, a loop with a condition that is always true. The loop body must always act to alter one or more of the variables involved in the condition of the loop. If the condition never changes then the loop will continue executing.

If your program enters an infinite loop then it will never exit. So, how do you get the command prompt back so that you can fix the error and recompile your program? The key combination ctrl-c (which means hold down the ctrl key and then hit the c key) will kill the current program that is running on the command line. This command works in Windows, Unix, and Mac OS X.


user.name@ComputerName  /current/directory
$ ./sp05
I've fallen and I can't get up! ...
Hit ctrl-c to kill the stuck program.


An accidental infinite loop is illustrated in the following example.

Examplecode.pngExample Code
   int i = 1;
   while (j < 10)
   {
      cout << j;
      i++;
   }


In this case, the programmer inadvertently based the termination of the loop upon a condition which is unaffected by the loop. Said another way, condition variable j is not modified in the body of the loop. This problem appears in many forms, some as simple as a typographical error. Here is another example.

Examplecode.pngExample Code
   i = 100;
   while (i > 10);
      i = i - 1;


This is a problem that is common, even among more seasoned programmers. What problem causes the infinite loop? It would seem that the meaning of the loop is clear, but the semicolon at the end of the condition ends the loop; the loop body is thus empty. The code reformatted to illustrate the real meaning is as follows.

Examplecode.pngExample Code
   i = 100;
   while (i > 10)
      ;        // empty statement
   i = i - 1;  // will execute once after loop ends


Since variable i is never altered by the body of the loop it retains the value of 100, which is greater than 10, and so the loop continues indefinitely. The code can be fixed by removing the extra semicolon at the end of the while condition. The semicolon is not placed at the end of every line of code; it is meant to terminate statements, each of which may extend through multiple lines.


Syntax of the for Control Structure

The for construct is especially well-suited for count-controlled loops. In count-controlled loops, the number of executions of the loop body is known before the loop body is executed the first time. As will be seen, the very format of the for loop suggests count-controlled repetition. As a general rule, use while for event-controlled loops, such as sentinel-controlled loops, and for with count-controlled loops. Consistency in this will make programs that much more readable and maintainable.

Here is an example of the syntax of the for control structure.

Information Icon.pngSyntax
for (INITIALIZE; TEST; UPDATE)
{
   BODY;
}

An example for loop that increments through pages one through six.

Examplecode.pngExample Code
   for (int page = 1; page <= 6; page++) // header: initialize, test & update
      cout << "page " << page << endl;   // loop body


The for control structure is comprised of two parts, the header and the body; this much is the same as for the while control structure. The for control structure header in turn has three parts, each a C++ statement, separated by semicolons: initialize, test, and update.

  1. Initialize: Initialization occurs once, as the first (preliminary) step in executing the for loop, before the loop begins repeating. This statement should be used to initialize the loop variable used in the test part of the header. In general, this variable will “count” the number of times through the loop. As will be seen, this variable may or may not appear in the loop body.
  2. Test: After initialization, the test expression is then evaluated repeatedly; each time it is determined to be true (nonzero), the loop body is executed. The for loop execution ends the first time the test expression is found to be false (zero). Note that the expression is evaluated before each execution of the loop, exactly as is done for the while loop. In counting loops, this test will compare the loop variable against some limit. In the above example, the loop continues as long as the loop variable page is less than seven. How many times does the above loop body execute?
  3. Update: Immediately after each execution of the loop body (and thus just prior to the next evaluation of the test expression), the loop control update is performed. In most iterative or counting loops, the loop variable needs to be incremented. In the example above, the loop variable page is incremented by one before it is tested. It is also possible to count down by using a decrement here or count by more than one using the += operator.


Add the for loop above and execute the program; observe that the numbers one through six are displayed, as one would expect.


A common mistake when constructing for loops is to use a comma in place of a semicolon. Change the semicolon following the initialization in the program to a comma and recompile the program.


Example for Control Structure Headers

The for control structure header is very flexible; here are several different for loop headers, some good and some not so good.

for (i = 1; i <= 100; i++) // count naturally through 100
This header is much like the first example, except that the loop variable (or counter) starts at one and ends at one hundred.
for (i = 100; i > 0; i--) // count down
This header counts from one hundred down to one by decrementing the count variable instead of incrementing.
for (int i = 0; i < 10; ++i) // preincrement ++; any difference?
This header illustrates the ability to declare the looping variables in the header. According to the defined standard for C++, the looping variable goes out of scope at the end of the for body; this means that a loop variable declared this way does not exist outside the body of the loop. This will be considered again in the next section.
for (i=0,j=1; j <= 100; j=i+j,i=j-i) // asking for trouble
This header uses commas to allow more than one statement in the initialization and increment parts. In fact, the increments are dependent on the previous values of the variables. Such dense loop headers should be avoided, as they lead to confusion and code that is difficult to maintain. (Try to determine what the values of these loop variables are.)
for (j = -5; j <= 42; j+=2) // count by two
This header sets different loop variable bounds and increments the loop variable by two instead of one.
for (;;)
The missing statements in this header are empty statements in C++; an empty condition is treated as being true, and since it can never become false, an infinite loop results. A more common intentional infinite loop uses the while loop with a header such as while(1) or while(true).

Declarations in the for Header

As mentioned above, variables can be declared inside the for loop header itself. Doing so can be useful when the only purpose of the variable is to count the number of iterations of the loop. Why clutter up the rest of the program with these variables when they serve no purpose outside the loop? Extend the program's function main to contain the following and execute it.


Verbatimcode.pngCode Illustration
   for (int i = 1; i <= 3; i++) 
      cout << "loop 1: i = " << i << endl;
   cout << endl;
   for (int i = 11; i < 13; i++) 
      cout << "loop 2: i = " << i << endl;


According to the C++ standard, which describes how C++ is supposed to behave, the for loops above result in a variable named i being declared within the scope of the for loop.

Equivalence of for and while Loops

It is interesting to note that since the for loop and the while loop are both pre-test loops, any for loop can be written as a while loop and vice versa. The following illustrates the conversion between a for and a while loop.

Information Icon.pngSyntax
                                               INITIALIZE;
for (INITIALIZE; TEST; UPDATE)       <==>      while (TEST)
{                                              {
   BODY;                                          BODY;
}                                                 UPDATE;
                                               }


Notice this follows the discussion earlier about the parts of the for loop header. The for control structure initializes only once before the loop, the condition is tested before each execution of the body of the loop, and the increment is done at the end of the loop prior to the next test.

As suggested above, any while loop can also be written as a for loop, again due to the fact that both are pre-test loops.

Information Icon.pngSyntax
INITIALIZE;                                    INITIALIZE;
while (TEST)                <==>               for ( ; TEST; )
{                                              {
   BODY;                                          BODY;
   UPDATE;                                        UPDATE;
}                                              }


Observe that the for loop's variable initialization and increment are omitted from the header. As noted earlier, C++ considers the missing statements to be empty statements and will still execute the for loop.

Extend function main with a while loop corresponding to each one of the for loops of the section Example for Control Structure Headers. Let the body of each loop display the content of the tested variable; the last (infinite) loop should be omitted.


Floating-Point Counter Variables

A different kind of problem can occur when using float or double loop variables. Consider the following loop.

Verbatimcode.pngCode Illustration
   for (double x = 0; x != 1; x += 0.10)
      cout << x << endl;


From inspection, one might expect the numbers to look like the following.

   0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0


Add this loop to function main and run it. The computer goes into what appears to be an infinite loop. Why did this occur?

The answer lies in the computer representation of decimal values. Decimal numbers are exactly what "decimal" advertises: base 10 (for which the digits are 0 through 9). Computers inherently work in base 2 (for which the digits are restricted to 0 and 1); computers are often able to hide base 2 by converting everything to base 10, but the conversions are not always perfect. To avoid this type of problem, use a less-demanding test (which the conversion will be able to pass).

The previous program could be made to work by examining the absolute value of the difference between x and 1; when the difference becomes small, positive or negative, x can be considered close enough to 1. The absolute value function in C++ is fabs, and it requires that the cmath header file be included (like iostream, although no using is necessary).

Use the multi-line comment delimiters /* and */ to comment out the infinite loop recently added to the program. Extend the program as follows.

Verbatimcode.pngCode Illustration
   for (double x = 0; fabs (x - 1) > 0.001; x += 0.10)
      cout << x << endl;


Devise another test to stop the loop when x gets close to one; this test should not use the fabs function.


Nested Loops

When loops are placed inside one another, they are said to be nested. To illustrate how for loops are nested, extend function main to ask the user for the number of students who made an 'A' on the last test.

Verbatimcode.pngCode Illustration
   int numberOfAs;
   cout << "Enter the number of 'A's on the last test: ";
   cin  >> numberOfAs;


Next, write a count-controlled loop using for that will print a row of asterisks equal in length to the number of 'A's.

Verbatimcode.pngCode Illustration
   for (int i = 1; i <= numberOfAs; i++)
      cout << "*";
   cout << endl;


When this sequence is working successfully, write another for loop around the sequence which will repeat the question and print rows of asterisks for each of the letter grades 'A' through 'F' (including 'E' for the moment). A for loop whose loop index is of type char can be used for this task; it would be initialized to 'A', tested against 'F', and incremented the same way an integer would be.

Verbatimcode.pngCode Illustration
   for (char grade = 'A'; grade <= 'F'; grade++)
   {
      int numberOfGrade;
      cout << "Enter the number of '" << grade << "'s on the last test: ";
      cin  >> numberOfGrade;
      for (int i = 1; i <= numberOfGrade; i++)
         cout << "*";
      cout << endl;
   }


Note that an if control structure will be required to prevent the question from being asked for the non-existent grade 'E'. Add this if control structure now.

Modify this program so that it asks for the quantity of all five grades before displaying any asterisks. Two separate loops will be required: one for reading quantities and the other for displaying asterisks. A if-else chain will be needed inside each for loop in order to access the correct counter. You will need to use a technique similar to the one you employed in the previous section to ignore the 'E' grade.

Pseudocode.pngPseudocode
   int numberOfAs = 0;
   // etc.
   for // grade = 'A' to 'F'
   {
      cout << "Enter the number of '" << grade << "'s on the last test: ";
      if (grade == 'A')
         cin >> numberOfAs;
      else if // etc.
   }  // end for

   for // grade = 'A' to 'F'
   {
      cout << grade << ": ";
      if (grade == 'A')
         for // i = 1 to numberOfAs
            cout << "*";
      else if // etc.
      cout << endl;
   }  // end for


Note: Additional refinements will be made to this task in a future tutorial.