Chapter 5. Control Statements: Part 1

 

Let’s all move one place on.

 
 --Lewis Carroll
 

The wheel is come full circle.

 
 --William Shakespeare
 

How many apples fell on Newton’s head before he took the hint!

 
 --Robert Frost
 

All the evolution we know of proceeds from the vague to the definite.

 
 --Charles Sanders Peirce
<feature> <supertitle>Objectives</supertitle>

In this chapter you’ll learn:

<objective>

Basic problem solving techniques.

</objective>
<objective>

To develop algorithms through the process of top-down, stepwise refinement.

</objective>
<objective>

To use the if and if...else selection statements to choose between actions.

</objective>
<objective>

To use the while statement to execute statements in an application repeatedly.

</objective>
<objective>

To use counter-controlled repetition and sentinel-controlled repetition.

</objective>
<objective>

To use the increment, decrement and compound assignment operators.

</objective>
</feature>
<feature> <supertitle>Outline</supertitle> </feature>

Introduction

Before writing an application to solve a problem, we must have a thorough understanding of the problem and a carefully planned approach to solving it. When writing an application, we must understand the types of building blocks that are available and employ proven application-construction techniques. In this chapter and in Chapter 6, Control Statements: Part 2, we discuss these issues in our presentation of the theory and principles of structured programming. The concepts presented here are crucial to building classes and manipulating objects. We introduce C#’s if, if...else and while control statements, three of the building blocks that allow you to specify the logic required for methods to perform their tasks.

Algorithms

Any computing problem can be solved by executing a series of actions in a specific order. A procedure for solving a problem in terms of

  1. the actions to execute and

  2. the order in which these actions execute

is called an algorithm. The following example demonstrates that correctly specifying the order in which the actions execute is important.

Consider the “rise-and-shine algorithm” followed by one junior executive for getting out of bed and going to work: (1) get out of bed, (2) take off pajamas, (3) take a shower, (4) get dressed, (5) eat breakfast and (6) carpool to work. This routine prepares the executive for a productive day at the office.

However, suppose that the same steps are performed in a slightly different order: (1) get out of bed, (2) take off pajamas, (3) get dressed, (4) take a shower, (5) eat breakfast, (6) carpool to work. In this case, our junior executive shows up for work soaking wet.

Specifying the order in which statements (actions) execute in an application is called program control. This chapter investigates program control using C#’s control statements.

Pseudocode

Pseudocode is an informal language that helps you develop algorithms without having to worry about the strict details of C# language syntax. Pseudocode is useful for developing algorithms that will be converted to structured portions of C# applications. Pseudocode is similar to English—it’s not a programming language.

Pseudocode does not execute on computers. Rather, it helps you “think out” an application before attempting to write it in C#. This chapter shows how to use pseudocode to develop C# applications.

You can create pseudocode using any text-editor application. A carefully prepared pseudocode application can easily be converted to a corresponding C# application. In many cases, this simply requires replacing pseudocode statements with C# equivalents.

Pseudocode normally describes only actions, such as input, output and calculations. We do not include variable declarations in our pseudocode, but some programmers do.

Control Structures

Normally, statements in an application are executed one after the other in the order in which they’re written. This process is called sequential execution. Various C# statements enable you to specify that the next statement to execute is not necessarily the next one in sequence. This is called transfer of control.

During the 1960s, it became clear that the indiscriminate use of transfers of control was the root of much difficulty experienced by software development groups. The blame was pointed at the goto statement (used in most programming languages of the time), which allows programmers to specify a transfer of control to one of a wide range of possible destinations in an application (creating what is often called “spaghetti code”). The notion of so-called structured programming became almost synonymous with “goto elimination.” We recommend that you avoid C#’s goto statement.

Research[1] had demonstrated that applications could be written without goto statements. The challenge of the era for programmers was to shift their styles to “goto-less programming.” Not until the 1970s did programmers start taking structured programming seriously. The results were impressive because structured applications were clearer, easier to debug and modify, and more likely to be bug free in the first place.

Bohm and Jacopini’s work demonstrated that all applications could be written in terms of only three control structures—the sequence structure, the selection structure and the repetition structure. When we introduce C#’s implementations of control structures, we’ll refer to them in the terminology of the C# Language Specification as “control statements.”

Sequence Structure in C#

The sequence structure is built into C#. Unless directed otherwise, the computer executes C# statements one after the other in the order in which they’re written—that is, in sequence. The UML activity diagram in Fig. 5.1 illustrates a typical sequence structure in which two calculations are performed in order. C# lets you have as many actions as you want in a sequence structure.

Sequence structure activity diagram.

Figure 5.1. Sequence structure activity diagram.

An activity diagram models the workflow (also called the activity) of a portion of a software system. Such workflows may include a portion of an algorithm, such as the sequence structure in Fig. 5.1. Activity diagrams are composed of special-purpose symbols, such as action-state symbols (rectangles with their left and right sides replaced with arcs curving outward), diamonds and small circles. These symbols are connected by transition arrows, which represent the flow of the activity—that is, the order in which the actions should occur.

Like pseudocode, activity diagrams help you develop and represent algorithms, although many programmers prefer pseudocode. Activity diagrams clearly show how control structures operate.

Consider the activity diagram for the sequence structure in Fig. 5.1. It contains two action states that represent actions to perform. Each action state contains an action expression—for example, “add grade to total” or “add 1 to counter”—that specifies an action to perform. Other actions might include calculations or input/output operations. The arrows in the activity diagram represent transitions, which indicate the order in which the actions occur. The portion of the application that implements the activities illustrated by the diagram in Fig. 5.1 first adds grade to total, then adds 1 to counter.

The solid circle located at the top of the activity diagram represents the activity’s initial state—the beginning of the workflow before the application performs the modeled actions. The solid circle surrounded by a hollow circle that appears at the bottom of the diagram represents the final state—the end of the workflow after the application performs its actions.

Figure 5.1 also includes rectangles with the upper-right corners folded over. These are UML notes (like comments in C#) that describe the purpose of symbols in the diagram. Figure 5.1 uses UML notes to show the C# code associated with each action state in the activity diagram. A dotted line connects each note with the element that the note describes. Activity diagrams normally do not show the C# code that implements the activity. We use notes for this purpose here to illustrate how the diagram relates to C# code.

Selection Structures in C#

C# has three types of selection structures, which from this point forward we shall refer to as selection statements. The if statement either performs (selects) an action if a condition is true or skips the action if the condition is false. The if...else statement performs an action if a condition is true or performs a different action if the condition is false. The switch statement (Chapter 6) performs one of many different actions, depending on the value of an expression.

The if statement is called a single-selection statement because it selects or ignores a single action (or, as we’ll soon see, a single group of actions). The if...else statement is called a double-selection statement because it selects between two different actions (or groups of actions). The switch statement is called a multiple-selection statement because it selects among many different actions (or groups of actions).

Repetition Structures in C#

C# provides four repetition structures, which from this point forward we shall refer to as repetition statements (also called iteration statements or loops). Repetition statements enable applications to perform statements repeatedly, depending on the value of a loop-continuation condition. The repetition statements are the while, do...while, for and foreach statements. (Chapter 6 presents the do...while and for statements. Chapter 8 discusses the foreach statement.) The while, for and foreach statements perform the action (or group of actions) in their bodies zero or more times—if the loop-continuation condition is initially false, the action (or group of actions) will not execute. The do...while statement performs the action (or group of actions) in its body one or more times. The words if, else, switch, while, do, for and foreach are C# keywords.

Summary of Control Statements in C#

C# has only three kinds of structured control statements: the sequence statement, selection statement (three types) and repetition statement (four types). We combine as many of each type of statement as necessary to make the program flow and work as required. As with the sequence statement in Fig. 5.1, we can model each control statement as an activity diagram. Each diagram contains one initial state and one final state that represent a control statement’s entry point and exit point, respectively. Single-entry/single-exit control statements make it easy to build applications—the control statements are “attached” to one another by connecting the exit point of one to the entry point of the next. This procedure is similar to the way in which a child stacks building blocks, so we call it control-statement stacking. You’ll learn that there’s only one other way in which control statements may be connected: control-statement nesting, in which a control statement appears inside another control statement. Thus, algorithms in C# applications are constructed from only three kinds of structured control statements, combined in only two ways. This is the essence of simplicity.

if Single-Selection Statement

Applications use selection statements to choose among alternative courses of action. For example, suppose that the passing grade on an exam is 60. The pseudocode statement

if grade is greater than or equal to 60
    display "Passed"

determines whether the condition “grade is greater than or equal to 60” is true or false. If the condition is true, “Passed” is displayed, and the next pseudocode statement in order is “performed.” (Remember that pseudocode is not a real programming language.) If the condition is false, the display statement is ignored, and the next pseudocode statement in order is performed. The indentation of the second line of this selection statement is optional, but recommended, because it emphasizes the inherent structure of structured applications.

The preceding pseudocode if statement may be written in C# as

if ( grade >= 60 )
   Console.WriteLine( "Passed" );

The C# code corresponds closely to the pseudocode. This is one of the characteristics of pseudocode that makes it such a useful application development tool.

Figure 5.2 illustrates the single-selection if statement. This activity diagram contains what is perhaps the most important symbol in an activity diagram—the diamond, or decision symbol, which indicates that a decision is to be made. The workflow will continue along a path determined by the symbol’s associated guard conditions, which can be true or false. Each transition arrow emerging from a decision symbol has a guard condition (specified in square brackets next to the transition arrow). If a guard condition is true, the workflow enters the action state to which the transition arrow points. In Fig. 5.2, if the grade is greater than or equal to 60, the application displays “Passed,” then transitions to the final state of this activity. If the grade is less than 60, the application immediately transitions to the final state without displaying a message.

if single-selection statement UML activity diagram.

Figure 5.2. if single-selection statement UML activity diagram.

The if statement is a single-entry/single-exit control statement. You’ll see that the activity diagrams for the remaining control statements also contain initial states, transition arrows, action states that indicate actions to perform and decision symbols (with associated guard conditions) that indicate decisions to be made, and final states. This is consistent with the action/decision model of programming we’ve been emphasizing.

Envision eight bins, each containing only one type of C# control statement. The control statements are all empty. Your task is to assemble an application from as many of each type of control statement as the algorithm demands, combining the control statements in only two possible ways (stacking or nesting), then filling in the action states and decisions with action expressions and guard conditions appropriate for the algorithm. We’ll discuss in detail the variety of ways in which actions and decisions can be written.

if...else Double-Selection Statement

The if single-selection statement performs an indicated action only when the condition is true; otherwise, the action is skipped. The if...else double-selection statement allows you to specify an action to perform when the condition is true and a different action when the condition is false. For example, the pseudocode statement

if grade is greater than or equal to 60
        display “Passed”
else
        display “Failed”

displays “Passed” if the grade is greater than or equal to 60, but displays “Failed” if it’s less than 60. In either case, after displaying occurs, the next pseudocode statement in sequence is “performed.”

The preceding if...else pseudocode statement can be written in C# as

if ( grade >= 60 )
    Console.WriteLine( "Passed" );
else
    Console.WriteLine( "Failed" );

The body of the else part is also indented. Whatever indentation convention you choose should be applied consistently throughout your applications. It’s difficult to read applications that do not obey uniform spacing conventions.

Good Programming Practice 5.1

Good Programming Practice 5.1

Indent both body statements of an if...else statement.

Good Programming Practice 5.2

Good Programming Practice 5.2

If there are several levels of indentation, each level should be indented the same additional amount of space.

Figure 5.3 illustrates the flow of control in the if...else statement. Imagine again a deep bin containing as many empty if...else statements as might be needed to build any C# application. Your job is to assemble these if...else statements (by stacking and nesting) with any other control statements required by the algorithm. You fill in the action states and decision symbols with action expressions and guard conditions appropriate for the algorithm you are developing.

if...else double-selection statement UML activity diagram.

Figure 5.3. if...else double-selection statement UML activity diagram.

Conditional Operator (?:)

C# provides the conditional operator (?:), which can be used in place of an if...else statement. This is C#’s only ternary operator—this means that it takes three operands. Together, the operands and the ?: symbols form a conditional expression. The first operand (to the left of the ?) is a boolean expression (i.e., an expression that evaluates to a bool-type value—true or false), the second operand (between the ? and :) is the value of the conditional expression if the boolean expression is true and the third operand (to the right of the :) is the value of the conditional expression if the boolean expression is false. For example, the statement

Console.WriteLine( grade >= 60 ? "Passed" : "Failed" );

displays the value of WriteLine’s conditional-expression argument. The conditional expression in the preceding statement evaluates to the string "Passed" if the boolean expression grade >= 60 is true and evaluates to the string "Failed" if the boolean expression is false. Thus, this statement with the conditional operator performs essentially the same function as the if...else statement shown earlier in this section, in which the boolean expression grade >= 60 was used as the if...else statement’s condition. Actually, every control statement’s condition must evaluate to the bool-type value true or false. You’ll see that conditional expressions can be used in some situations where if...else statements cannot.

Good Programming Practice 5.3

Good Programming Practice 5.3

When a conditional expression is inside a larger expression, it’s good practice to parenthesize the conditional expression for clarity. Adding parentheses may also prevent operator-precedence problems that could cause syntax errors.

Nested if...else Statements

An application can test multiple cases by placing if...else statements inside other if...else statements to create nested if...else statements. For example, the following pseudocode represents a nested if...else statement that displays A for exam grades greater than or equal to 90, B for grades in the range 80 to 89, C for grades in the range 70 to 79, D for grades in the range 60 to 69 and F for all other grades:

if grade is greater than or equal to 90
        display “A”
else
        if grade is greater than or equal to 80
                display “B”
        else
                if grade is greater than or equal to 70
                        display “C”
                else
                        if grade is greater than or equal to 60
                                display “D”
                        else
                                display “F”

This pseudocode may be written in C# as

if ( grade >= 90 )
   Console.WriteLine( "A" );
else
   if ( grade >= 80 )
      Console.WriteLine( "B" );
   else
      if ( grade >= 70 )
         Console.WriteLine( "C" );
      else
         if ( grade >= 60 )
            Console.WriteLine( "D" );
         else
            Console.WriteLine( "F" );

If grade is greater than or equal to 90, the first four conditions will be true, but only the statement in the if-part of the first if...else statement will execute. After that statement executes, the else-part of the “outermost” if...else statement is skipped. Most C# programmers prefer to write the preceding if...else statement as

if ( grade >= 90 )
   Console.WriteLine( "A" );
else if ( grade >= 80 )
   Console.WriteLine( "B" );
else if ( grade >= 70 )
   Console.WriteLine( "C" );
else if ( grade >= 60 )
   Console.WriteLine( "D" );
else
   Console.WriteLine( "F" );

The two forms are identical except for the spacing and indentation, which the compiler ignores. The latter form is popular because it avoids deep indentation of the code to the right—such indentation often leaves little room on a line of code, forcing lines to be split and decreasing the readability of your code.

Dangling-else Problem

The C# compiler always associates an else with the immediately preceding if unless told to do otherwise by the placement of braces ({ and }). This behavior can lead to what is referred to as the dangling-else problem. For example,

if ( x > 5 )
   if ( y > 5 )
      Console.WriteLine( "x and y are > 5" );
   else
      Console.WriteLine( "x is <= 5" );

appears to indicate that if x is greater than 5, the nested if statement determines whether y is also greater than 5. If so, the string "x and y are > 5" is output. Otherwise, it appears that if x is not greater than 5, the else part of the if...else outputs the string "x is <= 5".

Beware! This nested if...else statement does not execute as it appears. The compiler actually interprets the statement as

if ( x > 5 )
   if ( y > 5 )
      Console.WriteLine( "x and y are > 5" );
   else
      Console.WriteLine( "x is <= 5" );

in which the body of the first if is a nested if...else. The outer if statement tests whether x is greater than 5. If so, execution continues by testing whether y is also greater than 5. If the second condition is true, the proper string—"x and y are > 5"—is displayed. However, if the second condition is false, the string "x is <= 5" is displayed, even though we know that x is greater than 5.

To force the nested if...else statement to execute as it was originally intended, we must write it as follows:

if ( x > 5 )
{
   if ( y > 5 )
      Console.WriteLine( "x and y are > 5" );
}
else
   Console.WriteLine( "x is <= 5" );

The braces ({}) indicate to the compiler that the second if statement is in the body of the first if and that the else is associated with the first if. Exercises 5.275.28 investigate the dangling-else problem further.

Blocks

The if statement expects only one statement in its body. To include several statements in the body of an if (or the body of an else for an if...else statement), enclose the statements in braces ({ and }). A set of statements contained within a pair of braces is called a block. A block can be placed anywhere in an application that a single statement can be placed.

The following example includes a block in the else-part of an if...else statement:

if ( grade >= 60 )
   Console.WriteLine( "Passed" );
else
{
   Console.WriteLine( "Failed" );
   Console.WriteLine( "You must take this course again." );
}

In this case, if grade is less than 60, the application executes both statements in the body of the else and displays

Failed.
You must take this course again.

Note the braces surrounding the two statements in the else clause. These braces are important. Without the braces, the statement

Console.WriteLine( "You must take this course again." );

would be outside the body of the else-part of the if...else statement and would execute regardless of whether the grade was less than 60.

Syntax errors are caught by the compiler. A logic error (e.g., when both braces in a block are left out of the application) has its effect at execution time. A fatal logic error causes an application to fail and terminate prematurely. A nonfatal logic error allows an application to continue executing, but causes it to produce incorrect results.

Good Programming Practice 5.4

Good Programming Practice 5.4

Always using braces in an if...else (or other) statement helps prevent their accidental omission, especially when adding statements to the if-part or the else-part at a later time. To avoid omitting one or both of the braces, some programmers type the beginning and ending braces of blocks before typing the individual statements within them.

Just as a block can be placed anywhere a single statement can be placed, it’s also possible to have an empty statement. Recall from Section 3.9 that the empty statement is represented by placing a semicolon (;) where a statement would normally be.

Common Programming Error 5.1

Common Programming Error 5.1

Placing a semicolon after the condition in an if or if...else statement leads to a logic error in single-selection if statements and a syntax error in double-selection if...else statements (when the if-part contains an actual body statement).

while Repetition Statement

A repetition statement allows you to specify that an application should repeat an action while some condition remains true. The pseudocode statement

while there are more items on my shopping list
        put next item in cart and cross it off my list

describes the repetition that occurs during a shopping trip. The condition “there are more items on my shopping list” may be true or false. If it’s true, then the action “Purchase next item and cross it off my list” is performed. This action will be performed repeatedly while the condition remains true. The statement(s) contained in the while repetition statement constitute the body of the while repetition statement, which may be a single statement or a block. Eventually, the condition will become false (when the last item on the shopping list has been purchased and crossed off the list). At this point, the repetition terminates, and the first statement after the repetition statement executes.

As an example of C#’s while repetition statement, consider a code segment designed to find the first power of 3 larger than 100. When the following while statement finishes executing, product contains the result:

int product = 3;

while ( product <= 100 )
   product = 3 * product;

When this while statement begins execution, the value of variable product is 3. Each repetition of the while statement multiplies product by 3, so product takes on the subsequent values 9, 27, 81 and 243 successively. When variable product becomes 243, the while statement condition—product <= 100—becomes false. This terminates the repetition, so the final value of product is 243. At this point, application execution continues with the next statement after the while statement.

Common Programming Error 5.2

Common Programming Error 5.2

Not providing in the body of a while statement an action that eventually causes the condition in the while to become false normally results in a logic error called an infinite loop, in which the loop never terminates.

The activity diagram in Fig. 5.4 illustrates the flow of control for the preceding while statement. This diagram also introduces the UML’s merge symbol. The UML represents both the merge and decision symbols as diamonds. The merge symbol joins two flows of activity into one. In this diagram, the merge symbol joins the transitions from the initial state and the action state, so they both flow into the decision that determines whether the loop should begin (or continue) executing. The decision and merge symbols can be distinguished by the number of “incoming” and “outgoing” transition arrows. A decision symbol has one transition arrow pointing to the diamond and two or more transition arrows pointing out from the diamond to indicate possible transitions from that point. Each transition arrow pointing out of a decision symbol has a guard condition. A merge symbol has two or more transition arrows pointing to the diamond and only one transition arrow pointing from the diamond, to indicate multiple activity flows merging to continue the activity. None of the transition arrows associated with a merge have guard conditions.

while repetition statement UML activity diagram.

Figure 5.4. while repetition statement UML activity diagram.

Figure 5.4 clearly shows the repetition of the while statement discussed earlier in this section. The transition arrow emerging from the action state points back to the merge, from which program flow transitions back to the decision that is tested at the beginning of each repetition of the loop. The loop continues to execute until the guard condition product > 100 becomes true. Then the while statement exits (reaches its final state), and control passes to the next statement in sequence in the application.

Formulating Algorithms: Counter-Controlled Repetition

To illustrate how algorithms are developed, we modify the GradeBook class of Chapter 4 to solve two variations of a problem that averages student grades. Consider the following problem statement:

A class of 10 students took a quiz. The grades (integers in the range 0 to 100) for this quiz are available to you. Determine the class average on the quiz.

The class average is equal to the sum of the grades divided by the number of students. The algorithm for solving this problem on a computer must input each grade, keep track of the total of all grades input, perform the averaging calculation and display the result.

Pseudocode Algorithm with Counter-Controlled Repetition

Let’s use pseudocode to list the actions to execute and specify the order in which they should execute. We use counter-controlled repetition to input the grades one at a time. This technique uses a variable called a counter (or control variable) to control the number of times a set of statements will execute. Counter-controlled repetition is often called definite repetition, because the number of repetitions is known by the application before the loop begins executing. In this example, repetition terminates when the counter exceeds 10. This section presents a fully developed pseudocode algorithm (Fig. 5.5) and a version of class GradeBook (Fig. 5.6) that implements the algorithm in a C# method. The section then presents an application (Fig. 5.7) that demonstrates the algorithm in action. In Section 5.9, we demonstrate how to use pseudocode to develop such an algorithm from scratch.

Table 5.5. Pseudocode algorithm that uses counter-controlled repetition to solve the class-average problem.

 1   set total to zero
 2   set grade counter to one
 3
 4   while grade counter is less than or equal to 10
 5         prompt the user to enter the next grade
 6         input the next grade
 7         add the grade into the total
 8         add one to the grade counter
 9
10   set the class average to the total divided by 10
11   display the class average

Example 5.6. GradeBook class that solves the class-average problem using counter-controlled repetition.

 1   // Fig. 5.6: GradeBook.cs
 2   // GradeBook class that solves class-average problem using
 3   // counter-controlled repetition.
 4   using System;
 5
 6   public class GradeBook
 7   {
 8      // auto-implemented property CourseName
 9      public string CourseName { get; set; }
10
11      // constructor initializes CourseName property
12      public GradeBook( string name )
13      {
14         CourseName = name; // set CourseName to name
15      } // end constructor
16
17      // display a welcome message to the GradeBook user
18      public void DisplayMessage()
19      {
20         // property CourseName gets the name of the course
21         Console.WriteLine( "Welcome to the grade book for
{0}!
",
22            CourseName );
23      } // end method DisplayMessage
24
25      // determine class average based on 10 grades entered by user
26      public void DetermineClassAverage()
27      {
28         int total; // sum of the grades entered by user
29         int gradeCounter; // number of the grade to be entered next
30         int grade; // grade value entered by the user
31         int average; // average of the grades
32
33         // initialization phase
34         total = 0; // initialize the total
35         gradeCounter = 1; // initialize the loop counter
36
37         // processing phase
38         while ( gradeCounter <= 10 ) // loop 10 times
39         {
40            Console.Write( "Enter grade: " ); // prompt the user
41            grade = Convert.ToInt32( Console.ReadLine() ); // read grade
42            total = total + grade; // add the grade to total
43            gradeCounter = gradeCounter + 1; // increment the counter by 1
44         } // end while
45
46         // termination phase
47         average = total / 10; // integer division yields integer result
48
49         // display total and average of grades
50         Console.WriteLine( "
Total of all 10 grades is {0}", total );
51         Console.WriteLine( "Class average is {0}", average );
52      } // end method DetermineClassAverage
53   } // end class GradeBook

Example 5.7. Create GradeBook object and invoke its DetermineClassAverage method.

 1   // Fig. 5.7: GradeBookTest.cs
 2   // Create GradeBook object and invoke its DetermineClassAverage method.
 3   public class GradeBookTest
 4   {
 5      public static void Main( string[] args )
 6     {
 7         // create GradeBook object myGradeBook and
 8         // pass course name to constructor
 9         GradeBook myGradeBook = new GradeBook(
10            "CS101 Introduction to C# Programming" );
11
12         myGradeBook.DisplayMessage(); // display welcome message
13         myGradeBook.DetermineClassAverage(); // find average of 10 grades
14      } // end Main
15   } // end class GradeBookTest

Welcome to the grade book for
CS101 Introduction to C# Programming!
Enter grade: 88
Enter grade: 79
Enter grade: 95
Enter grade: 100
Enter grade: 48
Enter grade: 88
Enter grade: 92
Enter grade: 83
Enter grade: 90
Enter grade: 85

Total of all 10 grades is 848
Class average is 84

Software Engineering Observation 5.1

Software Engineering Observation 5.1

Experience has shown that the most difficult part of solving a problem on a computer is developing the algorithm for the solution. Once a correct algorithm has been specified, the process of producing a working C# application from it is normally straightforward.

Note the references in the algorithm of Fig. 5.5 to a total and a counter. A total is a variable used to accumulate the sum of several values. A counter is a variable used to count—in this case, the grade counter indicates which of the 10 grades is about to be entered by the user. Variables used to store totals are normally initialized to zero before being used in an application.

Implementing Counter-Controlled Repetition in Class GradeBook

Class GradeBook (Fig. 5.6) contains a constructor (lines 12–15) that assigns a value to the instance variable created by auto-implemented property CourseName in line 9. Lines 18–23 declare method DisplayMessage. Lines 26–52 declare method DetermineClassAverage, which implements the class-averaging algorithm described by the pseudocode in Fig. 5.5.

Lines 28–31 declare local variables total, gradeCounter, grade and average to be of type int. In this example, variable total accumulates the sum of the grades entered and gradeCounter counts the number of grades entered. Variable grade stores the most recent grade value entered (line 41). Variable average stores the average grade.

The declarations (in lines 28–31) appear in method DetermineClassAverage’s body. Variables declared in a method body are local variables and can be used only from the line of their declaration to the closing right brace of the block in which they’re declared. A local variable’s declaration must appear before the variable is used in that method. A local variable cannot be accessed outside the method in which it’s declared.

In the versions of class GradeBook in this chapter, we simply read and process a set of grades. The averaging calculation is performed in method DetermineClassAverage using local variables—we do not preserve any information about student grades in instance variables of the class. In later versions of the class (in Chapter 8), we store the grades using an instance variable that refers to a data structure known as an array. This allows a GradeBook object to perform various calculations on the same set of grades without requiring the user to enter the grades multiple times.

Good Programming Practice 5.5

Good Programming Practice 5.5

Separate declarations from other statements in methods with a blank line for readability.

We say that a variable is definitely assigned when it’s guaranteed to be assigned a value before it’s used. Notice that each local variable declared in lines 28–31 is definitely assigned before it’s used in calculations. The assignments (in lines 34–35) initialize total to 0 and gradeCounter to 1. Variables grade and average (for the user input and calculated average, respectively) need not be initialized here—their values are assigned as they’re input or calculated later in the method.

Common Programming Error 5.3

Common Programming Error 5.3

Using the value of a local variable before it’s definitely assigned results in a compilation error. All local variables must be definitely assigned before their values are used in expressions.

Error-Prevention Tip 5.1

Error-Prevention Tip 5.1

Initialize each counter and total, either in its declaration or in an assignment statement. Totals are normally initialized to 0. Counters are normally initialized to 0 or 1, depending on how they’re used (we’ll show examples of each).

Line 38 indicates that the while statement should continue looping (also called iterating) as long as the value of gradeCounter is less than or equal to 10. While this condition remains true, the while statement repeatedly executes the statements between the braces that delimit its body (lines 39–44).

Line 40 displays the prompt "Enter grade:" in the console window. Line 41 reads the grade entered by the user and assigns it to variable grade. Then line 42 adds the new grade entered by the user to the total and assigns the result to total, which replaces its previous value.

Line 43 adds 1 to gradeCounter to indicate that the application has processed a grade and is ready to input the next grade from the user. Incrementing gradeCounter eventually causes gradeCounter to exceed 10. At that point the while loop terminates, because its condition (line 38) becomes false.

When the loop terminates, line 47 performs the averaging calculation and assigns its result to the variable average. Line 50 uses Console’s WriteLine method to display the text "Total of all 10 grades is" followed by variable total’s value. Line 51 then displays the text "Class average is" followed by variable average’s value. Method DetermineClassAverage returns control to the calling method (i.e., Main in GradeBookTest of Fig. 5.7) after reaching line 52.

Class GradeBookTest

Class GradeBookTest (Fig. 5.7) creates an object of class GradeBook (Fig. 5.6) and demonstrates its capabilities. Lines 9–10 of Fig. 5.7 create a new GradeBook object and assign it to variable myGradeBook. The string in line 10 is passed to the GradeBook constructor (lines 12–15 of Fig. 5.6). Line 12 calls myGradeBook’s DisplayMessage method to display a welcome message to the user. Line 13 then calls myGradeBook’s DetermineClassAverage method to allow the user to enter 10 grades, for which the method then calculates and displays the average—the method performs the algorithm shown in Fig. 5.5.

Notes on Integer Division and Truncation

The averaging calculation performed by method DetermineClassAverage in response to the method call at line 13 in Fig. 5.7 produces an integer result. The application’s output indicates that the sum of the grade values in the sample execution is 848, which, when divided by 10, should yield the floating-point number 84.8. However, the result of the calculation total / 10 (line 47 of Fig. 5.6) is the integer 84, because total and 10 are both integers. Dividing two integers results in integer division—any fractional part of the calculation is lost (i.e., truncated, not rounded). We’ll see how to obtain a floating-point result from the averaging calculation in the next section.

Common Programming Error 5.4

Common Programming Error 5.4

Assuming that integer division rounds (rather than truncates) can lead to incorrect results. For example, 7 ÷ 4, which yields 1.75 in conventional arithmetic, truncates to 1 in integer arithmetic, rather than rounding to 2.

Formulating Algorithms: Sentinel-Controlled Repetition

Let us generalize Section 5.8’s class-average problem. Consider the following problem:

Develop a class-averaging application that processes grades for an arbitrary number of students each time it’s 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 the user will enter during the application’s execution. The application 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 display 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.” This is called sentinel-controlled repetition. 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 often called indefinite repetition because the number of repetitions is not known by the application before the loop begins executing.

Clearly, a sentinel value must be chosen 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. Thus, a run of the class-average application might process a stream of inputs such as 95, 96, 75, 74, 89 and −1. The application would then compute and display the class average for the grades 95, 96, 75, 74 and 89. Since −1 is the sentinel value, it should not enter into the averaging calculation.

Common Programming Error 5.5

Common Programming Error 5.5

Choosing a sentinel value that is also a legitimate data value is a logic error.

Developing the Pseudocode Algorithm with Top-Down, Stepwise Refinement: The Top and First Refinement

We approach the class-average application with a technique called top-down, stepwise refinement, which is essential to the development of well-structured applications. We begin with a pseudocode representation of the top—a single statement that conveys the overall function of the application:

determine the class average for the quiz

The top is, in effect, a complete representation of an application. Unfortunately, the top rarely conveys sufficient detail from which to write a C# application. So we now begin the refinement process. We divide the top into a series of smaller tasks and list these in the order in which they’ll be performed. This results in the following first refinement:

initialize variables
input, sum and count the quiz grades
calculate and display the class average

This refinement uses only the sequence structure—the steps listed should execute in order, one after the other.

Software Engineering Observation 5.2

Software Engineering Observation 5.2

Each refinement, as well as the top itself, is a complete specification of the algorithm—only the level of detail varies.

Software Engineering Observation 5.3

Software Engineering Observation 5.3

Many applications can be divided logically into three phases: an initialization phase that initializes the variables; a processing phase that inputs data values and adjusts application variables (e.g., counters and totals) accordingly; and a termination phase that calculates and outputs the final results.

Proceeding to the Second Refinement

The preceding Software Engineering Observation is often all you need for the first refinement in the top-down process. To proceed to the next level, the second refinement, we specify individual variables. In this example, we need a running total of the numbers, a count of how many numbers have been processed, a variable to receive the value of each grade as it’s input by the user and a variable to hold the calculated average. The pseudocode statement

initialize variables

can be refined as follows:

initialize total to zero
initialize counter to zero

Only the variables total and counter need to be initialized before they’re used. The variables average and grade (for the calculated average and the user input, respectively) need not be initialized, because their values will be replaced as they’re calculated or input.

The pseudocode statement

input, sum and count the quiz grades

requires a repetition statement that successively inputs each grade. We do not know in advance how many grades are to be processed, so we’ll use sentinel-controlled repetition. The user enters grades one at a time. After entering the last grade, the user enters the sentinel value. The application tests for the sentinel value after each grade is input and terminates the loop when the user enters the sentinel value. The second refinement of the preceding pseudocode statement is then

prompt the user to enter the first grade
input the first grade (possibly the sentinel)

while the user has not yet entered the sentinel
       add this grade into the running total
       add one to the grade counter
       prompt the user to enter the next grade
       input the next grade (possibly the sentinel)

In pseudocode, we do not use braces around the statements that form the body of the while structure. We simply indent the statements under the while to show that they belong to the while. Again, pseudocode is only an informal application-development aid.

The pseudocode statement

calculate and display the class average

can be refined as follows:

if the counter is not equal to zero
       set the average to the total divided by the counter
       display the average
else
       display “No grades were entered”

We’re careful here to test for the possibility of division by zero—a logic error that, if undetected, would cause the application to fail or produce invalid output. The complete second refinement of the pseudocode for the class-average problem is shown in Fig. 5.8.

Table 5.8. Class-average problem pseudocode algorithm with sentinel-controlled repetition.

 1   initialize total to zero
 2   initialize counter to zero
 3
 4   prompt the user to enter the first grade
 5   input the first grade (possibly the sentinel)
 6
 7   while the user has not yet entered the sentinel
 8          add this grade into the running total
 9          add one to the grade counter
10         prompt the user to enter the next grade
11         input the next grade (possibly the sentinel)
12
13   if the counter is not equal to zero
14         set the average to the total divided by the counter
15         display the average
16   else
17         display “No grades were entered”

Error-Prevention Tip 5.2

Error-Prevention Tip 5.2

When performing division by an expression whose value could be zero, explicitly test for this possibility and handle it appropriately in your application (e.g., by displaying an error message) rather than allowing the error to occur.

In Fig. 5.5 and Fig. 5.8, we included some completely blank lines and indentation in the pseudocode to make it more readable. The blank lines separate the pseudocode algorithms into their various phases and set off control statements, and the indentation emphasizes the bodies of the control statements.

The pseudocode algorithm in Fig. 5.8 solves the more general class-averaging problem. This algorithm was developed after only two refinements. Sometimes more refinements are necessary.

Software Engineering Observation 5.4

Software Engineering Observation 5.4

Terminate the top-down, stepwise refinement process when you’ve specified the pseudocode algorithm in sufficient detail for you to convert the pseudocode to C#. Normally, implementing the C# application is then straightforward.

Software Engineering Observation 5.5

Software Engineering Observation 5.5

Some experienced programmers write applications without ever using application-development tools like pseudocode. They feel that their ultimate goal is to solve the problem on a computer and that writing pseudocode merely delays the production of final outputs. Although this method may work for simple and familiar problems, it can lead to serious errors and delays in large, complex projects.

Implementing Sentinel-Controlled Repetition in Class GradeBook

Figure 5.9 shows the C# class GradeBook containing method DetermineClassAverage that implements the pseudocode algorithm of Fig. 5.8. Although each grade is an integer, the averaging calculation is likely to produce a number with a decimal point—in other words, a real number or floating-point number. The type int cannot represent such a number, so this class uses type double to do so.

Example 5.9. GradeBook class that solves the class-average problem using sentinel-controlled repetition.

 1   // Fig. 5.9: GradeBook.cs
 2   // GradeBook class that solves class-average problem using
 3   // sentinel-controlled repetition.
 4   using System;
 5
 6   public class GradeBook
 7   {
 8      // auto-implemented property CourseName
 9      public string CourseName { get; set; }
10
11      // constructor initializes the CourseName property
12      public GradeBook( string name )
13      {
14         CourseName = name; // set CourseName to name
15      } // end constructor
16
17      // display a welcome message to the GradeBook user
18      public void DisplayMessage()
19      {
20         Console.WriteLine( "Welcome to the grade book for
{0}!
",
21            CourseName );
22      } // end method DisplayMessage
23
24      // determine the average of an arbitrary number of grades
25      public void DetermineClassAverage()
26      {
27         int total; // sum of grades
28         int gradeCounter; // number of grades entered
29         int grade; // grade value
30         double average; // number with decimal point for average
31
32         // initialization phase
33         total = 0; // initialize total
34         gradeCounter = 0; // initialize loop counter
35
36         // processing phase
37         // prompt for and read a grade from the user   
38         Console.Write( "Enter grade or -1 to quit: " );
39         grade = Convert.ToInt32( Console.ReadLine() ); 
40
41         // loop until sentinel value is read from the user
42         while ( grade != -1 )
43         {
44            total = total + grade; // add grade to total
45            gradeCounter = gradeCounter + 1; // increment counter
46
47            // prompt for and read the next grade from the user
48            Console.Write( "Enter grade or -1 to quit: " );    
49            grade = Convert.ToInt32( Console.ReadLine() );     
50         } // end while
51
52         // termination phase
53         // if the user entered at least one grade...
54         if ( gradeCounter != 0 )
55         {
56            // calculate the average of all the grades entered
57            average = ( double ) total / gradeCounter;        
58
59            // display the total and average (with two digits of precision)
60            Console.WriteLine( "
Total of the {0} grades entered is {1}",
61               gradeCounter, total );
62            Console.WriteLine( "Class average is {0:F}", average );
63         } // end if
64         else // no grades were entered, so output error message
65            Console.WriteLine( "No grades were entered" );
66      } // end method DetermineClassAverage
67   } // end class GradeBook

In this example, we see that control statements may be stacked on top of one another (in sequence) just as a child stacks building blocks. The while statement (lines 42–50) is followed in sequence by an if...else statement (lines 54–65). Much of the code in this application is identical to the code in Fig. 5.6, so we concentrate on the new features and issues.

Line 30 declares double variable average. This variable allows us to store the calculated class average as a floating-point number. Line 34 initializes gradeCounter to 0, because no grades have been entered yet. Remember that this application uses sentinel-controlled repetition to input the grades from the user. To keep an accurate record of the number of grades entered, the application increments gradeCounter only when the user inputs a valid grade value.

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.6. In counter-controlled repetition, each repetition of the while statement (e.g., lines 38–44 of Fig. 5.6) reads a value from the user, for the specified number of repetitions. In sentinel-controlled repetition, the application reads the first value (lines 38–39 of Fig. 5.9) before reaching the while. This value determines whether the application’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 (because 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 44) and adds 1 to gradeCounter (line 45). Then lines 48–49 in the loop’s body input the next value from the user. Next, program control reaches the closing right brace of the body at line 50, so execution continues with the test of the while’s condition (line 42). The condition uses the most recent grade input by the user to determine whether the loop’s body should execute again. The value of variable grade is always input from the user immediately before the application tests the while condition. This allows the application to determine whether the value just input is the sentinel value before the application processes that value (i.e., adds it to the total). If the sentinel value is input, the loop terminates; the application does not add –1 to the total.

Good Programming Practice 5.6

Good Programming Practice 5.6

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

Notice the while statement’s block in Fig. 5.9 (lines 43–50). Without the braces, the loop would consider its body to be only the first statement, which adds the grade to the total. The last three statements in the block would fall outside the loop’s body, causing the computer to interpret the code incorrectly as follows:

while ( grade != -1 )
   total = total + grade; // add grade to total
gradeCounter = gradeCounter + 1; // increment counter


// prompt for input and read next grade from user
Console.Write( "Enter grade or -1 to quit: " );
grade = Convert.ToInt32( Console.ReadLine() );

The preceding code would cause an infinite loop in the application if the user did not enter the sentinel -1 at line 39 (before the while statement).

Error-Prevention Tip 5.3

Error-Prevention Tip 5.3

Omitting the braces that delimit a block can lead to logic errors, such as infinite loops. To prevent this problem, some programmers enclose the body of every control statement in braces even if the body contains only a single statement.

After the loop terminates, the if...else statement at lines 54–65 executes. The condition at line 54 determines whether any grades were input. If none were input, the else part (lines 64–65) of the if...else statement executes and displays the message "No grades were entered", and the method returns control to the calling method.

Explicitly and Implicitly Converting Between Simple Types

If at least one grade was entered, line 57 of Fig. 5.9 calculates the average of the grades. Recall from Fig. 5.6 that integer division yields an integer result. Even though variable average is declared as a double (line 30), the calculation

average = total / gradeCounter;

loses the division’s fractional part before the result is assigned to average. This occurs because total and gradeCounter are both integers, and integer division yields an integer result. To perform a floating-point calculation with integer values, we must temporarily treat these values as floating-point numbers for use in the calculation. C# provides the unary cast operator to accomplish this task. Line 57 uses the (double) cast operator—which has higher precedence than the arithmetic operators—to create a temporary floating-point copy of its operand total (which appears to the right of the operator). Using a cast operator in this manner is called explicit conversion. The value stored in total is still an integer.

The calculation now consists of a floating-point value (the temporary double version of total) divided by the integer gradeCounter. C# knows how to evaluate only arithmetic expressions in which the operands’ types are identical. To ensure that the operands are of the same type, C# performs an operation called promotion (or implicit conversion) on selected operands. For example, in an expression containing values of the types int and double, the int values are promoted to double values for use in the expression. In this example, the value of gradeCounter is promoted to type double, then floating-point division is performed and the result of the calculation is assigned to average. As long as the (double) cast operator is applied to any variable in the calculation, the calculation will yield a double result.

Common Programming Error 5.6

Common Programming Error 5.6

A cast operator can be used to convert between simple numeric types, such as int and double, and between related reference types (as we discuss in Chapter 12, OOP: Polymorphism, Interfaces and Operator Overloading). Casting to the wrong type may cause compilation or runtime errors.

Cast operators are available for all simple types. We’ll discuss cast operators for reference types in Chapter 12. The cast operator is formed by placing parentheses around the name of a type. This operator is a unary operator (i.e., an operator that takes only one operand). In Chapter 3, we studied the binary arithmetic operators. C# also supports unary versions of the plus (+) and minus () operators, so you can write expressions like +5 or -7. Cast operators associate from right to left and have the same precedence as other unary operators, such as unary + and unary -. This precedence is one level higher than that of the multiplicative operators *, / and %. (See the operator precedence chart in Appendix A.) We indicate the cast operator with the notation (type) in our precedence charts, to indicate that any type name can be used to form a cast operator.

Line 62 outputs the class average. In this example, we decided that we’d like to display the class average rounded to the nearest hundredth and output the average with exactly two digits to the right of the decimal point. The format specifier F in WriteLine’s format item (line 62) indicates that variable average’s value should be displayed as a real number. By default, numbers output with F have two digits to the right of the decimal point. The number of decimal places to the right of the decimal point is also known as the number’s precision. Any floating-point value output with F will be rounded to the hundredths position—for example, 123.457 will be rounded to 123.46, and 27.333 will be rounded to 27.33. In this application, the three grades entered during the sample execution of class GradeBookTest (Fig. 5.10) total 263, which yields the average 87.66666.... The format item rounds the average to the hundredths position, and the average is displayed as 87.67.

Example 5.10. Create GradeBook object and invoke DetermineClassAverage method.

 1   // Fig. 5.10: GradeBookTest.cs
 2   // Create GradeBook object and invoke its DetermineClassAverage method.
 3   public class GradeBookTest
 4   {
 5      public static void Main( string[] args )
 6      {
 7         // create GradeBook object myGradeBook and
 8         // pass course name to constructor
 9         GradeBook myGradeBook = new GradeBook(
10            "CS101 Introduction to C# Programming" );
11
12         myGradeBook.DisplayMessage(); // display welcome message
13         myGradeBook.DetermineClassAverage(); // find average of grades
14      } // end Main
15   } // end class GradeBookTest

Welcome to the grade book for
CS101 Introduction to C# Programming!

Enter grade or -1 to quit: 96
Enter grade or -1 to quit: 88
Enter grade or -1 to quit: 79
Enter grade or -1 to quit: -1

Total of the 3 grades entered is 263
Class average is 87.67

Formulating Algorithms: Nested Control Statements

For the next example, we once again formulate an algorithm by using pseudocode and top-down, stepwise refinement, and write a corresponding C# application. We’ve seen that control statements can be stacked on top of one another (in sequence). In this case study, we examine the only other structured way control statements can be connected, namely, by nesting one control statement within another.

Consider the following problem statement:

A college offers a course that prepares students for the state licensing exam for real estate brokers. Last year, 10 of the students who completed this course took the exam. The college wants to know how well its students did on the exam. You’ve been asked to write an application to summarize the results. You’ve been given a list of these 10 students. Next to each name is written a 1 if the student passed the exam or a 2 if the student failed.

Your application should analyze the results of the exam as follows:

  1. Input each test result (i.e., a 1 or a 2). Display the message “Enter result” on the screen each time the application requests another test result.

  2. Count the number of test results of each type.

  3. Display a summary of the test results indicating the number of students who passed and the number who failed.

  4. If more than eight students passed the exam, display the message “Bonus to instructor!”

After reading the problem statement, we make the following observations:

  1. The application must process test results for 10 students. A counter-controlled loop can be used because the number of test results is known in advance.

  2. Each test result has a numeric value—either a 1 or a 2. Each time the application reads a test result, the application must determine whether the number is a 1 or a 2. We test for a 1 in our algorithm. If the number is not a 1, we assume that it’s a 2. (Exercise 5.24 considers the consequences of this assumption.)

  3. Two counters are used to keep track of the exam results—one to count the number of students who passed the exam and one to count the number of students who failed the exam.

  4. After the application has processed all the results, it must determine whether more than eight students passed the exam.

Let us proceed with top-down, stepwise refinement. We begin with a pseudocode representation of the top:

analyze exam results and decide whether the instructor should receive a bonus

Once again, the top is a complete representation of the application, but several refinements are likely to be needed before the pseudocode can evolve naturally into a C# application.

Our first refinement is

initialize variables
input the 10 exam results, and count passes and failures
display a summary of the exam results and decide if the instructor should
     receive a bonus

Here, too, even though we have a complete representation of the entire application, further refinement is necessary. We now specify individual variables. Counters are needed to record the passes and failures, a counter will be used to control the looping process and a variable is needed to store the user input. The variable in which the user input will be stored is not initialized at the start of the algorithm, because its value is read from the user during each repetition of the loop.

The pseudocode statement

initialize variables

can be refined as follows:

initialize passes to zero
initialize failures to zero
initialize student counter to one

Notice that only the counters are initialized at the start of the algorithm.

The pseudocode statement

input the 10 exam results, and count passes and failures

requires a loop that successively inputs the result of each exam. We know in advance that there are precisely 10 exam results, so counter-controlled looping is appropriate. Inside the loop (i.e., nested within the loop), a double-selection statement will determine whether each exam result is a pass or a failure and will increment the appropriate counter. The refinement of the preceding pseudocode statement is then

while student counter is less than or equal to 10
      prompt the user to enter the next exam result
      input the next exam result

      if the student passed
            add one to passes
      else
            add one to failures

      add one to student counter

We use blank lines to isolate the if...else control statement, which improves readability.

The pseudocode statement

display a summary of the exam results and decide if the instructor should
        receive a bonus

can be refined as follows:

display the number of passes
display the number of failures

if more than eight students passed
        display “Bonus to instructor!”

Complete Second Refinement of Pseudocode and Conversion to Class Analysis

The complete second refinement of the pseudocode appears in Fig. 5.11. Notice that blank lines are also used to set off the while statement for readability. This pseudocode is now sufficiently refined for conversion to C#. The program that implements the pseudocode algorithm and sample outputs are shown in Fig. 5.12.

Table 5.11. Pseudocode for the examination-results problem.

 1   initialize passes to zero
 2   initialize failures to zero
 3   initialize student counter to one
 4
 5   while student counter is less than or equal to 10
 6         prompt the user to enter the next exam result
 7         input the next exam result
 8
 9         if the student passed
10               add one to passes
11         else
12               add one to failures
13
14         add one to student counter
15
16   display the number of passes
17   display the number of failures
18
19   if more than eight students passed
20         display “Bonus to instructor!”

Example 5.12. Analysis of examination results, using nested control statements.

 1   // Fig. 5.12: Analysis.cs
 2   // Analysis of examination results, using nested control statements.
 3   using System;
 4
 5   public class Analysis
 6   {
 7      public static void Main( string[] args )
 8      {
 9         // initialize variables in declarations   
10         int passes = 0; // number of passes       
11         int failures = 0; // number of failures   
12         int studentCounter = 1; // student counter
13         int result; // one exam result from user
14
15         // process 10 students using counter-controlled repetition
16         while ( studentCounter <= 10 )
17         {
18            // prompt user for input and obtain a value from the user
19            Console.Write( "Enter result (1 = pass, 2 = fail): " );
20            result = Convert.ToInt32( Console.ReadLine() );
21
22            // if...else nested in while                     
23            if ( result == 1 ) // if result 1,               
24               passes = passes + 1; // increment passes      
25            else // else result is not 1, so                 
26               failures = failures + 1; // increment failures
27
28            // increment studentCounter so loop eventually terminates
29            studentCounter = studentCounter + 1;
30         } // end while
31
32         // termination phase; prepare and display results
33         Console.WriteLine( "Passed: {0}
Failed: {1}", passes, failures );
34
35         // determine whether more than 8 students passed
36         if ( passes > 8 )
37            Console.WriteLine( "Bonus to instructor!" );
38      } // end Main
39   } // end class Analysis
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 2
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Passed: 9
Failed: 1
Bonus to instructor!
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 2
Enter result (1 = pass, 2 = fail): 2
Enter result (1 = pass, 2 = fail): 2
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 2
Enter result (1 = pass, 2 = fail): 2
Passed: 5
Failed: 5

This example contains only one class, with method Main performing all the class’s work. In this chapter and in Chapter 4, you’ve seen examples consisting of two classes—one class containing methods that perform useful tasks and one containing method Main, which creates an object of the other class and calls its methods. Occasionally, when it makes no sense to try to create a reusable class, we’ll use a mechanical example contained entirely within the Main method of a single class.

Lines 10–13 of Fig. 5.12 declare the variables that method Main uses to process the examination results. Several of these declarations use C#’s ability to incorporate variable initialization into declarations (passes is assigned 0, failures is assigned 0 and studentCounter is assigned 1).

The while statement (lines 16–30) loops 10 times. During each repetition, the loop inputs and processes one exam result. Notice that the if...else statement (lines 23–26) for processing each result is nested in the while statement. If the result is 1, the if...else statement increments passes; otherwise, it assumes the result is 2 and increments failures. Line 29 increments studentCounter before the loop condition is tested again at line 16. After 10 values have been input, the loop terminates and line 33 displays the number of passes and the number of failures. Lines 36–37 determine whether more than eight students passed the exam and, if so, outputs the message "Bonus to instructor!".

Figure 5.12 shows the input and output from two sample executions of the application. During the first sample execution, the condition at line 36 is true—more than eight students passed the exam, so the application outputs a message indicating that the instructor should receive a bonus.

Error-Prevention Tip 5.4

Error-Prevention Tip 5.4

Initializing local variables when they’re declared helps you avoid compilation errors that might arise from attempts to use uninitialized data. While C# does not require that local-variable initializations be incorporated into declarations, it does require that local variables be initialized before their values are used in an expression.

Compound Assignment Operators

C# provides several compound assignment operators for abbreviating assignment expressions. Any statement of the form

variable =  variable  operator  expression;

where operator is one of the binary operators +, -, *, / or % (or others we discuss later in the text) can be written in the form

variable  operator=  expression;

For example, you can abbreviate the statement

c = c + 3;

with the addition compound assignment operator, +=, as

c += 3;

The += operator adds the value of the expression on the right of the operator to the value of the variable on the left of the operator and stores the result in the variable on the left of the operator. Thus, the assignment expression c += 3 adds 3 to c. Figure 5.13 shows the arithmetic compound assignment operators, sample expressions using the operators and explanations of what the operators do.

Table 5.13. Arithmetic compound assignment operators.

Assignment operator

Sample expression

Explanation

Assigns

Assume: int c = 3, d = 5, e = 4, f = 6, g = 12;

+=

c += 7

c = c + 7

10 to c

-=

d -= 4

d = d - 4

1 to d

*=

e *= 5

e = e * 5

20 to e

/=

f /= 3

f = f / 3

2 to f

%=

g %= 9

g = g % 9

3 to g

Increment and Decrement Operators

C# provides two unary operators for adding 1 to or subtracting 1 from the value of a numeric variable. These are the unary increment operator, ++, and the unary decrement operator, --, respectively, which are summarized in Fig. 5.14. An application can increment by 1 the value of a variable called c using the increment operator, ++, rather than the expression c = c + 1 or c += 1. An increment or decrement operator that is prefixed to (placed before) a variable is referred to as the prefix increment operator or prefix decrement operator, respectively. An increment or decrement operator that is postfixed to (placed after) a variable is referred to as the postfix increment operator or postfix decrement operator, respectively.

Table 5.14. Increment and decrement operators.

Operator

Called

Sample expression

Explanation

++

prefix increment

++a

Increments a by 1, then uses the new value of a in the expression in which a resides.

++

postfix increment

a++

Uses the current value of a in the expression in which a resides, then increments a by 1.

--

prefix decrement

--b

Decrements b by 1, then uses the new value of b in the expression in which b resides.

--

postfix decrement

b--

Uses the current value of b in the expression in which b resides, then decrements b by 1.

Incrementing (or decrementing) a variable with the prefix increment (or prefix decrement) operator causes it to be incremented (or decremented) by 1; then the new value of the variable is used in the expression in which it appears. Incrementing (or decrementing) the variable with the postfix increment (or postfix decrement) operator causes the variable’s current value to be used in the expression in which it appears; then the variable’s value is incremented (or decremented) by 1.

Good Programming Practice 5.7

Good Programming Practice 5.7

Unlike binary operators, the unary increment and decrement operators should (by convention) be placed next to their operands, with no intervening spaces.

Figure 5.15 demonstrates the difference between the prefix increment and postfix increment versions of the ++ increment operator. The decrement operator (--) works similarly. In this example, we simply want to show the mechanics of the ++ operator, so we use only one class declaration containing method Main.

Example 5.15. Prefix increment and postfix increment operators.

 1   // Fig. 5.15: Increment.cs
 2   // Prefix increment and postfix increment operators.
 3   using System;
 4
 5   public class Increment
 6   {
 7      public static void Main( string[] args )
 8      {
 9         int c;
10
11         // demonstrate postfix increment operator
12         c = 5; // assign 5 to c
13         Console.WriteLine( c ); // display 5
14         Console.WriteLine( c++ ); // display 5 again, then increment
15         Console.WriteLine( c ); // display 6                        
16
17         Console.WriteLine(); // skip a line
18
19         // demonstrate prefix increment operator
20         c = 5; // assign 5 to c
21         Console.WriteLine( c ); // display 5
22         Console.WriteLine( ++c ); // increment, then display
23         Console.WriteLine( c ); // display 6 again          
24      } // end Main
25   } // end class Increment

5
5
6

5
6
6

Line 12 initializes the variable c to 5, and line 13 outputs c’s initial value. Line 14 outputs the value of the expression c++. This expression performs the postfix increment operation on the variable c, so c’s original value (5) is output, then c’s value is incremented. Thus, line 14 outputs c’s initial value (5) again. Line 15 outputs c’s new value (6) to prove that the variable’s value was indeed incremented in line 14.

Line 20 resets c’s value to 5, and line 21 outputs c’s value. Line 22 outputs the value of the expression ++c. This expression performs the prefix increment operation on c, so its value is incremented; then the new value (6) is output. Line 23 outputs c’s value again to show that the value of c is still 6 after line 22 executes.

The arithmetic compound assignment operators and the increment and decrement operators can be used to simplify statements. For example, the three assignment statements in Fig. 5.12 (lines 24, 26 and 29)

passes = passes + 1;
failures = failures + 1;
studentCounter = studentCounter + 1;

can be written more concisely with compound assignment operators as

passes += 1;
failures += 1;
studentCounter += 1;

and even more concisely with prefix increment operators as

++passes;
++failures;
++studentCounter;

or with postfix increment operators as

passes++;
failures++;
studentCounter++;

When incrementing or decrementing a variable in a statement by itself, the prefix increment and postfix increment forms have the same effect, and the prefix decrement and postfix decrement forms have the same effect. It’s only when a variable appears in the context of a larger expression that the prefix increment and postfix increment have different effects (and similarly for the prefix decrement and postfix decrement).

Common Programming Error 5.7

Common Programming Error 5.7

Attempting to use the increment or decrement operator on an expression other than one to which a value can be assigned is a syntax error. For example, writing ++(x + 1) is a syntax error, because (x + 1) is not an expression to which a value can be assigned.

Figure 5.16 shows the precedence and associativity of the operators we’ve introduced to this point. The operators are shown from top to bottom in decreasing order of precedence. The second column describes the associativity of the operators at each level of precedence. The conditional operator (?:); the unary operators prefix increment (++), prefix decrement (--), plus (+) and minus (-); the cast operators; and the assignment operators =, +=, -=, *=, /= and %= associate from right to left. All the other operators in the operator precedence chart in Fig. 5.16 associate from left to right. The third column names the groups of operators.

Table 5.16. Precedence and associativity of the operators discussed so far.

Operators

Associativity

Type

.

new

++(postfix)

--(postfix)

left to right

highest precedence

++

--

+

-

(type)

right to left

unary prefix

*

/

%

   

left to right

multiplicative

+

-

    

left to right

additive

<

<=

>

>=

  

left to right

relational

==

!=

    

left to right

equality

?:

     

right to left

conditional

=

+=

-=

*=

/=

%=

right to left

assignment

Simple Types

The table in Appendix B lists the 13 simple types in C#. Like its predecessor languages C and C++, C# requires all variables to have a type. For this reason, C# is referred to as a strongly typed language.

In C and C++, programmers frequently have to write separate versions of applications to support different computer platforms, because the simple types are not guaranteed to be identical from computer to computer. For example, an int value on one machine might be represented by 16 bits (2 bytes) of storage, while an int value on another machine might be represented by 32 bits (4 bytes) of storage. In C#, int values are always 32 bits (4 bytes). In fact, all C# numeric types have fixed sizes, as is shown in Appendix B.

Each type in Appendix B is listed with its size in bits (there are eight bits to a byte) and its range of values. Because the designers of C# want it to be maximally portable, they use internationally recognized standards for both character formats (Unicode; for more information, see Appendix F, Unicode®) and floating-point numbers (IEEE 754; for more information, visit grouper.ieee.org/groups/754/).

Recall from Section 4.5 that variables of simple types declared outside of a method as fields of a class are automatically assigned default values unless explicitly initialized. Instance variables of types char, byte, sbyte, short, ushort, int, uint, long, ulong, float, double, and decimal are all given the value 0 by default. Instance variables of type bool are given the value false by default. Similarly, reference-type instance variables are initialized by default to the value null.

Wrap-Up

This chapter presented basic problem-solving techniques that programmers use in building classes and developing methods for these classes. We demonstrated how to construct an algorithm (i.e., an approach to solving a problem), then how to refine the algorithm through several phases of pseudocode development, resulting in C# code that can be executed as part of a method. The chapter showed how to use top-down, stepwise refinement to plan out the specific actions that a method must perform and the order in which the method must perform these actions.

Only three types of control structures—sequence, selection and repetition—are needed to develop any algorithm. Specifically, we demonstrated the if single-selection statement, the if...else double-selection statement and the while repetition statement. These are some of the building blocks used to construct solutions to many problems. We used control-statement stacking to compute the total and the average of a set of student grades with counter- and sentinel-controlled repetition, and we used control-statement nesting to analyze and make decisions based on a set of exam results. We introduced C#’s compound assignment, unary cast, conditional (?:), increment and decrement operators. Finally, we discussed the simple types. In Chapter 6, we continue our discussion of control statements, introducing the for, do...while and switch statements.

Summary

Section 5.2 Algorithms

  • An algorithm is a procedure for solving a problem in terms of the actions to execute and the order in which these actions execute.

  • Specifying the order in which statements (actions) execute in an application is called program control.

Section 5.3 Pseudocode

  • Pseudocode is an informal language that helps you develop algorithms without having to worry about the strict details of C# language syntax.

  • Carefully prepared pseudocode can easily be converted to a corresponding C# application.

Section 5.4 Control Structures

  • There are three types of control structures—sequence, selection and repetition.

  • The sequence structure is built into C#. Unless directed otherwise, the computer executes C# statements one after the other in the order in which they’re written.

  • Activity diagrams are part of the UML. An activity diagram models the workflow of a portion of a software system.

  • Activity diagrams are composed of special-purpose symbols, such as action-state symbols, diamonds and small circles. These symbols are connected by transition arrows, which represent the flow of the activity.

  • Like pseudocode, activity diagrams help you develop and represent algorithms. Activity diagrams clearly show how control structures operate.

  • Action-state symbols (rectangles with their left and right sides replaced with arcs curving outward) represent actions to perform.

  • The arrows in an activity diagram represent transitions, which indicate the order in which the actions represented by the action states occur.

  • The solid circle in an activity diagram represents the activity’s initial state. The solid circle surrounded by a hollow circle represents the final state.

  • Rectangles with the upper-right corners folded over are UML notes (like comments in C#)—explanatory remarks that describe the purpose of symbols in the diagram.

  • C# has three types of selection statements: the if statement, the if...else statement and the switch statement.

  • The if statement is called a single-selection statement because it selects or ignores a single action.

  • The if...else statement is called a double-selection statement because it selects between two different actions (or groups of actions).

  • The switch statement is called a multiple-selection statement because it selects among many different actions (or groups of actions).

  • C# provides four repetition statements: the while, do...while, for and foreach statements.

  • The while, for and foreach statements perform the actions in their bodies zero or more times.

  • The do...while statement performs the actions in its body one or more times.

  • Control statements may be connected in two ways: control-statement stacking and control-statement nesting.

Section 5.5 if Single-Selection Statement

  • The if single-selection statement performs an indicated action (or group of actions) only when the condition is true; otherwise, the action is skipped.

  • In an activity diagram, the diamond symbol indicates that a decision is to be made. The workflow will continue along a path determined by the symbol’s associated guard conditions.

  • When modelled by a UML activity diagram, all control statements contain initial states, transition arrows, action states and decision symbols.

Section 5.6 if...else Double-Selection Statement

  • The if...else double-selection statement allows you to specify an action (or group of actions) to perform when the condition is true and a different action (or group of actions) when the condition is false.

  • C# provides the conditional operator (?:), which can be used in place of an if...else statement. The conditional expression evaluates to the second operand if the first operand evaluates to true, and evaluates to the third operand if the first operand evaluates to false.

  • To include several statements in the body of an if (or the body of an else for an if...else statement), enclose the statements in braces ({ and }).

  • A set of statements contained within a pair of braces is called a block. A block can be placed anywhere in an application that a single statement can be placed.

Section 5.7 while Repetition Statement

  • A repetition statement allows you to specify that an application should repeat an action while some condition remains true.

  • The format for the while repetition statement is

    while ( condition )
       statement

Section 5.8 Formulating Algorithms: Counter-Controlled Repetition

  • Counter-controlled repetition is a technique that uses a variable called a counter to control the number of times a set of statements will execute.

  • A variable is said to be definitely assigned when the variable is guaranteed to be assigned a value in every possible flow of control. Local variables must be definitely assigned before they’re used in calculations.

  • Dividing two integers results in integer division—any fractional part of the calculation is lost.

Section 5.9 Formulating Algorithms: Sentinel-Controlled Repetition

  • Sentinel-controlled repetition is a technique that uses a special value called a sentinel value to indicate “end of data entry.”

Section 5.10 Formulating Algorithms: Nested Control Statements

  • The unary cast operator (double) creates a temporary floating-point copy of its operand. Using a cast operator in this manner is called explicit conversion.

  • To ensure that both operands of a binary operator are of the same type, C# performs promotion on selected operands.

  • The format specifier F indicates that a variable’s value should be displayed as a real number.

Section 5.11 Compound Assignment Operators

  • C# provides several compound assignment operators for abbreviating assignment expressions, including +=, -=, *=, /= and %=.

Section 5.12 Increment and Decrement Operators

  • C# provides the unary increment operator, ++, and the unary decrement operator, --, for adding 1 to or subtracting 1 from the value of a numeric variable.

  • Incrementing (or decrementing) a variable with the prefix increment (or prefix decrement) operator causes the variable to be incremented (decremented) by 1; then the new value of the variable is used in the expression in which it appears. Incrementing (or decrementing) the variable with the postfix increment (or postfix decrement) operator causes the current value of the variable to be used in the expression in which it appears; then the variable’s value is incremented (decremented) by 1.

Section 5.13 Simple Types

  • C# is a strongly typed language—it requires all variables to have a type.

  • Variables of simple types declared outside a method as fields of a class are automatically assigned default values. Instance variables of types char, byte, sbyte, short, ushort, int, uint, long, ulong, float, double, and decimal are all given the value 0 by default. Instance variables of type bool are given the value false by default. Reference-type instance variables are initialized by default to the value null.

Terminology

Self-Review Exercises

5.1

Fill in the blanks in each of the following statements:

  1. All applications can be written in terms of three types of control structures: _______, _______ and _______.

  2. The _______ statement is used to execute one action when a condition is true and another when that condition is false.

  3. Repeating a set of instructions a specific number of times is called _______ repetition.

  4. When it’s not known in advance how many times a set of statements will be repeated, a(n) _______ value can be used to terminate the repetition.

  5. The _______ structure is built into C#—by default, statements execute in the order they appear.

  6. Instance variables of type int are given the value _______ by default.

  7. C# is a(n) _______ language—it requires all variables to have a type.

  8. If the increment operator is _______ to a variable, the variable is incremented by 1 first, then its new value is used in the expression.

5.1

  1. sequence, selection, repetition.

  2. if...else.

  3. counter-controlled (or definite).

  4. sentinel, signal, flag or dummy.

  5. sequence.

  6. 0 (zero).

  7. strongly typed.

  8. prefixed.

5.2

State whether each of the following is true or false. If false, explain why.

  1. An algorithm is a procedure for solving a problem in terms of the actions to execute and the order in which these actions execute.

  2. A set of statements contained within a pair of parentheses is called a block.

  3. A selection statement specifies that an action is to be repeated while some condition remains true.

  4. A nested control statement appears in the body of another control statement.

  5. C# provides the arithmetic compound assignment operators +=, -=, *=, /= and %= for abbreviating assignment expressions.

  6. Specifying the order in which statements (actions) execute in an application is called program control.

  7. The unary cast operator (double) creates a temporary integer copy of its operand.

  8. Instance variables of type bool are given the value true by default.

  9. Pseudocode helps you think out an application before attempting to write it in a programming language.

5.2

  1. True.

  2. False. A set of statements contained within a pair of braces ({ and }) is called a block.

  3. False. A repetition statement specifies that an action is to be repeated while some condition remains true. A selection statement determines whether an action is performed based on the truth or falsity of a condition.

  4. True.

  5. True.

  6. True.

  7. False. The unary cast operator (double) creates a temporary floating-point copy of its operand.

  8. False. Instance variables of type bool are given the value false by default.

  9. True.

5.3

Write four different C# statements that each add 1 to int variable x.

5.3

x = x + 1;
x += 1;
++x;
x++;

5.4

Write C# statements to accomplish each of the following tasks:

  1. Assign the sum of x and y to z, and increment x by 1 after the calculation. Use only one statement.

  2. Test whether variable count is greater than 10. If it is, display "Count is greater than 10".

  3. Decrement the variable x by 1, then subtract it from the variable total. Use only one statement.

  4. Calculate the remainder after q is divided by divisor, and assign the result to q. Write this statement in two different ways.

5.4

  1. z = x++ + y;
  2. if ( count > 10 )
       Console.WriteLine( "Count is greater than 10" );
  3. total -= --x;
  4. q %= divisor;
    q = q % divisor;

5.5

Write a C# statement to accomplish each of the following tasks:

  1. Declare variable sum to be of type int.

  2. Declare variable x to be of type int.

  3. Assign 1 to variable x.

  4. Assign 0 to variable sum.

  5. Add variable x to variable sum, and assign the result to variable sum.

  6. Display "The sum is: ", followed by the value of variable sum.

5.5

  1. int sum;
  2. int x;
  3. x = 1;
  4. sum = 0;
  5. sum += x; or  sum = sum + x;
  6. Console.WriteLine( "The sum is: {0}", sum );

5.6

Combine the statements that you wrote in Exercise 5.5 into a C# application that calculates and displays the sum of the integers from 1 to 10. Use a while statement to loop through the calculation and increment statements. The loop should terminate when the value of x becomes 11.

5.6

The application is as follows:

 1   // Ex. 5.6: Calculate.cs
 2   // Calculate the sum of the integers from 1 to 10
 3   using System;
 4
 5   public class Calculate
 6   {
 7      public static void Main( string[] args )
 8      {
 9         int sum;
10         int x;
11
12         x = 1; // initialize x to 1 for counting
13         sum = 0; // initialize sum to 0 for totaling
14
15         while ( x <= 10 ) // while x is less than or equal to 10
16         {
17            sum += x; // add x to sum
18            ++x; // increment x
19         } // end while
20
21         Console.WriteLine( "The sum is: {0}", sum );
22      } // end Main
23   } // end class Calculate
The sum is: 55

5.7

Determine the values of the variables in the following statement after it executes. Assume that when the statement begins executing, all variables are type int and have the value 5.

product *= x++;

5.7

product = 25, x = 6

5.8

Identify and correct the errors in each of the following sets of code:

  1. while ( c <= 5 )
    {
       product *= c;
       ++c;
  2. if ( gender == 1 )
       Console.WriteLine( "Woman" );
    else;
       Console.WriteLine( "Man" );

5.8

  1. Error: The closing right brace of the while statement’s body is missing.

    Correction: Add a closing right brace after the statement ++c;.

  2. Error: The semicolon after else results in a logic error. The second output statement will always execute.

    Correction: Remove the semicolon after else.

5.9

What is wrong with the following while statement?

while ( z >= 0 )
  sum += z;

5.9

The value of the variable z is never changed in the while statement. Therefore, an infinite loop occurs if the loop-continuation condition ( z >= 0) is initially true. To prevent an infinite loop, z must be decremented so that it eventually becomes less than 0.

Answers to Self-Review Exercises

Exercises

5.10

Compare and contrast the if single-selection statement and the while repetition statement. How are these two statements similar? How are they different?

5.11

(Integer Division) Explain what happens when a C# application attempts to divide one integer by another. What happens to the fractional part of the calculation? How can you avoid that outcome?

5.12

(Combining Control Statements) Describe the two ways in which control statements can be combined.

5.13

(Choosing Repetition Statements) What type of repetition would be appropriate for calculating the sum of the first 100 positive integers? What type of repetition would be appropriate for calculating the sum of an arbitrary number of positive integers? Briefly describe how each of these tasks could be performed.

5.14

(Prefix vs. Postfix Increment Operators) What is the difference between the prefix increment operator and the postfix increment operator?

5.15

(Find the Error) Identify and correct the errors in each of the following pieces of code. [Note: There may be more than one error in each piece of code.]

  1. if ( age >= 65 );
       Console.WriteLine( "Age greater than or equal to 65" );
    else
       Console.WriteLine( "Age is less than 65 )";
  2. int x = 1, total;
    while ( x <= 10 )
    {
       total += x;
       ++x;
    }
  3. while ( x <= 100 )
      total += x;
      ++x;
  4. while ( y > 0 )
    {
       Console.WriteLine( y );
       ++y;

5.16

(What Does This Program Do?) What does the following application display?

 1   // Ex. 5.16: Mystery.cs
 2   using System;
 3
 4   public class Mystery
 5   {
 6      public static void Main( string[] args )
 7      {
 8         int y;
 9         int x = 1;
10         int total = 0;
11
12         while ( x <= 10 )
13         {
14            y = x * x;
15            Console.WriteLine( y );
16            total += y;
17            ++x;
18         } // end while
19
20         Console.WriteLine( "Total is {0}", total );
21      } // end Main
22   } // end class Mystery

For Exercises 5.175.20, perform each of the following steps:

  1. Read the problem statement.

  2. Formulate the algorithm using pseudocode and top-down, stepwise refinement.

  3. Write a C# application.

  4. Test, debug and execute the C# application.

  5. Process three complete sets of data.

5.17

(Gas Mileage) Drivers are concerned with the mileage their automobiles get. One driver has kept track of several tankfuls of gasoline by recording the miles driven and gallons used for each tankful. Develop a C# application that will input the miles driven and gallons used (both as integers) for each tankful. The application should calculate and display the miles per gallon obtained for each tankful and display the combined miles per gallon obtained for all tankfuls up to this point. All averaging calculations should produce floating-point results. Display the results rounded to the nearest hundredth. Use the Console class’s ReadLine method and sentinel-controlled repetition to obtain the data from the user.

5.18

(Credit Limit Calculator) Develop a C# application that will determine whether any of several department-store customers has exceeded the credit limit on a charge account. For each customer, the following facts are available:

  1. account number

  2. balance at the beginning of the month

  3. total of all items charged by the customer this month

  4. total of all credits applied to the customer’s account this month

  5. allowed credit limit.

The application should input all these facts as integers, calculate the new balance (= beginning balance + charges − credits), display the new balance and determine whether the new balance exceeds the customer’s credit limit. For those customers whose credit limit is exceeded, the application should display the message "Credit limit exceeded". Use sentinel-controlled repetition to obtain the data for each account.

5.19

(Sales Commission Calculator) A large company pays its salespeople on a commission basis. The salespeople receive $200 per week plus 9% of their gross sales for that week. For example, a salesperson who sells $5,000 worth of merchandise in a week receives $200 plus 9% of $5,000, or a total of $650. You’ve been supplied with a list of the items sold by each salesperson. The values of these items are as follows:

Item     Value
1        239.99
2        129.75
3         99.95
4        350.89

Develop a C# application that inputs one salesperson’s items sold for the last week, then calculates and displays that salesperson’s earnings. There’s no limit to the number of items that can be sold by a salesperson.

5.20

(Salary Calculator) Develop a C# application that will determine the gross pay for each of three employees. The company pays straight time for the first 40 hours worked by each employee and time-and-a-half for all hours worked in excess of 40 hours. You are given a list of the three employees of the company, the number of hours each employee worked last week and the hourly rate of each employee. Your application should input this information for each employee, then should determine and display the employee’s gross pay. Use the Console class’s ReadLine method to input the data.

5.21

(Find the Largest Number) The process of finding the maximum value (i.e., the largest of a group of values) is used frequently in computer applications. For example, an application that determines the winner of a sales contest would input the number of units sold by each salesperson. The salesperson who sells the most units wins the contest. Write a pseudocode application, then a C# application that inputs a series of 10 integers, then determines and displays the largest integer. Your application should use at least the following three variables:

  1. counterA counter to count to 10 (i.e., to keep track of how many numbers have been input and to determine when all 10 numbers have been processed).

  2. numberThe integer most recently input by the user.

  3. largestThe largest number found so far.

5.22

(Tabular Output) Write a C# application that uses looping to display the following table of values:

N       10*N    100*N   1000*N

1       10      100     1000
2       20      200     2000
3       30      300     3000
4       40      400     4000
5       50      500     5000

5.23

(Find the Two Largest Numbers) Using an approach similar to that for Exercise 5.21, find the two largest values of the 10 values entered. [Note: You may input each number only once.]

5.24

(Validating User Input) Modify the application in Fig. 5.12 to validate its inputs. For any input, if the value entered is other than 1 or 2, display the message “Invalid input,” then keep looping until the user enters a correct value.

5.25

(What Does This Program Do?) What does the following application display?

 1   // Ex. 5.25: Mystery2.cs
 2   using System;
 3
 4   public class Mystery2
 5   {
 6      public static void Main( string[] args )
 7      {
 8         int count = 1;
 9
10         while ( count <= 10 )
11         {
12            Console.WriteLine( count % 2 == 1 ? "****" : "++++++++" );
13            ++count;
14         } // end while
15      } // end Main
16   } // end class Mystery2

5.26

(What Does This Program Do?) What does the following application display?

 1   // Ex. 5.26: Mystery3.cs
 2   using System;
 3
 4   public class Mystery3
 5   {
 6      public static void Main( string[] args )
 7      {
 8         int row = 10;
 9         int column;
10
11         while ( row >= 1 )
12         {
13            column = 1;
14
15            while ( column <= 10 )
16            {
17               Console.Write( row % 2 == 1 ? "<" : ">" );
18               ++column;
19            } // end while
20
21            --row;
22            Console.WriteLine();
23         } // end while
24      } // end Main
25   } // end class Mystery3

5.27

(Dangling-else Problem) Determine the output for each of the given sets of code when x is 9 and y, is 11 and when x is 11 and y is 9. The compiler ignores the indentation in a C# application. Also, the C# compiler always associates an else with the immediately preceding if unless told to do otherwise by the placement of braces ({}). On first glance, you may not be sure which if an else matches—this situation is referred to as the “dangling-else problem.” We’ve eliminated the indentation from the following code to make the problem more challenging. [Hint: Apply the indentation conventions you’ve learned.]

  1. if ( x < 10 )
    if ( y > 10 )
    Console.WriteLine( "*****" );
    else
    Console.WriteLine( "#####" );
    Console.WriteLine( "$$$$$" );
  2. if ( x < 10 )
    {
    if ( y > 10 )
    Console.WriteLine( "*****" );
    }
    else
    {
    Console.WriteLine( "#####" );
    Console.WriteLine( "$$$$$" );
    }

5.28

(Another Dangling-else Problem) Modify the given code to produce the output shown in each part of the problem. Use proper indentation techniques. Make no changes other than inserting braces and changing the indentation of the code. The compiler ignores indentation in a C# application. We’ve eliminated the indentation from the given code to make the problem more challenging. [Note: It’s possible that no modification is necessary for some of the parts.]

if ( y == 8 )
if ( x == 5 )
Console.WriteLine( "@@@@@" );
else
Console.WriteLine( "#####" );
Console.WriteLine( "$$$$$" );
Console.WriteLine( "&&&&&" );
  1. Assuming that x = 5 and y = 8, the following output is produced:

    @@@@@
    $$$$$
    &&&&&
  2. Assuming that x = 5 and y = 8, the following output is produced:

    @@@@@
  3. Assuming that x = 5 and y = 8, the following output is produced:

    @@@@@
    &&&&&
  4. Assuming that x = 5 and y = 7, the following output is produced.

    #####
    $$$$$
    &&&&&

5.29

(Square of Asterisks) Write an application that prompts the user to enter the size of the side of a square, then displays a hollow square of that size made of asterisks. Your application should work for squares of all side lengths between 1 and 20. If the user enters a number less than 1 or greater than 20, your application should display a square of size 1 or 20, respectively.

5.30

(Palindromes) A palindrome is a sequence of characters that reads the same backward as forward. For example, each of the following five-digit integers is a palindrome: 12321, 55555, 45554 and 11611. Write an application that reads in a five-digit integer and determines whether it’s a palindrome. If the number is not five digits long, display an error message and allow the user to enter a new value. [Hint: Use the remainder and division operators to pick off the number’s digits one at a time, from right to left.]

5.31

(Printing the Decimal Equivalent of a Binary Number) Write an application that inputs an integer containing only 0s and 1s (i.e., a binary integer) and displays its decimal equivalent. [Hint: Picking the digits off a binary number is similar to picking the digits off a decimal number, which you did in Exercise 5.30. In the decimal number system, the rightmost digit has a positional value of 1 and the next digit to the left has a positional value of 10, then 100, then 1000 and so on. The decimal number 234 can be interpreted as 4 * 1 + 3 * 10 + 2 * 100. In the binary number system, the rightmost digit has a positional value of 1, the next digit to the left has a positional value of 2, then 4, then 8 and so on. The decimal equivalent of binary 1101 is 1 * 1 + 0 * 2 + 1 * 4 + 1 * 8, or 1 + 0 + 4 + 8, or 13.]

5.32

(Checkerboard Pattern of Asterisks) Write an application that uses only the output statements

Console.Write( "* " );
Console.Write( " " );
Console.WriteLine();

to display the checkerboard pattern that follows. A Console.WriteLine method call with no arguments outputs a single newline character. [Hint: Repetition statements are required.]

* * * * * * * *
 * * * * * * * *
* * * * * * * *
 * * * * * * * *
* * * * * * * *
 * * * * * * * *
* * * * * * * *
 * * * * * * * *

5.33

(Multiples of 2) Write an application that keeps displaying in the console window the powers of the integer 2—namely, 2, 4, 8, 16, 32, 64 and so on. Loop 40 times. What happens when you run this application?

5.34

(What’s Wrong with This Code?) What is wrong with the following statement? Provide the correct statement to add 1 to the sum of x and y.

Console.WriteLine( ++(x + y) );

5.35

(Sides of a Triangle) Write an application that reads three nonzero values entered by the user, then determines and displays whether they could represent the sides of a triangle.

5.36

(Sides of a Right Triangle) Write an application that reads three nonzero integers, then determines and displays whether they could represent the sides of a right triangle.

5.37

(Factorials) The factorial of a nonnegative integer n is written as n! (pronounced “n factorial”) and is defined as follows:

  • n! = n · (n − 1) · (n − 2) · ... · 1 (for values of n greater than or equal to 1)

and

  • n! = 1 (for n = 0)

For example, 5! = 5 · 4 · 3 · 2 · 1, which is 120. Write an application that reads a nonnegative integer and computes and displays its factorial.

5.38

(Infinite Series: Mathematical Constant e) Write an application that estimates the value of the mathematical constant e by using the formula

largest:

The predefined constant Math.E (class Math is in the System namespace) provides a good approximation of e. Use the WriteLine method to output both your estimated value of e and Math.E for comparison.

5.39

(Infinite Series: ex) Write an application that computes the value of ex by using the formula

largest:

Compare the result of your calculation to the return value of the method call

Math.Pow( Math.E, x )

[Note: The predefined method Math.Pow takes two arguments and raises the first argument to the power of the second. We discuss Math.Pow in Section 6.4.]

Making a Difference Exercises

5.40

(World Population Growth) World population has grown considerably over the centuries. Continued growth could eventually challenge the limits of breathable air, drinkable water, arable cropland and other limited resources. There’s evidence that growth has been slowing in recent years and that world population could peak some time this century, then start to decline.

For this exercise, research world population growth issues online. Be sure to investigate various viewpoints. Get estimates for the current world population and its growth rate (the percentage by which it is likely to increase this year). Write a program that calculates world population growth each year for the next 75 years, using the simplifying assumption that the current growth rate will stay constant. When displaying the results, the first column should display the year from year 1 to year 75. The second column should display the anticipated world population at the end of that year. The third column should display the numerical increase in the world population that would occur that year. Using your results, determine the year in which the population would be double what it is today, if this year’s growth rate were to persist. [Hint: Use double variables because int variables can store values only up to approximately two billion. Display the double values using the format F0.]

5.41

(Enforcing Privacy with Cryptography) The explosive growth of Internet communications and data storage on Internet-connected computers has greatly increased privacy concerns. The field of cryptography is concerned with coding data to make it difficult (and hopefully—with the most advanced schemes—impossible) for unauthorized users to read. In this exercise you’ll investigate a simple scheme for encrypting and decrypting data. A company that wants to send data over the Internet has asked you to write a program that will encrypt it so that it may be transmitted more securely. All the data is transmitted as four-digit integers. Your application should read a four-digit integer entered by the user and encrypt it as follows: Replace each digit with the result of adding 7 to the digit and getting the remainder after dividing the new value by 10. Then swap the first digit with the third, and swap the second digit with the fourth. Then display the encrypted integer. Write a separate application that inputs an encrypted four-digit integer and decrypts it (by reversing the encryption scheme) to form the original number. Use the format specifier D4 to display the encrypted value in case the number starts with a 0.



[1] Bohm, C., and G. Jacopini, “Flow Diagrams, Turing Machines, and Languages with Only Two Formation Rules,” Communications of the ACM, Vol. 9, No. 5, May 1966, pp. 336–371.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.62.168