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.)
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:
// 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();
}
}
}
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
.
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,” section, 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 . . .
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.
bool informal = true;
string name = informal : “Chuck” ? “Charles”; // Returns “Chuck”
This chunk evaluates the expression before the colon. If the expression is true, return the value after the colon but before the question mark. If the expression is false, return the value 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.
The following program, CalculateInterestWithEmbeddedTest
, uses embedded if
statements to avoid stupid questions if a problem is detected in the input:
// 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.
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;
}
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;
}
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 be 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.
// 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.
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.
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.
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.
int nYear = 1;
while (nYear < 10)
{
// . . . whatever . . .
}
(I 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.)
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.
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;
}
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.
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.
// 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.
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.
The output from a sample execution of this program (showing 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.
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;
}
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.
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.
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.
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 . . .
}
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.
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.
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.
// 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.
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”?)
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.