Visual Basic 2005 for Programmers (2nd Edition)

5.10. Sentinel-Controlled Repetition

Let us generalize Section 5.9's class-average problem.

Develop a class-averaging program that processes grades for an arbitrary number of students each time it is run.

In the previous class-average example, the problem statement specified the number of students, so the number of grades (10) was known in advance. In this example, no indication is given of how many grades are to be input. The program must process an arbitrary number of grades. How can it determine when to stop the input of grades? How will it know when to calculate and print the class average?

One way to solve this problem is to use a special value called a sentinel value (also called a signal value, a dummy value or a flag value) to indicate "end of data entry." The user enters grades until all legitimate grades have been entered. The user then types the sentinel value to indicate that no more grades will be entered. Sentinel-controlled repetition is called indefinite repetition because the number of repetitions is not known before the loop begins its execution.

It is crucial to employ a sentinel value that cannot be confused with an acceptable input value. Grades on a quiz are nonnegative integers, so 1 is an acceptable sentinel value for this problem. A run of the class-average program might process a stream of inputs such as 95, 96, 75, 74, 89 and 1. The program would then compute and print the class average for the grades 95, 96, 75, 74 and 89. The sentinel value, 1, should not enter into the averaging calculation.

Common Programming Error 5.4

Choosing a sentinel value that is also a legitimate data value could result in a logic error that would cause a program to produce incorrect results.

Implementing Sentinel-Controlled Repetition in Class GradeBook

Figure 5.12 shows the Visual Basic class GradeBook containing the method DetermineClassAverage that solves the class average problem with sentinel-controlled repetition. Although each grade is an integer, the averaging calculation is likely to produce a number with a decimal pointa real number or floating-point number. The type Integer cannot represent such a number, so this class uses type Double to do so.

Figure 5.12. Sentinel-controlled repetition: Class-average problem.

1 ' Fig. 5.12: GradeBook.vb 2 ' GradeBook class that solves class-average problem using 3 ' sentinel-controlled repetition. 4 Public Class GradeBook 5 Private courseNameValue As String ' course name for this GradeBook 6 7 ' constructor initializes CourseName with String supplied as argument 8 Public Sub New(ByVal name As String) 9 CourseName = name ' validate and store course name 10 End Sub ' New 11 12 ' property that gets and sets the course name; the Set accessor 13 ' ensures that the course name has at most 25 characters 14 Public Property CourseName() As String 15 Get ' retrieve courseNameValue 16 Return courseNameValue 17 End Get 18 19 Set(ByVal value As String) ' set courseNameValue 20 If value.Length <= 25 Then ' if value has 25 or fewer characters 21 courseNameValue = value ' store the course name in the object 22 Else ' if name has more than 25 characters 23 ' set courseNameValue to first 25 characters of parameter name 24 ' start at 0, length of 25 25 courseNameValue = value.Substring(0, 25) 26 27 Console.WriteLine( _ 28 "Course name (" & value &") exceeds maximum length (25).") 29 Console.WriteLine( _ 30 "Limiting course name to first 25 characters." & vbCrLf) 31 End If 32 End Set 33 End Property ' CourseName 34 35 ' display a welcome message to the GradeBook user 36 Public Sub DisplayMessage() 37 ' this statement uses property CourseName to get the 38 ' name of the course this GradeBook represents 39 Console.WriteLine("Welcome to the grade book for "_ 40 & vbCrLf & CourseName & "!" & vbCrLf) 41 End Sub ' DisplayMessage 42 43 ' determine class average based on 10 grades entered by user 44 Public Sub DetermineClassAverage() 45 Dim total As Integer ' sum of grades entered by user 46 Dim gradeCounter As Integer ' number of grades input 47 Dim grade As Integer ' grade input by user 48 Dim average As Double ' average of all grades 49 50 ' initialization phase 51 total = 0 ' clear total 52 gradeCounter = 0 ' prepare to loop 53 54 ' processing phase 55 ' prompt for input and read grade from user 56 Console.Write("Enter grade or -1 to quit:") 57 grade = Console.ReadLine() 58 59 ' sentinel-controlled loop where -1 is the sentinel value 60 While grade <> -1 61 total += grade ' add grade to total 62 gradeCounter += 1 ' add 1 to gradeCounter 63 64 ' prompt for and input next grade from user 65 Console.Write("Enter grade or -1 to quit:") ' prompt 66 grade = Console.ReadLine() ' input next grade 67 End While 68 69 ' termination phase 70 If gradeCounter <> 0 Then ' if user entered at least one grade 71 ' calculate average of all grades entered 72 average = total / gradeCounter 73 74 ' display total and average (with two digits of precision) 75 Console.WriteLine(vbCrLf & "Total of the" & gradeCounter & _ 76 " grades entered is " & total) 77 Console.WriteLine("Class average is {0:F}", average) 78 Else ' no grades were entered, so output appropriate message 79 Console.WriteLine("No grades were entered") 80 End If 81 End Sub ' DetermineClassAverage 82 End Class ' GradeBook

In this example, we see that control statements may be stacked on top of one another (i.e., placed in sequence). The While statement (lines 6067) is followed in sequence by an If...Then...Else statement (lines 7080). Much of the code in this program is identical to the code in Fig. 5.10, so we concentrate on the new features and issues.

Line 48 declares Double variable average. This variable allows us to store the calculated class average as a floating-point number. We discuss floating-point numbers in more detail shortly. Line 52 initializes gradeCounter to 0, because no grades have been entered yet. Remember that this program uses sentinel-controlled repetition to input the grades from the user. To keep an accurate record of the number of grades entered, the program increments gradeCounter only when the user inputs a valid grade value. Recall that Integer variables are initialized to zero by default, so lines 5152 can be omitted.

Program Logic for Sentinel-Controlled Repetition vs. Counter-Controlled Repetition

Compare the program logic for sentinel-controlled repetition in this application with that for counter-controlled repetition in Fig. 5.10. In counter-controlled repetition, each iteration (repetition) of the While statement (e.g., lines 5561 of Fig. 5.10) reads a value from the user for the specified number of iterations. In sentinel-controlled repetition, the program reads the first value (lines 5657 of Fig. 5.12) before reaching the While. This value determines whether the program's flow of control should enter the body of the While. If the condition of the While is false, the user entered the sentinel value, so the body of the While does not execute (i.e., no grades were entered). If, on the other hand, the condition is true, the body begins execution, and the loop adds the grade value to the total (line 61). Lines 6566 in the loop's body input the next value from the user. Next, program control reaches the end of the While statement, so execution continues with the test of the While's condition (line 60). The condition uses the most recent grade input by the user to determine whether the loop's body should execute again. Note that the value of variable grade is always input from the user immediately before the program tests the While condition. This allows the program to determine whether the value just input is the sentinel value before processing that value (i.e., adding it to the total). If the sentinel value is input, the loop terminates, and the program does not add 1 to the total.

Good Programming Practice 5.2

In a sentinel-controlled loop, the prompts requesting data entry should explicitly remind the user of the sentinel value.

After the loop terminates, the If...Then...Else statement at lines 7080 executes. The condition at line 70 determines whether any grades were input. If none were input, the Else part (lines 7879) of the If...Then...Else statement executes and displays the message "No grades were entered" and the method returns control to the calling method.

Floating-Point Numbers and Type Double

Although each grade entered is an integer, the averaging calculation is likely to produce a number with a decimal point (i.e., a floating-point number). The type Integer cannot represent floating-point numbers, so this program uses data type Double to store floating-point numbers.

Visual Basic provides two primitive types for storing floating-point numbers in memorySingle and Double. The primary difference between them is that Double variables can store numbers of larger magnitude and finer detail (i.e., more digits to the right of the decimal pointalso known as the number's precision) than Single variables.

Floating-Point Number Precision and Memory Requirements

Variables of type Single represent single-precision floating-point numbers and have seven significant digits. Variables of type Double represent double-precision floating-point numbers. These require twice as much memory as Single variables and provide 15 significant digitsapproximately double the precision of Single variables. For the range of values required by most programs, variables of type Single should suffice, but you can use Double to play it safe. In some applications, even variables of type Double will be inadequatesuch applications are beyond the scope of this book. Most programmers represent floating-point numbers with type Double. In fact, Visual Basic treats all the floating-point numbers you type in a program's source code (such as 7.33 and 0.0975) as Double values by default. Such values in the source code are known as floating-point literals. See Section 7.11 for the ranges of values for Singles and Doubles.

Implicitly Converting Between Primitive Types

If at least one grade was entered, line 72 of Fig. 5.12 calculates the average of the grades. Recall from Fig. 5.10 that we used the integer division operator to yield an integer result. Since we are now calculating a floating-point value, we use the floating-point division operator. But the operands of the division in line 72 are of type Integer. To perform a floating-point calculation with integer values, we must temporarily treat these values as floating-point numbers for use in the calculation.

The floating-point division operator is defined to operate on values of three typesSingle, Double and Decimal (you will learn about type Decimal in Chapter 6). To ensure that the operator's operands are one of these three types, Visual Basic performs an operation called implicit conversion on selected operands. For example, in an expression using the floating-point division operator, if both operands are of type Integer, the operands will be promoted to Double values for use in the expression. In this example, the values of total and gradeCounter are promoted to type Double, then the floating-point division is performed and the result of the calculation is assigned to average. We'll say more about the implicit conversion rules in Section 7.9.

Formatting for Floating-Point Numbers

Line 77 outputs the class average. In this example, we decided to display the class average rounded to the nearest hundredth and to output the average with exactly two digits to the right of the decimal point. Note that the call to method WriteLine in line 77 uses the string "{0:F}" to indicate that the value of average should be displayed in the command prompt as a fixed-point number, (i.e., a number with a specified number of places after the decimal point). The numeric value that appears before the colon (in this case, 0) indicates which of WriteLine's arguments will be formatted0 specifies the first argument that occurs after the string is passed to WriteLine, namely average. Additional values can be inserted into the string to specify other arguments. If we had passed total as a third argument to method WriteLine and used the string "{1:F}" the value to be formatted would be total. The value after the colon (in this case, F) is known as a format specifier, which indicates how a value is to be formatted. The format specifier F indicates that a fixed-point number should (by default) be displayed with two decimal places. This can be changed by placing a numeric value after the format specifier. For example, the string "{0:F3}" would display a fixed-point value with three decimal places. Some of the more common format specifiers are summarized in Fig. 5.13. The format specifiers are case insensitive. Also, the format specifiers D and X can be used only with integer values.

Figure 5.13. Formatting codes for Strings.

Format Code

Description

C

Currency. Precedes the number with $, separates every three digits with commas and sets the number of decimal places to two.

E

Scientific notation. Displays one digit to the left of the decimal and six digits to the right of the decimal, followed by the character E and a three-digit integer representing the exponent of a power of 10. For example, 956.2 is formatted as 9.562000E+002.

F

Fixed point. Sets the number of decimal places to two.

G

General. Visual Basic chooses either E or F for you, depending on which representation generates a shorter string.

D

Decimal integer. Displays an integer as a whole number in standard base-10 format.

N

Number. Separates every three digits with a comma and sets the number of decimal places to two.

X

Hexadecimal integer. Displays the integer in hexadecimal (base-16) notation. We discuss hexadecimal notation in Appendix B.

Good Programming Practice 5.3

When formatting with two positions to the right of the decimal point, some programmers prefer to use the format specifier F2for clarity.

The three grades entered during the sample execution of module GradeBookTest (Fig. 5.14) total 257, which yields the average 85.666666.... The format specifier causes the output to be rounded to the specified number of digits. In this program, the average is rounded to the hundredths position and is displayed as 85.67.

Figure 5.14. GradeBookTest module creates an object of class GradeBook (Fig. 5.12) and invokes its DetermineClassAverage method.

1 ' Fig. 5.14: GradeBookTest.vb 2 ' Create GradeBook object and invoke its DetermineClassAverage method. 3 Module GradeBookTest 4 Sub Main() 5 ' create GradeBook object gradeBook and 6 ' pass course name to constructor 7 Dim gradeBook As New GradeBook("CS101 Introduction to VB") 8 9 gradeBook.DisplayMessage() ' display welcome message 10 gradeBook.DetermineClassAverage() ' find average of grades 11 End Sub ' Main 12 End Module ' GradeBookTest

Welcome to the grade book for CS101 Introduction to VB! Enter grade or -1 to quit: 97 Enter grade or -1 to quit: 88 Enter grade or -1 to quit: 72 Enter grade or -1 to quit: -1 Total of the 3 grades entered is 257 Class average is 85.67

Категории