Chapter 5. Getting Into the Program Flow

In This Chapter

  • Making decisions if you can

  • Deciding what else to do

  • Looping without going in a circle

  • Using the while and do . . . while loops

  • Using the for loop and understanding scope

Consider this simple program:

using System;
namespace HelloWorld
{
  public class Program
  {
    // This is where the program starts.
    static void Main(string[] args)
    {
      // Prompt user to enter a name.
      Console.WriteLine("Enter your name, please:");
      // Now read the name entered.
      string name = Console.ReadLine();
      // Greet the user with the entered name.
      Console.WriteLine("Hello, " + name);
      // Wait for user to acknowledge the results.
      Console.WriteLine("Press Enter to terminate . . . ");
      Console.Read();
    }
  }
}

Beyond introducing you to a few fundamentals of C# programming, this program is almost worthless. It simply spits back out whatever you entered. You can imagine more complicated program examples that accept input, perform some type of calculations, generate some type of output (otherwise, why do the calculations?), and then exit at the bottom. However, even a program such as this one can be of only limited use.

One key element of any computer processor is its ability to make decisions. When I say "make decisions," I mean that the processor sends the flow of execution down one path of instructions if a condition is true or down another path if the condition is not true. Any programming language must offer this fundamental capability to control the flow of execution.

The three basic types of flow control are the if statement, the loop, and the jump. (I describe one of the looping controls, the foreach, in Chapter 6 of this minibook.)

Branching Out with if and switch

The basis of all C# decision-making capability is the if statement (and the basis of all my decisions is the maybe):

if (bool-expression)
{
    // Control goes here if the expression is true.
}
// Control passes to this statement whether the expression is true or not.

A pair of parentheses immediately following the keyword if contains a conditional expression of type bool. (See Chapter 2 of this minibook for a discussion of bool expressions.) Immediately following the expression is a block of code set off by a pair of braces. If the expression is true, the program executes the code within the braces; if the expression is not true, the program skips the code in the braces. (If the program executes the code in braces, it ends just after the closing brace and continues from there.)

The if statement is easier to understand by looking at a concrete example:

// Make sure that a is not negative:
// If a is less than 0 . . .
if (a < 0)
{
    //  . . . then assign 0 to it so that it's no longer negative.
    a = 0;
}

This segment of code ensures that the variable a is nonnegative — greater than or equal to 0. The if statement says, "If a is less than 0, assign 0 to a." (In other words, turn a into a positive value.)

Note

The braces aren't required. C# treats if(bool-expression) statement; as though it had been written if(bool-expression) {statement;}. The general consensus (and my preference) is to always use braces for better clarity. In other words, don't ask — just do it.

Introducing the if statement

Consider a small program that calculates interest. The user enters the principal amount and the interest rate, and the program spits out the resulting value for each year. (This program isn't sophisticated.) The simplistic calculation appears as follows in C#:

// Calculate the value of the principal plus interest.
decimal interestPaid;
interestPaid = principal * (interest / 100);
// Now calculate the total.
decimal total = principal + interestPaid;

The first equation multiplies the principal principal times the interest interest to produce the interest to be paid — interestPaid. (You divide by 100 because interest is usually calculated by entering a percentage amount.) The interest to be paid is then added back into the principal, resulting in a new principal, which is stored in the variable total.

The program must anticipate almost anything when dealing with human input. For example, you don't want your program to accept a negative principal or interest amount (well, maybe a negative interest). The following CalculateInterest program includes checks to ensure that neither of these entries happens:

Note

// CalculateInterest -- Calculate the interest amount paid
//    on a given principal. If either the principal or the
//    interest rate is negative, generate an error message.
using System;
namespace CalculateInterest
{
  public class Program
  {
    public static void Main(string[] args)
    {
      // Prompt user to enter source principal.
      Console.Write("Enter principal: ");
      string principalInput = Console.ReadLine();
      decimal principal = Convert.ToDecimal(principalInput);
      // Make sure that the principal is not negative.
      if (principal < 0)
      {
        Console.WriteLine("Principal cannot be negative");
        principal = 0;
      }
      // Enter the interest rate.
      Console.Write("Enter interest: ");
      string interestInput = Console.ReadLine();
      decimal interest = Convert.ToDecimal(interestInput);
      // Make sure that the interest is not negative either.
      if (interest < 0)
      {
        Console.WriteLine("Interest cannot be negative");
        interest = 0;
      }
      // Calculate the value of the principal plus interest.
      decimal interestPaid = principal * (interest / 100);
      // Now calculate the total.
      decimal total = principal + interestPaid;
      // Output the result.
      Console.WriteLine();  // Skip a line.
      Console.WriteLine("Principal     = " + principal);
      Console.WriteLine("Interest      = " + interest + "%");
      Console.WriteLine();
      Console.WriteLine("Interest paid = " + interestPaid);
Console.WriteLine("Total         = " + total);
      // Wait for user to acknowledge the results.
      Console.WriteLine("Press Enter to terminate . . . ");
      Console.Read();
    }
  }
}

Tip

The CalculateInterest program begins by prompting the user for his name using WriteLine() to write a string to the console. Tell the user exactly what you want and, if possible, specify the format. Users don't respond well to uninformative prompts, such as >.

The sample program uses the ReadLine() command to read in whatever the user types; the program returns the value entered, in the form of a string, when the user presses Enter. Because the program is looking for the principal in the form of a decimal, the input string must be converted using the Convert.ToDecimal() command. The result is stored in principalInput.

Note

The ReadLine(), WriteLine(), and ToDecimal() commands are all examples of method calls. A method call delegates some work to another part of the program, called a method. I describe method calls in detail in Book II; these particular method calls are straightforward. You should be able to get the gist of the meaning using my extraordinarily insightful explanatory narrative. If that doesn't work, ignore the narrative. If that doesn't work, see Book II.

The next line in the example checks principal. If it's negative, the program outputs a polite "nastygram" indicating that the user has fouled up. The program does the same thing for the interest rate, and then it performs the simplistic interest calculation outlined earlier, in the "Introducing the If Statement," and spits out the result, using a series of WriteLine() commands.

The program generates the following output with a legitimate principal amount and a usurious interest rate that is perfectly legal in most states:

Enter principal: 1234
Enter interest: 21

Principal     = 1234
Interest      = 21%

Interest paid = 259.14
Total         = 1493.14
Press Enter to terminate . . .

Executing the program with illegal input generates the following output:

Enter principal: 1234
Enter interest: −12.5
Interest cannot be negative

Principal     = 1234
Interest      = 0%
Interest paid = 0
Total         = 1234
Press Enter to terminate . . .

Tip

Indent the lines within an if clause to enhance readability. This type of indentation is ignored by C# but is helpful to us humans. Most programming editors support autoindenting, whereby the editor automatically indents as soon as you enter the if command. To set autoindenting in Visual Studio, choose Tools

Introducing the if statement

Examining the else statement

Sometimes, your code must check for mutually exclusive conditions. For example, the following code segment stores the maximum of two numbers, a and b, in the variable max:

// Store the maximum of a and b into the variable max.
int max;
// If a is greater than b . . .
if (a > b)
{
    //  . . . save a as the maximum.
    max = a;
}
// If a is less than or equal to b . . .
if (a <= b)
{
    //  . . . save b as the maximum.
    max = b;
}

The second if statement causes needless processing because the two conditions are mutually exclusive. If a is greater than b, a can't possibly be less than or equal to b. C# defines an else clause for just this case. The else keyword defines a block of code that's executed if the if block is not.

The code segment to calculate the maximum now appears this way:

// Store the maximum of a and b into the variable max.
int max;
// If a is greater than b . . .
if (a > b)
{
    //  . . . save a as the maximum; otherwise . . .
    max = a;
}
else
{
    //  . . . save b as the maximum.
    max = b;
}

If a is greater than b, the first block is executed; otherwise, the second block is executed. In the end, max contains the greater of a or b.

Avoiding even the else

Sequences of else clauses can become confusing. Some programmers like to avoid them when doing so doesn't cause even more confusion. You could write the maximum calculation like this:

// Store the maximum of a and b into the variable max.
int max;
// Start by assuming that a is greater than b.
max = a;
// If it is not . . .
if (b > a)
{
  //  . . . then you can change your mind.
  max = b;
}

Some programmers avoid this style like the plague, and I can sympathize. (That doesn't mean that I'm going to change; it just means that I sympathize.) You see both this style and the "else style" in common use.

Tip

Programmers who like to be cool and cryptic often use the ternary operator, :?, equivalent to an if/else on one line:

bool informal = true;
string name = informal : "Chuck" ? "Charles";  // Returns "Chuck"

This chunk evaluates the expression before the colon. If the expression is true, return it after the colon but before the question mark. If the expression is false, return it after the question mark. This process turns an if/else into an expression.

I generally advise using ternary only rarely because it truly is cryptic.

Nesting if statements

The CalculateInterest program warns the user of illegal input; however, continuing with the interest calculation, even if one of the values is illogical, doesn't seem quite right. It causes no real harm here because the interest calculation takes little or no time and the user can ignore the results, but some calculations aren't nearly as quick. In addition, why ask the user for an interest rate after she has already entered an invalid value for the principal? The user knows that the results of the calculation will be invalid no matter what she enters next. (You'd be amazed at how much it infuriates users.)

The program should ask the user for an interest rate only if the principal is reasonable and perform the interest calculation only if both values are valid. To accomplish this, you need two if statements, one within the other.

Note

An if statement found within the body of another if statement is an embedded, or nested, statement.

The following program, CalculateInterestWithEmbeddedTest, uses embedded if statements to avoid stupid questions if a problem is detected in the input:

Note

// CalculateInterestWithEmbeddedTest -- Calculate the interest amount
//    paid on a given principal. If either the principal or the
//    interest rate is negative, then generate an error message
//    and don't proceed with the calculation.
using System;
namespace CalculateInterestWithEmbeddedTest
{
  public class Program
  {
    public static void Main(string[] args)
    {
      // Define a maximum interest rate.
      int maximumInterest = 50;
      // Prompt user to enter source principal.
      Console.Write("Enter principal: ");
      string principalInput = Console.ReadLine();
      decimal principal = Convert.ToDecimal(principalInput);
      // If the principal is negative . . .
      if (principal < 0)
      {
        // . . . generate an error message . . .
        Console.WriteLine("Principal cannot be negative");
      }
      else  // Go here only if principal was > 0: thus valid.
      {
        //  . . . otherwise, enter the interest rate.
        Console.Write("Enter interest: ");
        string interestInput = Console.ReadLine();
        decimal interest = Convert.ToDecimal(interestInput);
        // If the interest is negative or too large . . .
        if (interest < 0 || interest > maximumInterest)
        {
          //  . . . generate an error message as well.
          Console.WriteLine("Interest cannot be negative " +
                            "or greater than " + maximumInterest);
          interest = 0;
        }
        else  // Reach this point only if all is well.
        {
          // Both the principal and the interest appear to be legal;
          // calculate the value of the principal plus interest.
          decimal interestPaid;
          interestPaid = principal * (interest / 100);
          // Now calculate the total.
          decimal total = principal + interestPaid;
          // Output the result.
          Console.WriteLine();  // Skip a line.
          Console.WriteLine("Principal     = " + principal);
          Console.WriteLine("Interest      = " + interest + "%");
          Console.WriteLine();
          Console.WriteLine("Interest paid = " + interestPaid);
          Console.WriteLine("Total         = " + total);
        }
      }
// Wait for user to acknowledge the results.
      Console.WriteLine("Press Enter to terminate . . . ");
      Console.Read();
    }
  }
}

The program first reads the principal from the user. If the principal is negative, the program outputs an error message and quits. If the principal is not negative, control passes to the else clause, where the program continues executing.

The interest rate test has been improved in this example. Here, the program requires an interest rate that's nonnegative (a mathematical law) and less than a maximum rate (a judiciary law — I can only wish that credit cards had an interest rate limit). This if statement uses the following compound test:

if (interest < 0 || interest > maximumInterest)

This statement is true if interest is less than 0 or greater than maximumInterest. Notice that I declare maximumInterest at the top of the program rather than hard-code it as a constant number here. Hard-coding refers to using values directly in your code, rather than creating a constant to hold them.

Tip

Define important constants at the top of your program. Giving a constant a descriptive name (rather than just a number) makes it easy to find and easier to change. If the constant appears ten times in your code, you still have to make only one change to change all references.

Entering a correct principal but a negative interest rate generates this output:

Enter principal: 1234
Enter interest: −12.5
Interest cannot be negative or greater than 50.
Press Enter to terminate . . .

Only when the user enters both a legal principal and a legal interest rate does the program generate the correct calculation:

Enter principal: 1234
Enter interest: 12.5

Principal     = 1234
Interest      = 12.5%

Interest paid = 154.250
Total         = 1388.250
Press Enter to terminate . . .

Running the switchboard

You often want to test a variable for numerous different values. For example, maritalStatus may be 0 for unmarried, 1 for married, 2 for divorced, 3 for widowed (surely I covered all the options — oh, wait), or 4 for none of your business. To differentiate among these values, you could use the following series of if statements:

if (maritalStatus == 0)
{
  // Must be unmarried . . .
  //  . . . do something . . .
}
else
{
  if (maritalStatus == 1)
  {
    // Must be married . . .
    //  . . . do something else . . .

And so on.

You can see that these repetitive if statements grow tiresome quickly. Testing for multiple cases is such a common occurrence that C# provides a special construct to decide between a set of mutually exclusive conditions. This control, the switch, works as follows:

switch(maritalStatus)
{
  case 0:
           //  . . . do the unmarried stuff . . .
           break;
  case 1:
           //  . . . do the married stuff . . .
           break;
  case 2:
           //  . . . do the divorced stuff . . .
           break;
  case 3:
           //  . . . do the widowed stuff . . .
           break;
  case 4:
           //  . . . get out of my face . . .
           break;
  default:
           // Goes here if it fails to pass a case;
           // this is probably an error condition.
           break;
}

The expression at the top of the switch statement is evaluated. In this case, the expression is simply the variable maritalStatus. The value of that expression is then compared against the value of each of the cases. Control passes to the default clause if no match is found.

The argument to the switch statement can also be a string:

string s = "Davis";
switch(s)
{
  case "Davis":
//  . . . control will actually pass here . . .
           break;
  case "Smith":
           //  . . . do Smith stuff . . .
           break;
  case "Jones":
           //  . . . do Jones stuff . . .
           break;
  case "Hvidsten":
           //  . . . do Hvidsten stuff . . .
           break;
  default:
           // Goes here if it doesn't pass any cases.
           break;
}

Note

Using the switch statement involves these severe restrictions:

  • The argument to the switch() must be one of the counting types (including char) or a string. Floating-point values are excluded.

  • The various case values must refer to values of the same type as the switch expression.

  • The case values must be constant in the sense that their value must be known at compile time. (A statement such as case x isn't legal unless x is a type of constant.)

  • Each clause must end in a break statement (or another exit command, such as return). The break statement passes control out of the switch.

  • You can omit a break statement if two cases lead to the same actions: A single case clause may have more than one case label, as in this example:

    string s = "Davis";
    switch(s)
    {
      case "Davis":
      case "Hvidsten":
             // Do the same thing whether s is Davis or Hvidsten
             // since they're related.
               break;
      case "Smith":
               //  . . . do Smith stuff . . .
               break;
      default:
               // Goes here if it doesn't pass any cases.
               break;
    }

Note

This approach enables the program to perform the same operation, whether the input is Davis or Hvidsten. The SwitchSyntaxTest example on the Web site illustrates a variety of advice about using switch. The final section of this chapter supplies a small addendum to the switch story. You can find the code at csharp102.info.

Here We Go Loop-the-Loop

The if statement enables a program to take different paths through the code being executed depending on the results of a bool expression. This statement provides for drastically more interesting programs than programs without decision-making capability. Adding the ability to execute a set of instructions repeatedly adds another quantum jump in capability.

Consider the CalculateInterest program from the section "Introducing the if statement," earlier in this chapter. Performing this simple interest calculation by using a calculator (or by hand, using a piece of paper) would be much easier than writing and executing a program.

If you could calculate the amount of principal for each of several succeeding years, that would even more useful. A simple macro in a Microsoft Excel spreadsheet is still easier to handle, but at least you're getting closer.

What you need is a way for the computer to execute the same short sequence of instructions multiple times — known as a loop.

Looping for a while

The C# keyword while introduces the most basic form of execution loop:

while(bool-expression)
{
    //  . . . repeatedly executed as long as the expression is true.
}

When the while loop is first encountered, the bool expression is evaluated. If the expression is true, the code within the block is executed. When the block of code reaches the closed brace, control returns to the top and the whole process starts over again. (It's kind of the way I feel when I'm walking the dog. The dog and I loop around and around the yard until the dog . . . well, until he's finished.) Control passes beyond the closed brace the first time the bool expression is evaluated and turns out to be false.

Note

If the condition is not true the first time the while loop is encountered, the set of commands within the braces is never executed.

Warning

Programmers often become sloppy in their speech. (Programmers are sloppy most of the time.) If a programmer says that a loop is executed until a condition is false, it implies that control passes outside the loop — no matter where the program happens to be executing — as soon as the condition becomes false. This definitely isn't the case. The program doesn't check whether the condition is still true until control specifically passes back to the top of the loop.

You can use the while loop to create the CalculateInterestTable program, a looping version of the CalculateInterest program. CalculateInterestTable calculates a table of principals showing accumulated annual payments:

Note

// CalculateInterestTable -- Calculate the interest paid on a given
//    principal over a period of years.
using System;
namespace CalculateInterestTable
{
  using System;
  public class Program
    {
    public static void Main(string[] args)
    {
      // Define a maximum interest rate.
      int maximumInterest = 50;
      // Prompt user to enter source principal.
      Console.Write("Enter principal: ");
      string principalInput = Console.ReadLine();
      decimal principal = Convert.ToDecimal(principalInput);
      // If the principal is negative . . .
      if (principal < 0)
      {
        // . . . generate an error message . . .
        Console.WriteLine("Principal cannot be negative");
      }
      else  // Go here only if principal was > 0: thus valid.
      {
        //  . . . otherwise, enter the interest rate.
        Console.Write("Enter interest: ");
        string interestInput = Console.ReadLine();
        decimal interest = Convert.ToDecimal(interestInput);
        // If the interest is negative or too large . . .
        if (interest < 0 || interest > maximumInterest)
        {
          //  . . . generate an error message as well.
          Console.WriteLine("Interest cannot be negative " +
                            "or greater than " + maximumInterest);
          interest = 0;
        }
        else  // Reach this point only if all is well.
        {
          // Both the principal and the interest appear to be
          // legal; finally, input the number of years.
          Console.Write("Enter number of years: ");
          string durationInput = Console.ReadLine();
          int duration = Convert.ToInt32(durationInput);
          // Verify the input.
          Console.WriteLine();  // Skip a line.
          Console.WriteLine("Principal     = " + principal);
          Console.WriteLine("Interest      = " + interest + "%");
          Console.WriteLine("Duration      = " + duration + " years");
          Console.WriteLine();

          // Now loop through the specified number of years.
          int year = 1;
          while(year <= duration)
          {
            // Calculate the value of the principal plus interest.
            decimal interestPaid;
            interestPaid = principal * (interest / 100);
// Now calculate the new principal by adding
            // the interest to the previous principal amount.
            principal = principal + interestPaid;
            // Round off the principal to the nearest cent.
            principal = decimal.Round(principal, 2);
            // Output the result.
            Console.WriteLine(year + "-" + principal);
            // Skip over to next year.
            year = year + 1;
          }
        }
      }
      // Wait for user to acknowledge the results.
      Console.WriteLine("
Press Enter to terminate . . . ");
      Console.Read();
    }
  }
}

The output from a trial run of CalculateInterestTable appears this way:

Enter principal: 1234
Enter interest: 12.5
Enter number of years: 10

Principal     = 1234
Interest      = 12.5%
Duration      = 10 years

1-1388.25
2-1561.78
3-1757.00
4-1976.62
5-2223.70
6-2501.66
7-2814.37
8-3166.17
9-3561.94
10-4007.18

Press Enter to terminate . . .

Each value represents the total principal after the number of years elapsed, assuming simple interest compounded annually. For example, the value of $1,234 at 12.5 percent is $3,561.94 after nine years.

Note

Most of the values show two decimal places for the cents in the amount. Because trailing zeros aren't displayed in all versions of C#, some values may show only a single digit — or even no digit — after the decimal point. Thus, $12.70 may be displayed as 12.7. If so, you can fix the problem by using the special formatting characters described in Chapter 3 of this minibook. (C# 2.0 and later appear to show trailing zeros by default.)

The CalculateInterestTable program begins by reading the principal and interest values from the user and checking to make sure that they're valid. CalculateInterestTable then reads the number of years over which to iterate and stores this value in the variable duration.

Before entering the while loop, the program declares a variable year, which it initializes to 1. This will be the "current year" — that is, this number changes "each year" as the program loops. If the year number contained in year is less than the total duration contained in duration, the principal for "this year" is recalculated by calculating the interest based on the "previous year." The calculated principal is output along with the current-year offset.

Note

The statement decimal.Round() rounds the calculated value to the nearest fraction of a cent.

The key to the program lies in the last line within the block. The statement year = year + 1; increments year by 1. If year begins with the value 3, its value will be 4 after this expression. This incrementing moves the calculations along from one year to the next.

After the year has been incremented, control returns to the top of the loop, where the value year is compared to the requested duration. In the sample run, if the current year is less than 10, the calculation continues. After being incremented ten times, the value of year becomes 11, which is greater than 10, and program control passes to the first statement after the while loop — the program stops looping.

Tip

Most looping commands follow this basic principle of incrementing a counter until it exceeds a previously defined value.

The counting variable year in CalculateInterestTable must be declared and initialized before the while loop in which it is used. In addition, the year variable must be incremented, usually as the last statement within the loop. As this example demonstrates, you have to look ahead to see which variables you need. This pattern is easier to use after you've written a few thousand while loops, like I have.

Warning

When writing while loops, don't forget to increment the counting variable, as I did in this example:

int nYear = 1;
while (nYear < 10)
{
    //  . . . whatever . . .
}

(We left off the year = year + 1;.) Without the increment, year is always 1 and the program loops forever. The only way to exit this infinite loop is to terminate the program or reboot. (So nothing is truly infinite, with the possible exception of a particle passing through the event horizon of a black hole.)

Note

Make sure that the terminating condition can be satisfied. Usually, this means your counting variable is being incremented properly. Otherwise, you're looking at an infinite loop, an angry user, bad press, and 50 years of drought.

Tip

Infinite loops are a common mistake, so don't be embarrassed when you get caught in one.

Doing the do . . . while loop

A variation of the while loop is the do . . . while loop. In this example, the condition isn't checked until the end of the loop:

int year = 1;
do
{
    //  . . . some calculation . . .
    year = year + 1;
} while (year < duration);

In contrast to the while loop, the do . . . while loop is executed at least once, regardless of the value of duration.

Breaking up is easy to do

You can use two special commands to bail out of a loop: break and continue. Executing the break command causes control to pass to the first expression immediately following the loop. The similar continue command passes control straight back up to the conditional expression at the top of the loop to start over and get it right this time.

Tip

I have rarely used continue in my programming career, and I doubt that many programmers even remember that it exists. Don't forget about it completely because it may be a trick question in an interview or a crossword puzzle.

Suppose that you want to take your money out of the bank as soon as the principal exceeds a certain number of times the original amount, irrespective of the duration in years. (After all, how much money do you really need?) You could easily accommodate this amount by adding the following code within the loop:

if (principal > (maxPower * originalPrincipal))
{
  break;
}

Note

Anyone who watches The Simpsons as much as I do knows who maxPower is. (Hint: D'oh!)

The break clause isn't executed until the condition within the if comparison is true — in this case, until the calculated principal is maxPower times the original principal or more. Executing the break statement passes control outside the while(year <= duration) statement, and the program resumes execution immediately after the loop.

Note

For a version of the interest table program with this addition, see the CalculateInterestTableWithBreak program at csharp102.info. (I don't include the listing here, for brevity's sake.)

An example of output from this program looks like this:

Enter principal: 100
Enter interest: 25
Enter number of years: 100

Principal     = 100
Interest      = 25%
Duration      = 100 years
Quit if a multiplier of 10 is reached

1-125.00
2-156.25
3-195.31
4-244.14
5-305.18
6-381.48
7-476.85
8-596.06
9-745.08
10-931.35
11-1164.19
Press Enter to terminate . . .

The program terminates as soon as the calculated principal exceeds $1,000 — thank goodness, you didn't have to wait 100 years!

Looping until you get it right

The CalculateInterestTable program is smart enough to terminate in the event that the user enters an invalid balance or interest amount. However, jumping immediately out of the program just because the user mistypes something seems harsh. Even my user-unfriendly accounting program gives me three chances to enter the correct password before it gives up.

Note

A combination of while and break enables the program to be a little more flexible. The CalculateInterestTableMoreForgiving program demonstrates the principle this way:

// CalculateInterestTableMoreForgiving -- Calculate the interest paid on a
//    given principal over a period of years. This version gives the user
//    multiple chances to input the legal principal and interest.
using System;
namespace CalculateInterestTableMoreForgiving
{
  using System;
  public class Program
  {
    public static void Main(string[] args)
    {
      // Define a maximum interest rate.
      int maximumInterest = 50;
// Prompt user to enter source principal; keep prompting
      // until the correct value is entered.
      decimal principal;
     while(true)
      {
        Console.Write("Enter principal: ");
        string principalInput = Console.ReadLine();
        principal = Convert.ToDecimal(principalInput);
        // Exit if the value entered is correct.
        if (principal >= 0)
        {
          break;
        }
        // Generate an error on incorrect input.
        Console.WriteLine("Principal cannot be negative");
        Console.WriteLine("Try again");
        Console.WriteLine();
      }
      // Now enter the interest rate.
      decimal interest;
      while(true)
      {
        Console.Write("Enter interest: ");
        string interestInput = Console.ReadLine();
        interest = Convert.ToDecimal(interestInput);
        // Don't accept interest that is negative or too large . . .
        if (interest >= 0 && interest <= maximumInterest)
        {
          break;
        }
        //  . . . generate an error message as well.
        Console.WriteLine("Interest cannot be negative " +
                          "or greater than " + maximumInterest);
        Console.WriteLine("Try again");
        Console.WriteLine();
      }
      // Both the principal and the interest appear to be
      // legal; finally, input the number of years.
      Console.Write("Enter number of years: ");
      string durationInput = Console.ReadLine();
      int duration = Convert.ToInt32(durationInput);
      // Verify the input.
      Console.WriteLine();  // Skip a line.
      Console.WriteLine("Principal     = " + principal);
      Console.WriteLine("Interest      = " + interest + "%");
      Console.WriteLine("Duration      = " + duration + " years");
      Console.WriteLine();
      // Now loop through the specified number of years.
      int year = 1;
      while(year <= duration)
      {
        // Calculate the value of the principal plus interest.
        decimal interestPaid;
        interestPaid = principal * (interest / 100);
        // Now calculate the new principal by adding
        // the interest to the previous principal.
        principal = principal + interestPaid;
        // Round off the principal to the nearest cent.
        principal = decimal.Round(principal, 2);
        // Output the result.
        Console.WriteLine(year + "-" + principal);
        // Skip over to next year.
year = year + 1;
      }
      // Wait for user to acknowledge the results.
      Console.WriteLine("Press Enter to terminate . . . ");
      Console.Read();
    }
  }
}

This program works largely the same way as do the examples in previous sections of this chapter, except in the area of user input. This time, a while loop replaces the if statement used in earlier examples to detect invalid input:

decimal principal;
while(true)
{
  Console.Write("Enter principal: ");
  string principalInput = Console.ReadLine();
  principal = Convert.ToDecimal(principalInput);
  // Exit when the value entered is correct.
  if (principal >= 0)
  {
    break;
  }
  // Generate an error on incorrect input.
  Console.WriteLine("Principal cannot be negative");
  Console.WriteLine("Try again");
  Console.WriteLine();
}

This section of code inputs a value from the user within a loop. If the value of the text is okay, the program exits the input loop and continues. However, if the input has an error, the user sees an error message and control passes back to the program flow to start over.

Note

The program continues to loop until the user enters the correct input. (In the worst case, the program could loop until an obtuse user dies of old age.)

Notice that the conditionals have been reversed because the question is no longer whether illegal input should generate an error message but, rather, whether the correct input should exit the loop. In the interest section, for example, consider this test:

principal < 0 || principal > maximumInterest

This test changes to this:

interest >= 0 && interest <= maximumInterest

Clearly, interest >= 0 is the opposite of interest < 0. What may not be as obvious is that the OR (||) operator is replaced with an AND (&&) operator. It says, "Exit the loop if the interest is greater than zero AND less than the maximum amount (in other words, if it is correct)."

By the way, how could you revise CalculateInterestTableMoreForgiving to let the user run calculation after calculation and enter new principal and interest figures every time until she wants to quit? Hint: Use another while(true) loop with its own exit condition.

Note that the principal variable must be declared outside the loop because of scope rules, which I explain in the next section.

Warning

It may sound obvious, but the expression true evaluates to true. Therefore, while(true) is your archetypical infinite loop. It's the embedded break command that exits the loop. Therefore, if you use the while(true) loop, make sure that your break condition can occur.

The output from a sample execution of this program (showing one of the author's ignorance) appears this way:

Enter principal: −1000
Principal cannot be negative
Try again

Enter principal: 1000
Enter interest: −10
Interest cannot be negative or greater than 50
Try again

Enter interest: 10
Enter number of years: 5

Principal     = 1000
Interest      = 10%
Duration      = 5 years

1-1100.0
2-1210.00
3-1331.00
4-1464.10
5-1610.51
Press Enter to terminate . . .

The program refuses to accept a negative principal or interest amount and patiently explains the mistake on each loop.

Warning

Explain exactly what the user did wrong before looping back for further input or else that person will become extremely confused. Showing an example may also help, especially for formatting problems. A little diplomacy can't hurt, either, as Grandma may have pointed out.

Focusing on scope rules

A variable declared within the body of a loop is only defined within that loop. Consider this code snippet:

int days = 1;
while(days < duration)
{
    int average = value / days;
    //  . . . some series of commands . . .
    days = days + 1;
}

Note

The variable average isn't defined outside the while loop. Various reasons for this exist, but consider this one: The first time the loop executes, the program encounters the declaration int average and the variable is defined. On the second loop, the program again encounters the declaration for average, and were it not for scope rules, it would be an error because the variable is already defined.

I could provide other, more convincing reasons than this one, but this one should do for now.

Suffice it to say that the variable average goes away, as far as C# is concerned, as soon as the program reaches the closed brace — and is redefined each time through the loop.

Tip

Experienced programmers say that the scope of the variable average is limited to the while loop.

Looping a Specified Number of Times with for

The while loop is the simplest and second most commonly used looping structure in C#. Compared to the for loop, however, the while loop is used about as often as metric tools in an American machine shop.

The for loop has this structure:

for(initExpression; condition; incrementExpression)
{
    // . . . body of code . . .
}

When the for loop is encountered, the program first executes the initExpression expression and then executes the condition. If the condition expression is true, the program executes the body of the loop, which is surrounded by the braces immediately following the for command. When the program reaches the closed brace, control passes to incrementExpression and then back to condition, where the next pass through the loop begins.

In fact, the definition of a for loop can be converted into this while loop:

initExpression;
while(condition)
{
//  . . . body of code . . .
    incrementExpression;
}

An example

You can better see how the for loop works in this example:

// Here is one C# expression or another.
a = 1;
// Now loop for awhile.
for(int year = 1; year < duration; year = year + 1)
{
    //  . . . body of code . . .
}
// The program continues here.
a = 2;

Assume that the program has just executed the a = 1; expression. Next, the program declares the variable year and initializes it to 1. Then the program compares year to duration. If year is less than duration, the body of code within the braces is executed. After encountering the closed brace, the program jumps back to the top and executes the year = year + 1 clause before sliding back over to the year < duration comparison.

Warning

The year variable is undefined outside the scope of the for loop. The loop's scope includes the loop's heading as well as its body.

Why do you need another loop?

Why do you need the for loop if C# has an equivalent while loop? The short answer is that you don't — the for loop doesn't bring anything to the table that the while loop can't already do.

However, the sections of the for loop exist for convenience — and to clearly establish the three parts that every loop should have: the setup, exit criteria, and increment. Not only is this arrangement easier to read, but it's also easier to get right. (Remember that the most common mistakes in a while loop are forgetting to increment the counting variable and failing to provide the proper exit criteria.)

Beyond any sort of song-and-dance justification that I may make, the most important reason to understand the for loop is that it's the loop everyone uses — and it (along with its cousin, foreach) is the one you see 90 percent of the time when you're reading other people's code.

Note

The for loop is designed so that the first expression initializes a counting variable and the last section increments it; however, the C# language doesn't enforce any such rule. You can do anything you want in these two sections — however, you would be ill advised to do anything but initialize and increment the counting variable.

The increment operator is particularly popular when writing for loops. (I describe the increment operator along with other operators in Chapter 4 of this minibook.) The previous for loop is usually written this way:

for(int year = 1; year < nDuration; year++)
{
    //  . . . body of code . . .
}

Tip

You almost always see the postincrement operator used in a for loop instead of the preincrement operator, although the effect in this case is the same. There's no reason other than habit and the fact that it looks cooler. (The next time you want to break the ice, just haul out your C# listing full of postincrement operators to show how cool you are. It almost never works, but it's worth a try.)

The for loop has one variation that I can't claim to understand. If the logical condition expression is missing, it's assumed to be true. Thus for(;;) is an infinite loop.

Tip

You see for(;;) used as an infinite loop more often than while(true). I have no idea why that's the case.

Nesting Loops

An inner loop can appear within an outer loop, this way:

for( . . .some condition . . .)
{
  for( . . .some other condition . . .)
  {
    //  . . . do whatever . . .
  }
}

The inner loop is executed to completion after each pass through the outer loop. The loop variable (such as year) used in the inner for loop isn't defined outside the inner loop's scope.

Note

A loop contained within another loop is a nested loop. Nested loops cannot "cross." For example, the following code won't work:

do                 // Start a do..while loop.
{
  for( . . .)      // Start some for loop.
  {
  } while( . . .)  // End do..while loop.
}                  // End for loop.

I'm not even sure what this chunk would mean, but it doesn't matter because the compiler tells you that it's not legal anyway.

Note

A break statement within a nested loop breaks out of the inner loop only. In the following example, the break statement exits loop B and goes back into loop A:

// for loop A
for( . . .some condition . . .)
{
  // for loop B
  for( . . .some other condition . . .)
  {
    //  . . . do whatever . . .
    if (something is true)
    {
      break;        // Breaks out of loop B and not loop A
    }
  }
}

C# doesn't have a break command that exits both loops simultaneously.

Note

That's not as big a limitation as it sounds. In practice, the often-complex logic contained within such nested loops is better encapsulated in a method. Executing a return from within any of the loops exits the method — thereby bailing out of all loops, no matter how nested they are. We describe methods and the return statement in Chapter 7 of this minibook.

Note

The DisplayXWithNestedLoops example (not shown here) illustrates nesting one loop inside another to do some primitive drawing on the screen.

Don't goto Pieces

You can transfer control in an unstructured fashion by using the goto statement. It's followed by one of these items:

  • A label

  • A case in a switch statement

  • The keyword default (the default clause of a switch statement)

The idea behind the latter two items is to "jump" from one case to another.

This snippet demonstrates how the goto statement is used:

// If the condition is true . . .
if (a > b)
{
  //  . . . control passes unconditionally from the goto to the label.
  goto exitLabel;
}
//  . . . whatever other code goes here . . .
exitLabel:
  // Control continues here.

The goto statement is unpopular for the very reason that makes it such a powerful control: It is almost completely unstructured. Tracking the flow of control through anything larger than a trivial piece of code can be difficult if you use goto. (Can you say "spaghetti code"?)

Tip

Religious wars have sprung up over the use of the goto statement. In fact, the C# language itself has been criticized for its inclusion of the control. Actually, goto is neither all that horrible nor necessary. Because you can almost always avoid using goto, I recommend staying away from it, other than occasionally using it to link two cases within a switch statement, like this:

switch(n)  // This example becomes gnarly in the logic department . . .
{
  case 0:
    // Do something for the 0 case, then  . . .
    goto 3;  // jump to another case; no break statement needed.
  case 1:
    // Do something for the 1 case.
    break;
  case 3:    // Case 0 jumps to here after doing its thing.
    // Add some case 3 stuff to what case 0 did, thus "chaining" the cases.
    break;
  default:
    // Default case.
    break;
}

Don't get addicted to goto, though. Really.

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

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