Not everything that can be counted counts, and not everything that counts can be counted. | ||
--Albert Einstein |
Who can control his fate? | ||
--William Shakespeare |
The used key is always bright. | ||
--Benjamin Franklin |
Every advantage in the past is judged in the light of the final issue. | ||
--Demosthenes |
In this chapter you’ll learn:
<objective>The essentials of counter-controlled repetition.
</objective> <objective>To use the for
and do
...while
repetition statements.
To use the switch
multiple selection statement.
To use the break
and continue
statements to alter the flow of control.
To use the logical operators to form complex conditional expressions.
</objective> </feature><feature> <supertitle>Outline</supertitle> </feature>In this chapter, we continue our presentation of the theory and principles of structured programming by introducing several of C#’s remaining control statements. (The foreach
statement is introduced in Chapter 8, Arrays.) The control statements we study here and in Chapter 5 are helpful in building and manipulating objects.
Through a series of short examples using while
and for
, we explore the essentials of counter-controlled repetition. We create a version of class GradeBook
that uses a switch
statement to count the number of A, B, C, D and F grade equivalents in a set of numeric grades entered by the user. We introduce the break
and continue
program-control statements. We discuss C#’s logical operators, which enable you to use more complex conditional expressions in control statements. Finally, we summarize C#’s control statements and the proven problem-solving techniques presented in this chapter and Chapter 5.
This section uses the while
repetition statement to formalize the elements required to perform counter-controlled repetition. Counter-controlled repetition requires
To see these elements of counter-controlled repetition, consider the application of Fig. 6.1, which uses a loop to display the numbers from 1 through 10.
Example 6.1. Counter-controlled repetition with the while
repetition statement.
1 // Fig. 6.1: WhileCounter.cs 2 // Counter-controlled repetition with the while repetition statement. 3 using System; 4 5 public class WhileCounter 6 { 7 public static void Main( string[] args ) 8 { 9 int counter = 1; // declare and initialize control variable 10 11 while ( counter <= 10 ) // loop-continuation condition 12 { 13 Console.Write( "{0} ", counter ); 14 ++counter; // increment control variable 15 } // end while 16 17 Console.WriteLine(); // output a newline 18 } // end Main 19 } // end class WhileCounter
In method Main
(lines 7–18), the elements of counter-controlled repetition are defined in lines 9, 11 and 14. Line 9 declares the control variable (counter
) as an int
, reserves space for it in memory and sets its initial value to 1
.
Line 13 in the while
statement displays control variable counter
’s value during each iteration of the loop. Line 14 increments the control variable by 1 for each iteration of the loop. The loop-continuation condition in the while
(line 11) tests whether the value of the control variable is less than or equal to 10
(the final value for which the condition is true
). The application performs the body of this while
even when the control variable is 10
. The loop terminates when the control variable exceeds 10
(i.e., counter
becomes 11
).
Because floating-point values may be approximate, controlling loops with floating-point variables may result in imprecise counter values and inaccurate termination tests.
The application in Fig. 6.1 can be made more concise by initializing counter
to 0
in line 9 and incrementing counter
in the while
condition with the prefix increment operator as follows:
while ( ++counter <= 10 ) // loop-continuation condition Console.Write( "{0} ", counter ); |
This code saves a statement (and eliminates the need for braces around the loop’s body), because the while
condition performs the increment before testing the condition. (Recall from Section 5.12 that the precedence of ++
is higher than that of <=
.) Code written in such a condensed fashion might be more difficult to read, debug, modify and maintain.
Section 6.2 presented the essentials of counter-controlled repetition. The while
statement can be used to implement any counter-controlled loop. C# also provides the for
repetition statement, which specifies the elements of counter-controlled-repetition in a single line of code. In general, counter-controlled repetition should be implemented with a for
statement. Figure 6.2 reimplements the application in Fig. 6.1 using the for
statement.
Example 6.2. Counter-controlled repetition with the for
repetition statement.
1 // Fig. 6.2: ForCounter.cs 2 // Counter-controlled repetition with the for repetition statement. 3 using System; 4 5 public class ForCounter 6 { 7 public static void Main( string[] args ) 8 { 9 // for statement header includes initialization, 10 // loop-continuation condition and increment 11 for ( int counter = 1; counter <= 10; counter++ ) 12 Console.Write( "{0} ", counter ); 13 14 Console.WriteLine(); // output a newline 15 } // end Main 16 } // end class ForCounter
1 2 3 4 5 6 7 8 9 10 |
The application’s Main
method operates as follows: when the for
statement (lines 11–12) begins executing, control variable counter
is declared and initialized to 1
. (Recall from Section 6.2 that the first two elements of counter-controlled repetition are the control variable and its initial value.) Next, the application checks the loop-continuation condition, counter <= 10
, which is between the two required semicolons. Because the initial value of counter
is 1
, the condition initially is true. Therefore, the body statement (line 12) displays control variable counter
’s value, which is 1
. After executing the loop’s body, the application increments counter
in the expression counter++
, which appears to the right of the second semicolon. Then the loop-continuation test is performed again to determine whether the application should continue with the next iteration of the loop. At this point, the control-variable value is 2
, so the condition is still true (the final value is not exceeded)—and the application performs the body statement again (i.e., the next iteration of the loop). This process continues until the numbers 1 through 10 have been displayed and the counter
’s value becomes 11
, causing the loop-continuation test to fail and repetition to terminate (after 10 repetitions of the loop body at line 12). Then the application performs the first statement after the for
—in this case, line 14.
Fig. 6.2 uses (in line 11) the loop-continuation condition counter <= 10
. If you incorrectly specified counter < 10
as the condition, the loop would iterate only nine times—a common logic error called an off-by-one error.
Using the final value in the condition of a while
or for
statement with the <=
relational operator helps avoid off-by-one errors. For a loop that displays the values 1 to 10, the loopcontinuation condition should be counter <= 10
, rather than counter < 10
(which causes an off-by-one error) or counter < 11
(which is correct). Many programmers prefer so-called zero-based counting, in which, to count 10 times, counter
would be initialized to zero and the loop-continuation test would be counter < 10
.
Figure 6.3 takes a closer look at the for
statement in Fig. 6.2. The for
’s first line (including the keyword for
and everything in parentheses after for
)—line 11 in Fig. 6.2—is sometimes called the for
statement header, or simply the for
header. The for
header “does it all”—it specifies each of the items needed for counter-controlled repetition with a control variable. If there’s more than one statement in the body of the for
, braces are required to define the body of the loop.
The general format of the for
statement is
for ( initialization; loopContinuationCondition; increment ) statement |
where the initialization expression names the loop’s control variable and provides its initial value, the loopContinuationCondition is the condition that determines whether looping should continue and the increment modifies the control variable’s value (whether an increment or decrement), so that the loop-continuation condition eventually becomes false. The two semicolons in the for
header are required. We don’t include a semicolon after statement, because the semicolon is already assumed to be included in the notion of a statement.
In most cases, the for
statement can be represented with an equivalent while
statement as follows:
initialization; while ( loopContinuationCondition ) { statement increment; } |
In Section 6.7, we discuss a case in which a for
statement cannot be represented with an equivalent while
statement.
Typically, for
statements are used for counter-controlled repetition, and while
statements are used for sentinel-controlled repetition. However, while
and for
can each be used for either repetition type.
If the initialization expression in the for
header declares the control variable (i.e., the control variable’s type is specified before the variable name, as in Fig. 6.2), the control variable can be used only in that for
statement—it will not exist outside it. This restricted use of the name of the control variable is known as the variable’s scope. The scope of a variable defines where it can be used in an application. For example, a local variable can be used only in the method that declares the variable and only from the point of declaration through the end of the block in which the variable has been declared. Scope is discussed in detail in Chapter 7, Methods: A Deeper Look.
When a for
statement’s control variable is declared in the initialization section of the for
’s header, using the control variable after the for'
s body is a compilation error.
All three expressions in a for
header are optional. If the loopContinuationCondition is omitted, C# assumes that the loop-continuation condition is always true, thus creating an infinite loop. You can omit the initialization expression if the application initializes the control variable before the loop—in this case, the scope of the control variable will not be limited to the loop. You can omit the increment expression if the application calculates the increment with statements in the loop’s body or if no increment is needed. The increment expression in a for
acts as if it were a stand-alone statement at the end of the for
’s body. Therefore, the expressions
counter = counter + 1 counter += 1 ++counter counter++ |
are equivalent increment expressions in a for
statement. Many programmers prefer counter++
because it’s concise and because a for
loop evaluates its increment expression after its body executes—so the postfix increment form seems more natural. In this case, the variable being incremented does not appear in a larger expression, so the prefix and postfix increment operators have the same effect.
There’s a slight performance advantage to using the prefix increment operator, but if you choose the postfix increment operator because it seems more natural (as in a for
header), optimizing compilers will generate MSIL code that uses the more efficient form anyway.
In many cases, the prefix and postfix increment operators are both used to add 1 to a variable in a statement by itself. In these cases, the effect is exactly the same, except that the prefix increment operator has a slight performance advantage. Given that the compiler typically optimizes your code to help you get the best performance, use the idiom (prefix or postfix) with which you feel most comfortable in these situations.
Infinite loops occur when the loop-continuation condition in a repetition statement never becomes false. To prevent this situation in a counter-controlled loop, ensure that the control variable is incremented (or decremented) during each iteration of the loop. In a sentinel-controlled loop, ensure that the sentinel value is eventually input.
The initialization, loop-continuation condition and increment portions of a for
statement can contain arithmetic expressions. For example, assume that x = 2
and y = 10
; if x
and y
are not modified in the body of the loop, then the statement
for ( int j = x; j <= 4 * x * y; j += y / x ) |
is equivalent to the statement
for ( int j = 2; j <= 80; j += 5 ) |
The increment of a for
statement may also be negative, in which case it’s a decrement, and the loop counts downward.
If the loop-continuation condition is initially false
, the application does not execute the for
statement’s body. Instead, execution proceeds with the statement following the for
.
Applications frequently display the control variable value or use it in calculations in the loop body, but this use is not required. The control variable is commonly used to control repetition without being mentioned in the body of the for
.
Although the value of the control variable can be changed in the body of a for
loop, avoid doing so, because this can lead to subtle errors.
Figure 6.4 shows the activity diagram of the for
statement in Fig. 6.2. The diagram makes it clear that initialization occurs only once before the loop-continuation test is evaluated the first time, and that incrementing occurs each time through the loop after the body statement executes.
The following examples show techniques for varying the control variable in a for
statement. In each case, we write the appropriate for
header. Note the change in the relational operator for loops that decrement the control variable.
Vary the control variable from 1
to 100
in increments of 1
.
for ( int i = 1; i <= 100; i++ ) |
Vary the control variable from 100
to 1
in decrements of 1
.
for ( int i = 100; i >= 1; i-- ) |
Vary the control variable from 7
to 77
in increments of 7
.
for ( int i = 7; i <= 77; i += 7 ) |
Vary the control variable from 20
to 2
in decrements of 2
.
for ( int i = 20; i >= 2; i -= 2 ) |
Vary the control variable over the following sequence of values: 2
, 5
, 8
, 11
, 14
, 17
, 20
.
for ( int i = 2; i <= 20; i += 3 ) |
Vary the control variable over the following sequence of values: 99
, 88
, 77
, 66
, 55
, 44
, 33
, 22
, 11
, 0
.
for ( int i = 99; i >= 0; i -= 11 ) |
Not using the proper relational operator in the loop-continuation condition of a loop that counts downward (e.g., using i <= 1
instead of i >= 1
in a loop counting down to 1) is a logic error.
We now consider two sample applications that demonstrate simple uses of for
. The application in Fig. 6.5 uses a for
statement to sum the even integers from 2 to 20 and store the result in an int
variable called total
.
Example 6.5. Summing integers with the for
statement.
1 // Fig. 6.5: Sum.cs 2 // Summing integers with the for statement. 3 using System; 4 5 public class Sum 6 { 7 public static void Main( string[] args ) 8 { 9 int total = 0; // initialize total 10 11 // total even integers from 2 through 20 12 for ( int number = 2; number <= 20; number += 2 ) 13 total += number; 14 15 Console.WriteLine( "Sum is {0}", total ); // display results 16 } // end Main 17 } // end class Sum
The initialization and increment expressions can be comma-separated lists that enable you to use multiple initialization expressions or multiple increment expressions. For example, you could merge the body of the for
statement in lines 12–13 of Fig. 6.5 into the increment portion of the for
header by using a comma as follows:
for ( int number = 2; number <= 20; total += number, number += 2 ) ; // empty statement |
The next application uses the for
statement to compute compound interest. Consider the following problem:
A person invests $1,000 in a savings account yielding 5% interest, compounded yearly. Assuming that all the interest is left on deposit, calculate and display the amount of money in the account at the end of each year for 10 years. Use the following formula to determine the amounts:
a = p (1 + r)n
where
p is the original amount invested (i.e., the principal)
r is the annual interest rate (e.g., use 0.05 for 5%)
n is the number of years
a is the amount on deposit at the end of the nth year.
This problem involves a loop that performs the indicated calculation for each of the 10 years the money remains on deposit. The solution is the application shown in Fig. 6.6. Lines 9–11 in method Main
declare decimal
variables amount
and principal
, and double
variable rate
. Lines 10–11 also initialize principal
to 1000
(i.e., $1000.00) and rate
to 0.05
. C# treats real-number constants like 0.05
as type double
. Similarly, C# treats whole-number constants like 7
and 1000
as type int
. When principal
is initialized to 1000
, the value 1000
of type int
is promoted to a decimal
type implicitly—no cast is required.
Example 6.6. Compound-interest calculations with for
.
1 // Fig. 6.6: Interest.cs 2 // Compound-interest calculations with for. 3 using System; 4 5 public class Interest 6 { 7 public static void Main( string[] args ) 8 { 9 decimal amount; // amount on deposit at end of each year 10 decimal principal = 1000; // initial amount before interest 11 double rate = 0.05; // interest rate 12 13 // display headers 14 Console.WriteLine( "Year{0,20}", "Amount on deposit" ); 15 16 // calculate amount on deposit for each of ten years 17 for ( int year = 1; year <= 10; year++ ) 18 { 19 // calculate new amount for specified year 20 amount = principal * 21 ( ( decimal ) Math.Pow( 1.0 + rate, year ) ); 22 23 // display the year and the amount 24 Console.WriteLine( "{0,4}{1,20:C}", year, amount ); 25 } // end for 26 } // end Main 27 } // end class Interest
Line 14 outputs the headers for the application’s two columns of output. The first column displays the year, and the second column displays the amount on deposit at the end of that year. We use the format item {0,20}
to output the string "Amount on deposit"
. The integer 20
after the comma indicates that the value output should be displayed with a field width of 20—that is, WriteLine
displays the value with at least 20 character positions. If the value to be output is less than 20 character positions wide (17 characters in this example), the value is right justified in the field by default (in this case the value is preceded by three blanks). If the year
value to be output were more than four character positions wide, the field width would be extended to the right to accommodate the entire value—this would push the amount
field to the right, upsetting the neat columns of our tabular output. To indicate that output should be left justified, simply use a negative field width.
The for
statement (lines 17–25) executes its body 10 times, varying control variable year
from 1
to 10
in increments of 1
. This loop terminates when control variable year
becomes 11
. (year
represents n in the problem statement.)
Classes provide methods that perform common tasks on objects. In fact, most methods must be called on a specific object. For example, to output a greeting in Fig. 4.2, we called method DisplayMessage
on the myGradeBook
object. Many classes also provide methods that perform common tasks and cannot be called on objects—they must be called using a class name. Such methods are called static
methods. For example, C# does not include an exponentiation operator, so the designers of C#’s Math
class defined static
method Pow
for raising a value to a power. You can call a static
method by specifying the class name followed by the member access (.
) operator and the method name, as in
ClassName.methodName( arguments ) |
Console
methods Write
and WriteLine
are static
methods. In Chapter 7, you’ll learn how to implement static
methods in your own classes.
We use static
method Pow
of class Math
to perform the compound interest calculation in Fig. 6.6. Math.Pow(
x,
y)
calculates the value of x raised to the yth power. The method receives two double
arguments and returns a double
value. Lines 20–21 perform the calculation a = p (1 + r)n, where a is the amount
, p is the principal
, r is the rate
and n is the year
. Notice that, in this calculation, we need to multiply a decimal
value (principal
) by a double
value (the return value of Math.Pow
). C# will not implicitly convert double
to a decimal
type, or vice versa, because of the possible loss of information in either conversion, so line 21 contains a (decimal)
cast operator that explicitly converts the double
return value of Math.Pow
to a decimal
.
After each calculation, line 24 outputs the year and the amount on deposit at the end of that year. The year is output in a field width of four characters (as specified by {0,4}
). The amount is output as a currency value with the format item {1,20:C}
. The number 20
in the format item indicates that the value should be output right justified with a field width of 20 characters. The format specifier C
specifies that the number should be formatted as currency.
Notice that we declared the variables amount
and principal
to be of type decimal
rather than double
. Recall that we introduced type decimal
for monetary calculations in Section 4.11. We also use decimal
in Fig. 6.6 for this purpose. You may be curious as to why we do this. We are dealing with fractional parts of dollars and thus need a type that allows decimal points in its values. Unfortunately, floating-point numbers of type double
(or float
) can cause trouble in monetary calculations. Two double
dollar amounts stored in the machine could be 14.234 (which would normally be rounded to 14.23 for display purposes) and 18.673 (which would normally be rounded to 18.67 for display purposes). When these amounts are added, they produce the internal sum 32.907, which would normally be rounded to 32.91 for display purposes. Thus, your output could appear as
14.23 + 18.67 ------- 32.91 |
but a person adding the individual numbers as displayed would expect the sum to be 32.90. You’ve been warned! For people who work with programming languages that do not support a type for precise monetary calculations, Exercise 6.18 explores the use of integers to perform such calculations.
Do not use variables of type double
(or float
) to perform precise monetary calculations; use type decimal
instead. The imprecision of floating-point numbers can cause errors that will result in incorrect monetary values.
The body of the for
statement contains the calculation 1.0 + rate
, which appears as an argument to the Math.Pow
method. In fact, this calculation produces the same result each time through the loop, so repeating the calculation in every iteration of the loop is wasteful.
The do
...while
repetition statement is similar to the while
statement. In the while
, the application tests the loop-continuation condition at the beginning of the loop, before executing the loop’s body. If the condition is false, the body never executes. The do
...while
statement tests the loop-continuation condition after executing the loop’s body; therefore, the body always executes at least once. When a do
...while
statement terminates, execution continues with the next statement in sequence. Figure 6.7 uses a do
...while
(lines 11–15) to output the numbers 1–10.
Example 6.7. do
...while
repetition statement.
1 // Fig. 6.7: DoWhileTest.cs 2 // do...while repetition statement. 3 using System; 4 5 public class DoWhileTest 6 { 7 public static void Main( string[] args ) 8 { 9 int counter = 1; // initialize counter 10 11 do 12 { 13 Console.Write( "{0} ", counter ); 14 ++counter; 15 } while ( counter <= 10 ); // end do...while 16 17 Console.WriteLine(); // outputs a newline 18 } // end Main 19 } // end class DoWhileTest
1 2 3 4 5 6 7 8 9 10 |
Line 9 declares and initializes control variable counter
. Upon entering the do
...while
statement, line 13 outputs counter
’s value, and line 14 increments counter
. Then the application evaluates the loop-continuation test at the bottom of the loop (line 15). If the condition is true, the loop continues from the first body statement in the do
...while
(line 13). If the condition is false, the loop terminates, and the application continues with the next statement after the loop.
Figure 6.8 contains the UML activity diagram for the do
...while
statement. This diagram makes it clear that the loop-continuation condition is not evaluated until after the loop performs the action state at least once. Compare this activity diagram with that of the while
statement (Fig. 5.4). It’s not necessary to use braces in the do
...while
repetition statement if there’s only one statement in the body. However, most programmers include the braces to avoid confusion between the while
and do
...while
statements. For example,
while ( condition ) |
is normally the first line of a while
statement. A do
...while
statement with no braces around a single-statement body appears as:
do statement while ( condition ); |
which can be confusing. A reader may misinterpret the last line—while(
condition );
—as a while
statement containing an empty statement (the semicolon by itself). To avoid confusion, a do
...while
statement with one body statement can be written as follows:
do { statement } while ( condition ); |
We discussed the if
single-selection statement and the if
...else
double-selection statement in Chapter 5. C# provides the switch
multiple-selection statement to perform different actions based on the possible values of an expression. Each action is associated with the value of a constant integral expression or a constant string expression that the variable or expression on which the switch
is based may assume. A constant integral expression is any expression involving character and integer constants that evaluates to an integer value—i.e., values of type sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
and char
, or a constant from an enum
type (enum
is discussed in Section 7.10). A constant string expression is any expression composed of string
literals that always results in the same string
.
Figure 6.9 contains an enhanced version of the GradeBook
class introduced in Chapter 4 and further developed in Chapter 5. The version of the class we now present not only calculates the average of a set of numeric grades entered by the user, but uses a switch
statement to determine whether each grade is the equivalent of an A, B, C, D or F, then increments the appropriate grade counter. The class also displays a summary of the number of students who received each grade. Figure 6.10 shows sample input and output of the GradeBookTest
application that uses class GradeBook
to process a set of grades.
Example 6.9. GradeBook
class that uses a switch
statement to count A, B, C, D and F grades.
1 // Fig. 6.9: GradeBook.cs 2 // GradeBook class uses switch statement to count letter grades. 3 using System; 4 5 public class GradeBook 6 { 7 private int total; // sum of grades 8 private int gradeCounter; // number of grades entered 9 private int aCount; // count of A grades 10 private int bCount; // count of B grades 11 private int cCount; // count of C grades 12 private int dCount; // count of D grades 13 private int fCount; // count of F grades 14 15 // automatic property CourseName 16 public string CourseName { get; set; } 17 18 // constructor initializes automatic property CourseName; 19 // int instance variables are initialized to 0 by default 20 public GradeBook( string name ) 21 { 22 CourseName = name; // set CourseName to name 23 } // end constructor 24 25 // display a welcome message to the GradeBook user 26 public void DisplayMessage() 27 { 28 // CourseName gets the name of the course 29 Console.WriteLine( "Welcome to the grade book for {0}! ", 30 CourseName ); 31 } // end method DisplayMessage 32 33 // input arbitrary number of grades from user 34 public void InputGrades() 35 { 36 int grade; // grade entered by user 37 string input; // text entered by the user 38 39 Console.WriteLine( "{0} {1}", 40 "Enter the integer grades in the range 0-100.", 41 "Type <Ctrl> z and press Enter to terminate input:" ); 42 43 input = Console.ReadLine(); // read user input 44 45 // loop until user enters the end-of-file indicator (<Ctrl> z) 46 while ( input != null ) 47 { 48 grade = Convert.ToInt32( input ); // read grade off user input 49 total += grade; // add grade to total 50 ++gradeCounter; // increment number of grades 51 52 // call method to increment appropriate counter 53 IncrementLetterGradeCounter( grade ); 54 55 input = Console.ReadLine(); // read user input 56 } // end while 57 } // end method InputGrades 58 59 // add 1 to appropriate counter for specified grade 60 private void IncrementLetterGradeCounter( int grade ) 61 { 62 // determine which grade was entered 63 switch ( grade / 10 ) 64 { 65 case 9: // grade was in the 90s 66 case 10: // grade was 100 67 ++aCount; // increment aCount 68 break; // necessary to exit switch 69 case 8: // grade was between 80 and 89 70 ++bCount; // increment bCount 71 break; // exit switch 72 case 7: // grade was between 70 and 79 73 ++cCount; // increment cCount 74 break; // exit switch 75 case 6: // grade was between 60 and 69 76 ++dCount; // increment dCount 77 break; // exit switch 78 default: // grade was less than 60 79 ++fCount; // increment fCount 80 break; // exit switch 81 } // end switch 82 } // end method IncrementLetterGradeCounter 83 84 // display a report based on the grades entered by the user 85 public void DisplayGradeReport() 86 { 87 Console.WriteLine( " Grade Report:" ); 88 89 // if user entered at least one grade... 90 if ( gradeCounter != 0 ) 91 { 92 // calculate average of all grades entered 93 double average = ( double ) total / gradeCounter; 94 95 // output summary of results 96 Console.WriteLine( "Total of the {0} grades entered is {1}", 97 gradeCounter, total ); 98 Console.WriteLine( "Class average is {0:F}", average ); 99 Console.WriteLine( "{0}A: {1} B: {2} C: {3} D: {4} F: {5}", 100 "Number of students who received each grade: ", 101 aCount, // display number of A grades 102 bCount, // display number of B grades 103 cCount, // display number of C grades 104 dCount, // display number of D grades 105 fCount ); // display number of F grades 106 } // end if 107 else // no grades were entered, so output appropriate message 108 Console.WriteLine( "No grades were entered" ); 109 } // end method DisplayGradeReport 110 } // end class GradeBook
Example 6.10. Create GradeBook
object, input grades and display grade report.
1 // Fig. 6.10: GradeBookTest.cs 2 // Create GradeBook object, input grades and display grade report. 3 4 public class GradeBookTest 5 { 6 public static void Main( string[] args ) 7 { 8 // create GradeBook object myGradeBook and 9 // pass course name to constructor 10 GradeBook myGradeBook = new GradeBook( 11 "CS101 Introduction to C# Programming" ); 12 13 myGradeBook.DisplayMessage(); // display welcome message 14 myGradeBook.InputGrades(); // read grades from user 15 myGradeBook.DisplayGradeReport(); // display report based on grades 16 } // end Main 17 } // end class GradeBookTest
Welcome to the grade book for CS101 Introduction to C# Programming! Enter the integer grades in the range 0-100. Type <Ctrl> z and press Enter to terminate input: 99 92 45 100 57 63 76 14 92 ^Z Grade Report: Total of the 9 grades entered is 638 Class average is 70.89 Number of students who received each grade: A: 4 B: 0 C: 1 D: 1 F: 3 |
Class GradeBook
(Fig. 6.9) declares instance variables total
(line 7) and gradeCounter
(line 8), which keep track of the sum of the grades entered by the user and the number of grades entered, respectively. Lines 9–13 declare counter variables for each grade category. Class GradeBook
maintains total
, gradeCounter
and the five letter-grade counters as instance variables so that they can be used or modified in any of the class’s methods.
Like earlier versions of the class, class GradeBook
declares automatic property CourseName
(line 16) and method DisplayMessage
(lines 26–31) to display a welcome message to the user. The class also contains a constructor (lines 20–23) that initializes the course name. The constructor sets only the course name—the remaining seven instance variables are int
s and are initialized to 0
by default.
Class GradeBook
contains three additional methods—InputGrades
, IncrementLetterGradeCounter
and DisplayGradeReport
. Method InputGrades
(lines 34–57) reads an arbitrary number of integer grades from the user using sentinel-controlled repetition and updates instance variables total
and gradeCounter
. Method InputGrades
calls method IncrementLetterGradeCounter
(lines 60–82) to update the appropriate letter-grade counter for each grade entered. Class GradeBook
also contains method DisplayGradeReport
(lines 85–109), which outputs a report containing the total of all grades entered, the average of the grades and the number of students who received each letter grade. Let’s examine these methods in more detail.
Lines 36–37 in method InputGrades
declare variables grade
and input
, which will first store the user’s input as a string
(in the variable input
), then convert it to an int
to store in the variable grade
. Lines 39–41 prompt the user to enter integer grades and to type Ctrl + z, then press Enter to terminate the input. The notation Ctrl + z means to simultaneously press both the Ctrl key and the z key when typing in a Command Prompt. Ctrl + z is the Windows key sequence for typing the end-of-file indicator. This is one way to inform an application that there’s no more data to input. If Ctrl + z is entered while the application is awaiting input with a ReadLine
method, null
is returned. (The end-of-file indicator is a system-dependent keystroke combination. On many non-Windows systems, end-of-file is entered by typing Ctrl + d.) In Chapter 17, Files and Streams, we’ll see how the end-of-file indicator is used when an application reads its input from a file. [Note: Windows typically displays the characters ^Z
in a Command Prompt when the end-of-file indicator is typed, as shown in the output of Fig. 6.10.]
Line 43 uses the ReadLine
method to get the first line that the user entered and store it in variable input
. The while
statement (lines 46–56) processes this user input. The condition at line 46 checks whether the value of input
is a null
reference. The Console
class’s ReadLine
method will return null
only if the user typed an end-of-file indicator. As long as the end-of-file indicator has not been typed, input
will not contain a null
reference, and the condition will pass.
Line 48 converts the string
in input
to an int
type. Line 49 adds grade
to total
. Line 50 increments gradeCounter
. The class’s DisplayGradeReport
method uses these variables to compute the average of the grades. Line 53 calls the class’s IncrementLetterGradeCounter
method (declared in lines 60–82) to increment the appropriate letter-grade counter, based on the numeric grade entered.
Method IncrementLetterGradeCounter
contains a switch
statement (lines 63–81) that determines which counter to increment. In this example, we assume that the user enters a valid grade in the range 0–100. A grade in the range 90–100 represents A, 80–89 represents B, 70–79 represents C, 60–69 represents D and 0–59 represents F. The switch
statement consists of a block that contains a sequence of case
labels and an optional default
label. These are used in this example to determine which counter to increment based on the grade.
When the flow of control reaches the switch
statement, the application evaluates the expression in the parentheses (grade / 10
) following keyword switch
—this is called the switch
expression. The application attempts to match the value of the switch
expression with one of the case
labels. The switch
expression in line 63 performs integer division, which truncates the fractional part of the result. Thus, when we divide any value in the range 0–100 by 10, the result is always a value from 0 to 10. We use several of these values in our case
labels. For example, if the user enters the integer 85
, the switch
expression evaluates to int
value 8
. If a match occurs between the switch
expression and a case
(case 8:
at line 69), the application executes the statements for that case
. For the integer 8
, line 70 increments bCount
, because a grade in the 80s is a B. The break
statement (line 71) causes program control to proceed with the first statement after the switch
—in this application, we reach the end of method IncrementLetterGradeCounter
’s body, so control returns to line 55 in method InputGrades
(the first line after the call to IncrementLetterGradeCounter
). This line uses the ReadLine
method to read the next line entered by the user and assign it to the variable input
. Line 56 marks the end of the body of the while
statement that inputs grades (lines 46–56), so control flows to the while
’s condition (line 46) to determine whether the loop should continue executing based on the value just assigned to the variable input
.
The case
s in our switch
explicitly test for the values 10
, 9
, 8
, 7
and 6
. Note the case
labels at lines 65–66 that test for the values 9
and 10
(both of which represent the grade A). Listing case
labels consecutively in this manner with no statements between them enables the case
s to perform the same set of statements—when the switch
expression evaluates to 9
or 10
, the statements in lines 67–68 execute. The switch
statement does not provide a mechanism for testing ranges of values, so every value to be tested must be listed in a separate case
label. Each case
can have multiple statements. The switch
statement differs from other control statements in that it does not require braces around multiple statements in each case
.
In C, C++, and many other programming languages that use the switch
statement, the break
statement is not required at the end of a case
. Without break
statements, each time a match occurs in the switch
, the statements for that case
and subsequent case
s execute until a break
statement or the end of the switch
is encountered. This is often referred to as “falling through” to the statements in subsequent case
s. This frequently leads to logic errors when you forget the break
statement. For this reason, C# has a “no fall through” rule for case
s in a switch
—after the statements in a case
, you are required to include a statement that terminates the case
, such as a break
, a return
or a throw
. (We discuss the throw
statement in Chapter 13, Exception Handling: A Deeper Look.)
If no match occurs between the switch
expression’s value and a case
label, the statements after the default
label (lines 79–80) execute. We use the default
label in this example to process all switch
-expression values that are less than 6
—that is, all failing grades. If no match occurs and the switch
does not contain a default
label, program control simply continues with the first statement (if there is one) after the switch
statement.
Class GradeBookTest
(Fig. 6.10) creates a GradeBook
object (lines 10–11). Line 13 invokes the object’s DisplayMessage
method to output a welcome message to the user. Line 14 invokes the object’s InputGrades
method to read a set of grades from the user and keep track of the sum of all the grades entered and the number of grades. Recall that method InputGrades
also calls method IncrementLetterGradeCounter
to keep track of the number of students who received each letter grade. Line 15 invokes method DisplayGradeReport
of class GradeBook
, which outputs a report based on the grades entered. Line 90 of class GradeBook
(Fig. 6.9) determines whether the user entered at least one grade—this avoids dividing by zero. If so, line 93 calculates the average of the grades. Lines 96–105 then output the total of all the grades, the class average and the number of students who received each letter grade. If no grades were entered, line 108 outputs an appropriate message. The output in Fig. 6.10 shows a sample grade report based on 9 grades.
Class GradeBookTest
(Fig. 6.10) does not directly call GradeBook
method IncrementLetterGradeCounter
(lines 60–82 of Fig. 6.9). This method is used exclusively by method InputGrades
of class GradeBook
to update the appropriate letter-grade counter as each new grade is entered by the user. Method IncrementLetterGradeCounter
exists solely to support the operations of class GradeBook
’s other methods and thus is declared private
. Members of a class declared with access modifier private
can be accessed only by members of the class in which the private
members are declared. When a private
member is a method, it’s commonly referred to as a utility method or helper method, because it can be called only by other members of that class and is used to support the operation of those other members.
Figure 6.11 shows the UML activity diagram for the general switch
statement. Every set of statements after a case
label normally ends its execution with a break
or return
statement to terminate the switch
statement after processing the case
. Typically, you’ll use break
statements. Figure 6.11 emphasizes this by including break
statements in the activity diagram. The diagram makes it clear that the break
statement at the end of a case
causes control to exit the switch
statement immediately.
Although each case
and the default
label in a switch
can occur in any order, place the default
label last for clarity.
When using the switch
statement, remember that the expression after each case
can be only a constant integral expression or a constant string expression—that is, any combination of constants that evaluates to a constant value of an integral or string
type. An integer constant is simply an integer value (e.g., −7, 0 or 221). In addition, you can use character constants—specific characters in single quotes, such as 'A'
, '7'
or '$'
—which represent the integer values of characters. (Appendix C shows the integer values of the characters in the ASCII character set, which is a popular subset of the Unicode character set used by C#.) A string
constant (or string
literal) is a sequence of characters in double quotes, such as "Welcome to C# Programming!"
. For string
s, you can also use null
.
The expression in each case
also can be a constant—a value which does not change for the entire application. Constants are declared with the keyword const
(discussed in Chapter 7). C# also has a feature called enumerations, which we also present in Chapter 7. Enumeration constants can also be used in case
labels. In Chapter 12, we present a more elegant way to implement switch
logic—we use a technique called polymorphism to create applications that are often clearer, easier to maintain and easier to extend than applications using switch
logic.
In addition to selection and repetition statements, C# provides statements break
and continue
to alter the flow of control. The preceding section showed how break
can be used to terminate a switch
statement’s execution. This section discusses how to use break
to terminate any repetition statement.
The break
statement, when executed in a while
, for
, do
...while
, switch
, or foreach
, causes immediate exit from that statement. Execution typically continues with the first statement after the control statement—you’ll see that there are other possibilities as you learn about additional statement types in C#. Common uses of the break
statement are to escape early from a repetition statement or to skip the remainder of a switch
(as in Fig. 6.9). Figure 6.12 demonstrates a break
statement exiting a for
.
Example 6.12. break
statement exiting a for
statement.
1 // Fig. 6.12: BreakTest.cs 2 // break statement exiting a for statement. 3 using System; 4 5 public class BreakTest 6 { 7 public static void Main( string[] args ) 8 { 9 int count; // control variable also used after loop terminates 10 11 for ( count = 1; count <= 10; count++ ) // loop 10 times 12 { 13 if ( count == 5 ) // if count is 5, 14 break; // terminate loop 15 16 Console.Write( "{0} ", count ); 17 } // end for 18 19 Console.WriteLine( " Broke out of loop at count = {0}", count ); 20 } // end Main 21 } // end class BreakTest
When the if
nested at line 13 in the for
statement (lines 11–17) determines that count
is 5
, the break
statement at line 14 executes. This terminates the for
statement, and the application proceeds to line 19 (immediately after the for
statement), which displays a message indicating the value of the control variable when the loop terminated. The loop fully executes its body only four times instead of 10 because of the break
.
The continue
statement, when executed in a while
, for
, do
...while
, or foreach
, skips the remaining statements in the loop body and proceeds with the next iteration of the loop. In while
and do
...while
statements, the application evaluates the loop-continuation test immediately after the continue
statement executes. In a for
statement, the increment expression normally executes next, then the application evaluates the loop-continuation test.
Figure 6.13 uses the continue
statement in a for
to skip the statement at line 14 when the nested if
(line 11) determines that the value of count
is 5
. When the continue
statement executes, program control continues with the increment of the control variable in the for
statement (line 9).
Example 6.13. continue
statement terminating an iteration of a for
statement.
1 // Fig. 6.13: ContinueTest.cs 2 // continue statement terminating an iteration of a for statement. 3 using System; 4 5 public class ContinueTest 6 { 7 public static void Main( string[] args ) 8 { 9 for ( int count = 1; count <= 10; count++ ) // loop 10 times 10 { 11 if ( count == 5 ) // if count is 5, 12 continue; // skip remaining code in loop 13 14 Console.Write( "{0} ", count ); 15 } // end for 16 17 Console.WriteLine( " Used continue to skip displaying 5" ); 18 } // end Main 19 } // end class ContinueTest
In Section 6.3, we stated that the while
statement can be used in most cases in place of for
. One exception occurs when the increment expression in the while
follows a continue
statement. In this case, the increment does not execute before the application evaluates the repetition-continuation condition, so the while
does not execute in the same manner as the for
.
Some programmers feel that break
and continue
statements violate structured programming. Since the same effects are achievable with structured programming techniques, these programmers prefer not to use break
or continue
statements.
There’s a tension between achieving quality software engineering and achieving the best-performing software. Often, one of these goals is achieved at the expense of the other. For all but the most performance-intensive situations, apply the following rule: First, make your code simple and correct; then make it fast, but only if necessary.
The if
, if
...else
, while
, do
...while
and for
statements each require a condition to determine how to continue an application’s flow of control. So far, we’ve studied only simple conditions, such as count <= 10
, number != sentinelValue
and total > 1000
. Simple conditions are expressed in terms of the relational operators >
, <
, >=
and <=
, and the equality operators ==
and !=
. Each expression tests only one condition. To test multiple conditions in the process of making a decision, we performed these tests in separate statements or in nested if
or if
...else
statements. Sometimes, control statements require more complex conditions to determine an application’s flow of control.
C# provides logical operators to enable you to form more complex conditions by combining simple conditions. The logical operators are &&
(conditional AND), ||
(conditional OR), &
(boolean logical AND), |
(boolean logical inclusive OR), ^
(boolean logical exclusive OR) and !
(logical negation).
Suppose that we wish to ensure at some point in an application that two conditions are both true before we choose a certain path of execution. In this case, we can use the &&
(conditional AND) operator, as follows:
if ( gender == 'F' && age >= 65 ) ++seniorFemales; |
This if
statement contains two simple conditions. The condition gender == 'F'
determines whether a person is female. The condition age >= 65
might be evaluated to determine whether a person is a senior citizen. The if
statement considers the combined condition
gender == 'F' && age >= 65 |
which is true if and only if both simple conditions are true. If the combined condition is true, the if
statement’s body increments seniorFemales
by 1
. If either or both of the simple conditions are false, the application skips the increment. Some programmers find that the preceding combined condition is more readable when redundant parentheses are added, as in:
( gender == 'F' ) && ( age >= 65 ) |
The table in Fig. 6.14 summarizes the &&
operator. The table shows all four possible combinations of false
and true
values for expression1 and expression2. Such tables are called truth tables. C# evaluates all expressions that include relational operators, equality operators or logical operators to bool
values—which are either true
or false
.
Now suppose we wish to ensure that either or both of two conditions are true before we choose a certain path of execution. In this case, we use the ||
(conditional OR) operator, as in the following application segment:
if ( ( semesterAverage >= 90 ) || ( finalExam >= 90 ) ) Console.WriteLine ( "Student grade is A" ); |
This statement also contains two simple conditions. The condition semesterAverage >=
90 is evaluated to determine whether the student deserves an A in the course because of a solid performance throughout the semester. The condition finalExam >= 90
is evaluated to determine whether the student deserves an A in the course because of an outstanding performance on the final exam. The if
statement then considers the combined condition
( semesterAverage >= 90 ) || ( finalExam >= 90 ) |
and awards the student an A if either or both of the simple conditions are true. The only time the message "Student grade is A"
is not displayed is when both of the simple conditions are false. Figure 6.15 is a truth table for operator conditional OR (||
). Operator &&
has a higher precedence than operator ||
. Both operators associate from left to right.
The parts of an expression containing &&
or ||
operators are evaluated only until it’s known whether the condition is true or false. Thus, evaluation of the expression
( gender == 'F' ) && ( age >= 65 ) |
stops immediately if gender
is not equal to 'F'
(i.e., at that point, it’s certain that the entire expression is false
) and continues if gender
is equal to 'F'
(i.e., the entire expression could still be true
if the condition age >= 65
is true
). This feature of conditional AND and conditional OR expressions is called short-circuit evaluation.
In expressions using operator &&
, a condition—which we refer to as the dependent condition—may require another condition to be true for the evaluation of the dependent condition to be meaningful. In this case, the dependent condition should be placed after the other condition, or an error might occur. For example, in the expression (i != 0) && (10 / i == 2)
, the second condition must appear after the first condition, or a divide-by-zero error might occur.
The boolean logical AND (&
) and boolean logical inclusive OR (|
) operators work identically to the &&
(conditional AND) and ||
(conditional OR) operators, with one exception—the boolean logical operators always evaluate both of their operands (i.e., they do not perform short-circuit evaluation). Therefore, the expression
( gender == 'F' ) & ( age >= 65 ) |
evaluates age >= 65
regardless of whether gender
is equal to 'F'
. This is useful if the right operand of the boolean logical AND or boolean logical inclusive OR operator has a required side effect—such as, a modification of a variable’s value. For example, the expression
( birthday == true ) | ( ++age >= 65 ) |
guarantees that the condition ++age >= 65
will be evaluated. Thus, the variable age
is incremented in the preceding expression, regardless of whether the overall expression is true
or false
.
A complex condition containing the boolean logical exclusive OR (^
) operator (also called the logical XOR operator) is true
if and only if one of its operands is true
and the other is false
. If both operands are true
or both are false
, the entire condition is false
. Figure 6.16 is a truth table for the boolean logical exclusive OR operator (^
). This operator is also guaranteed to evaluate both of its operands.
The !
(logical negation or not) operator enables you to “reverse” the meaning of a condition. Unlike the logical operators &&
, ||
, &
, |
and ^
, which are binary operators that combine two conditions, the logical negation operator is a unary operator that has only a single condition as an operand. The logical negation operator is placed before a condition to choose a path of execution if the original condition (without the logical negation operator) is false
, as in the code segment
if ( ! ( grade == sentinelValue ) ) Console.WriteLine( "The next grade is {0}", grade ); |
which executes the WriteLine
call only if grade
is not equal to sentinelValue
. The parentheses around the condition grade == sentinelValue
are needed because the logical negation operator has a higher precedence than the equality operator.
In most cases, you can avoid using logical negation by expressing the condition differently with an appropriate relational or equality operator. For example, the previous statement may also be written as follows:
if ( grade != sentinelValue ) Console.WriteLine( "The next grade is {0}", grade ); |
This flexibility can help you express a condition in a more convenient manner. Figure 6.17 is a truth table for the logical negation operator.
Figure 6.18 demonstrates the logical operators and boolean logical operators by producing their truth tables. The output shows the expression that was evaluated and the bool
result of that expression. Lines 10–14 produce the truth table for &&
(conditional AND). Lines 17–21 produce the truth table for ||
(conditional OR). Lines 24–28 produce the truth table for &
(boolean logical AND). Lines 31–36 produce the truth table for |
(boolean logical inclusive OR). Lines 39–44 produce the truth table for ^
(boolean logical exclusive OR). Lines 47–49 produce the truth table for !
(logical negation).
Example 6.18. Logical operators.
1 // Fig. 6.18: LogicalOperators.cs 2 // Logical operators. 3 using System; 4 5 public class LogicalOperators 6 { 7 public static void Main( string[] args ) 8 { 9 // create truth table for && (conditional AND) operator 10 Console.WriteLine( "{0} {1}: {2} {3}: {4} {5}: {6} {7}: {8} ", 11 "Conditional AND (&&)", "false && false", ( false && false ), 12 "false && true", ( false && true ), 13 "true && false", ( true && false ), 14 "true && true", ( true && true ) ); 15 16 // create truth table for || (conditional OR) operator 17 Console.WriteLine( "{0} {1}: {2} {3}: {4} {5}: {6} {7}: {8} ", 18 "Conditional OR (||)", "false || false", ( false || false ), 19 "false || true", ( false || true ), 20 "true || false", ( true || false ), 21 "true || true", ( true || true ) ); 22 23 // create truth table for & (boolean logical AND) operator 24 Console.WriteLine( "{0} {1}: {2} {3}: {4} {5}: {6} {7}: {8} ", 25 "Boolean logical AND (&)", "false & false", ( false & false ), 26 "false & true", ( false & true ), 27 "true & false", ( true & false ), 28 "true & true", ( true & true ) ); 29 30 // create truth table for | (boolean logical inclusive OR) operator 31 Console.WriteLine( "{0} {1}: {2} {3}: {4} {5}: {6} {7}: {8} ", 32 "Boolean logical inclusive OR (|)", 33 "false | false", ( false | false ), 34 "false | true", ( false | true ), 35 "true | false", ( true | false ), 36 "true | true", ( true | true ) ); 37 38 // create truth table for ^ (boolean logical exclusive OR) operator 39 Console.WriteLine( "{0} {1}: {2} {3}: {4} {5}: {6} {7}: {8} ", 40 "Boolean logical exclusive OR (^)", 41 "false ^ false", ( false ^ false ), 42 "false ^ true", ( false ^ true ), 43 "true ^ false", ( true ^ false ), 44 "true ^ true", ( true ^ true ) ); 45 46 // create truth table for ! (logical negation) operator 47 Console.WriteLine( "{0} {1}: {2} {3}: {4}", 48 "Logical negation (!)", "!false", ( !false ), 49 "!true", ( !true ) ); 50 } // end Main 51 } // end class LogicalOperators
Conditional AND (&&) false && false: False false && true: False true && false: False true && true: True Conditional OR (||) false || false: False false || true: True true || false: True true || true: True Boolean logical AND (&) false & false: False false & true: False true & false: False true & true: True Boolean logical inclusive OR (|) false | false: False false | true: True true | false: True true | true: True Boolean logical exclusive OR (^) false ^ false: False false ^ true: True true ^ false: True true ^ true: False Logical negation (!) !false: True !true: False |
Figure 6.19 shows the precedence and associativity of the C# operators introduced so far. The operators are shown from top to bottom in decreasing order of precedence.
Table 6.19. Precedence/associativity of the operators discussed so far.
Operators | Associativity | Type | |||||
---|---|---|---|---|---|---|---|
|
|
|
| left to right | highest precedence | ||
|
|
|
|
|
| right to left | unary prefix |
|
|
| left to right | multiplicative | |||
| left to right | additive | |||||
|
|
|
| left to right | relational | ||
|
| left to right | equality | ||||
| left to right | boolean logical AND | |||||
| left to right | boolean logical exclusive OR | |||||
| left to right | boolean logical inclusive OR | |||||
| left to right | conditional AND | |||||
| left to right | conditional OR | |||||
| right to left | conditional | |||||
|
|
|
|
|
| right to left | assignment |
Just as architects design buildings by employing the collective wisdom of their profession, so should programmers design applications. Our field is younger than architecture, and our collective wisdom is considerably sparser. We’ve learned that structured programming produces applications that are easier than unstructured applications to understand, test, debug, modify and even prove correct in a mathematical sense.
Figure 6.20 uses UML activity diagrams to summarize C#’s control statements. The initial and final states indicate the single entry point and the single exit point of each control statement. Arbitrarily connecting individual symbols in an activity diagram can lead to unstructured applications. Therefore, a limited set of control statements can be combined in only two simple ways to build structured applications.
For simplicity, only single-entry/single-exit control statements are used—there’s only one way to enter and only one way to exit each control statement. Connecting control statements in sequence to form structured applications is simple. The final state of one control statement is connected to the initial state of the next—that is, the control statements are placed one after another in an application in sequence. We call this “control-statement stacking.” The rules for forming structured applications also allow for control statements to be nested.
Figure 6.21 shows the rules for forming structured applications. The rules assume that action states may be used to indicate any action. The rules also assume that we begin with the simplest activity diagram (Fig. 6.22) consisting of only an initial state, an action state, a final state and transition arrows.
Table 6.21. Rules for forming structured applications.
1 | Begin with the simplest activity diagram (Fig. 6.22). |
2 | Any action state can be replaced by two action states in sequence. |
3 | Any action state can be replaced by any control statement (sequence of action states, |
4 | Rules 2 and 3 can be applied as often as necessary in any order. |
Applying the rules in Fig. 6.21 always results in a properly structured activity diagram with a neat, building-block appearance. For example, repeatedly applying Rule 2 to the simplest activity diagram results in an activity diagram containing many action states in sequence (Fig. 6.23). Rule 2 generates a stack of control statements, so let us call Rule 2 the stacking rule. [Note: The vertical dashed lines in Fig. 6.23 are not part of the UML—we use them to separate the four activity diagrams that demonstrate the application of Rule 2 of Fig. 6.21.]
Figure 6.23. Repeatedly applying the stacking rule (Rule 2) of Fig. 6.21 to the simplest activity diagram.
Rule 3 is called the nesting rule. Repeatedly applying Rule 3 to the simplest activity diagram results in an activity diagram with neatly nested control statements. For example, in Fig. 6.24, the action state in the simplest activity diagram is replaced with a double-selection (if
...else
) statement. Then Rule 3 is applied again to the action states in the double-selection statement, replacing each of these action states with a double-selection statement. The dashed action-state symbols around each of the double-selection statements represent the action state that was replaced. [Note: The dashed arrows and dashed action-state symbols shown in Fig. 6.24 are not part of the UML. They’re used here to illustrate that any action state can be replaced with any control statement.]
Rule 4 generates larger, more involved and more deeply nested statements. The diagrams that emerge from applying the rules in Fig. 6.21 constitute the set of all possible structured activity diagrams and hence the set of all possible structured applications. The beauty of the structured approach is that we use only eight simple single-entry/single-exit control statements (counting the foreach
statement, which we introduce in Section 8.6) and assemble them in only two simple ways.
If the rules in Fig. 6.21 are followed, an “unstructured” activity diagram (like the one in Fig. 6.25) cannot be created. If you are uncertain about whether a particular diagram is structured, apply the rules of Fig. 6.21 in reverse to reduce the diagram to the simplest activity diagram. If you can reduce it, the original diagram is structured; otherwise, it’s not.
Structured programming promotes simplicity. Bohm and Jacopini have shown that only three forms of control are needed to implement any algorithm:
sequence
selection
repetition
Sequence is trivial. Simply list the statements of the sequence in the order in which they should execute. Selection is implemented in one of three ways:
if
statement (single selection)
if
...else
statement (double selection)
switch
statement (multiple selection)
In fact, it’s straightforward to prove that the simple if
statement is sufficient to provide any form of selection—everything that can be done with the if
...else
statement and the switch
statement can be done by combining if
statements (although perhaps not as clearly and efficiently).
Repetition is implemented in one of four ways:
It’s straightforward to prove that the while
statement is sufficient to provide any form of repetition. Everything that can be done with the do
...while
, for
and foreach
statements can be done with the while
statement (although perhaps not as conveniently).
Combining these results illustrates that any form of control ever needed in a C# application can be expressed in terms of
sequence structure
if
statement (selection)
while
statement (repetition)
and that these can be combined in only two ways—stacking and nesting. Indeed, structured programming is the essence of simplicity.
Chapter 5 discussed the if
, if
...else
and while
control statements. In this chapter, we discussed the for
, do
...while
and switch
control statements. (We’ll discuss the foreach
statement in Chapter 8.) You learned that any algorithm can be developed using combinations of sequence (i.e., statements listed in the order in which they should execute), the three selection statements—if
, if
...else
and switch
—and the four repetition statements—while
, do
...while
, for
and foreach
. You saw that the for
and do
...while
statements are simply more convenient ways to express certain types of repetition. Similarly, we showed that the switch
statement is a convenient notation for multiple selection, rather than using nested if
...else
statements. We discussed how you can combine various control statements by stacking and nesting them. We showed how to use the break
and continue
statements to alter the flow of control in repetition statements. You learned about the logical operators, which enable you to use more complex conditional expressions in control statements.
In Chapter 4, we introduced the basic concepts of objects, classes and methods. Chapters 5 and 6 provided a thorough introduction to the control statements that you use to specify application logic in methods. In Chapter 7, we examine methods in greater depth.
Counter-controlled repetition requires a control variable, the initial value of the control variable, the increment (or decrement) by which the control variable is modified each time through the loop and the loop-continuation condition that determines whether looping should continue.
The for
header “does it all”—it specifies each of the items needed for counter-controlled repetition with a control variable. The general format of the for
statement is
for ( initialization; loopContinuationCondition; increment ) statement
where the initialization expression names the loop’s control variable and provides its initial value, the loopContinuationCondition is the condition that determines whether looping should continue and the increment modifies the control variable’s value so that the loop-continuation condition eventually becomes false
.
Typically, for
statements are used for counter-controlled repetition, and while
statements for sentinel-controlled repetition.
The scope of a variable defines where it can be used in an application. For example, a local variable can be used only in the method that declares the variable and only from the point of declaration through the end of the block in which the variable is declared.
The increment of a for
statement may also be negative, in which case it’s a decrement, and the loop counts downward.
If the loop-continuation condition is initially false
, the application does not execute the for
statement’s body.
When a variable of type decimal
is initialized to an int
value, the value of type int
is promoted to a decimal
type implicitly—no cast is required.
In a format item, an integer n after a comma indicates that the value output should be displayed with a field width of n—that is, Write
(or WriteLine
) displays the value with at least n character positions.
Values are right justified in a field by default. To indicate that values should be output left justified, simply use a negative field width.
Methods that must be called using a class name are called static
methods.
C# does not include an exponentiation operator. Instead, Math.Pow(x, y)
calculates the value of x raised to the yth power. The method receives two double
arguments and returns a double
value.
C# will not implicitly convert a double
to a decimal
type, or vice versa, because of the possible loss of information in either conversion. To perform this conversion, a cast operator is required.
Floating-point numbers of type double
(or float
) can cause trouble in monetary calculations; use type decimal
instead.
The do
...while
statement tests the loop-continuation condition after executing the loop’s body; therefore, the body always executes at least once.
The do
...while
statement has the form:
do { statement } while ( condition );
The switch
multiple-selection statement performs different actions based on the possible values of an expression.
Method Console.ReadLine
returns null
when the end-of-file key sequence is encountered.
The switch
statement consists of a block that contains a sequence of case
labels and an optional default
label.
The expression in parentheses following keyword switch
is the switch
expression. The application attempts to match the value of the switch
expression to a case
label. If a match occurs, the application executes the statements for that case
.
The switch
statement does not provide a mechanism for testing ranges of values, so every value to be tested must be listed in a separate case
label.
After the statements in a case
execute, you are required to include a statement that terminates the case
, such as a break
or a return
.
If no match occurs between the switch expression’s value and a case
label, the statements after the default
label execute. If no match occurs and the switch
does not contain a default
label, program control typically continues with the first statement after the switch
statement.
The break
statement causes immediate exit from a while
, for
, do
...while
, switch
or foreach
statement. Execution typically continues with the first statement after the control statement.
The continue
statement, when executed in a while
, for
, do
...while
or foreach
, skips the remaining statements in the loop body and proceeds with the next iteration of the loop. In a for
statement, the increment is peformed before the loop-continuation condition is tested.
Logical operators enable you to form more complex conditions by combining simple conditions. The logical operators are &&
(conditional AND), ||
(conditional OR), &
(boolean logical AND), |
(boolean logical inclusive OR), ^
(boolean logical exclusive OR) and !
(logical negation).
The &&
(conditional AND) operator ensures that two conditions are both true
before we choose a certain path of execution.
The ||
(conditional OR) operator ensures that either or both of two conditions are true
before we choose a certain path of execution.
The parts of an expression containing &&
or ||
operators are evaluated only until it’s known whether the condition is true
or false
. This feature of conditional AND and conditional OR expressions is called short-circuit evaluation.
The boolean logical AND (&
) and boolean logical inclusive OR (|
) operators work identically to the &&
(conditional AND) and ||
(conditional OR) operators, but the boolean logical operators always evaluate both of their operands (i.e., they do not perform short-circuit evaluation).
A complex condition containing the boolean logical exclusive OR (^
) operator is true
if and only if one of its operands is true
and the other is false
. If both operands are true
or both are false
, the entire condition is false
.
The !
(logical negation) operator enables you to “reverse” the meaning of a condition. The logical negation operator is placed before a condition to choose a path of execution if the original condition is false
. In most cases, you can avoid using logical negation by expressing the condition differently with an appropriate relational or equality operator.
state machine diagram
6.5 | Describe the four basic elements of counter-controlled repetition. | ||||||||
6.6 | Compare and contrast the | ||||||||
6.7 | Discuss a situation in which it would be more appropriate to use a | ||||||||
6.8 | Compare and contrast the | ||||||||
6.9 | Find and correct the error(s) in each of the following segments of code:
| ||||||||
6.10 | (What Does This Code Do?) What does the following application do? 1 // Exercise 6.10 Solution: Printing.cs 2 using System; 3 4 public class Printing 5 { 6 public static void Main( string[] args ) 7 { 8 for ( int i = 1; i <= 10; i++ ) 9 { 10 for ( int j = 1; j <= 5; j++ ) 11 Console.Write( '@' ); 12 13 Console.WriteLine(); 14 } // end outer for 15 } // end Main 16 } // end class Printing | ||||||||
6.11 | (Find the Smallest Value) Write an application that finds the smallest of several integers. Assume that the first value read specifies the number of values to input from the user. | ||||||||
6.12 | (Product of Odd Integers) Write an application that calculates the product of the odd integers from 1 to 7. | ||||||||
6.13 | (Factorials) Factorials are used frequently in probability problems. The factorial of a positive integer n (written n! and pronounced “n factorial”) is equal to the product of the positive integers from 1 to n. Write an application that evaluates the factorials of the integers from 1 to 5. Display the results in tabular format. What difficulty might prevent you from calculating the factorial of 20? | ||||||||
6.14 | (Modified Compound Interest Program) Modify the compound-interest application (Fig. 6.6) to repeat its steps for interest rates of 5, 6, 7, 8, 9 and 10%. Use a | ||||||||
6.15 | (Triangle Printing Program) Write an application that displays the following patterns separately, one below the other. Use
| ||||||||
6.16 | (Bar Chart Printing) One interesting application of computers is to display graphs and bar charts. Write an application that reads three numbers between 1 and 30. For each number that is read, your application should display the same number of adjacent asterisks. For example, if your application reads the number 7, it should display | ||||||||
6.17 | (Calculating Sales) A website sells three products whose retail prices are as follows: product 1, $2.98; product 2, $4.50; and product 3, $9.98. Write an application that reads a series of pairs of numbers as follows:
Your application should use a | ||||||||
6.18 | (Modified Compound Interest Program) In the future, you may work with other programming languages that do not have a type like | ||||||||
6.19 | Assume that
| ||||||||
6.20 | (Calculating the Value of π) Calculate the value of π from the infinite series Display a table that shows the value of π approximated by computing one term of this series, by two terms, by three terms, and so on. How many terms of this series do you have to use before you first get 3.14? 3.141? 3.1415? 3.14159? | ||||||||
6.21 | (Pythagorean Triples) A right triangle can have sides whose lengths are all integers. The set of three integer values for the lengths of the sides of a right triangle is called a Pythagorean triple. The lengths of the three sides must satisfy the relationship that the sum of the squares of two of the sides is equal to the square of the hypotenuse. Write an application to find all Pythagorean triples for | ||||||||
6.22 | (Modified Triangle Printing Program) Modify Exercise 6.15 to combine your code from the four separate triangles of asterisks such that all four patterns display side by side. Make clever use of nested | ||||||||
6.23 | (Diamond Printing Program) Write an application that displays the following diamond shape. You may use output statements that display a single asterisk (
| ||||||||
6.24 | (Modified Diamond Printing Program) Modify the application you wrote in Exercise 6.23 to read an odd number in the range 1 to 19 to specify the number of rows in the diamond. Your application should then display a diamond of the appropriate size. | ||||||||
6.25 | (Structured Equivalent of | ||||||||
6.26 | (What Does This Code Do?) What does the following code segment do? for ( int i = 1; i <= 5; i++ ) { for ( int j = 1; j <= 3; j++ ) { for ( int k = 1; k <= 4; k++ ) Console.Write( '*' ); Console.WriteLine(); } // end middle for Console.WriteLine(); } // end outer for | ||||||||
6.27 | (Structured Equivalent of |