What you will learn in this chapter:
wrox.com code downloads for this chapter
You can find the wrox.com code downloads for this chapter at www.wrox.com/remtitle.cgi?isbn=9781118336922 on the Download Code tab. The code in the Chapter07 folder is individually named according to the names throughout the chapter.
Computer programs are good doing repetitive tasks…much better than humans because computers don't get bored. In this chapter, you learn about program loops. These are simply a means by which a program can repeat the execution of a given set of program statements.
Most nontrivial programs use some form of program loop. Loops are so useful that C# makes several different types available. Now see what loops can do for you.
If you're sitting in a public building right now, look at the ceiling; chances are you can see sprinklers embedded there. In most modern office buildings, each room has one or more sprinklers in it. Modern sprinklers are monitored by software that tests each one to see if it senses a fire. If a building has 500 sprinklers, the program samples the current state of sprinkler number 1 and, assuming everything's okay, goes on to test sprinkler number 2. This process continues until sprinkler 500 is tested. Assuming all 500 passed the test and no fire is sensed, the program goes back to the start and tests sprinkler 1 again. The program then moves on to sprinkler 2 and the entire process repeats itself. It's fairly quick, probably taking less than a few seconds to test all 500 sprinklers.
The process of repeated testing is performed by a program loop within the fire system software. If one of the sprinkler system tests failed, the software would likely branch out of the loop and process code that would sound the alarm, make sure there is water pressure to feed the sprinkler system, turn on the sprinkler system, and place a call to the fire department. Loops are everywhere around us, in everything from fire alarms to elevators to circuits in your car's engine and safety equipment. Indeed, people who find their lives boring are perhaps caught in endless loops wherein they do the same things over and over again!
Not all loops are created equal. As you see in a moment, there are good loops and bad loops. To understand the difference, consider the conditions of a well-behaved loop.
In general, well-behaved loops:
The first condition means that your code should always start executing a program loop in a known state. Usually this means that you assign the variable that controls the loop some initial value. Failure to initialize a loop variable almost always means that the loop executes an unexpected number of times—not good!
The second condition means that you must have some form of test that decides whether another pass through the loop is needed. Usually this means testing the variable that you initialized as part of the first condition against some termination criteria. This condition often means that a relational operator is part of the condition.
The third condition means that some variable must change its value during the execution of the loop. If the state of the loop does not change during execution, there is no condition that can terminate the loop. This produces an infinite loop, which is one that continues to execute forever. Most of the time, an infinite loop is the result of an error in the logic of the program.
To illustrate the three conditions of a well-behaved loop, consider the C# for loop.
The syntax for a for loop is as follows:
for (expression1; expression2; expression3) { // for loop statement block }
expression1 is normally used to initialize the starting value of a variable that controls the loop. expression2 is usually a relational test that determines if another iteration of the loop is needed. Finally, expression3 is often a statement that increments or decrements the variable controlling the loop. A concrete example should help identify these expressions. Suppose you want to generate a table of squares for values from 0 through 100. The for loop controlling the processing might look like this:
for (i = 0; i <= 100; i++) { // statements to calculate the values }
expression1 is the statement that immediately follows the opening parenthesis of the for loop:
i = 0;
This expression sets, or initializes, the initial state of the variable that controls the loop. In this case you start the table-of-squares calculations with the value 0. Because the variable i appears in all three expressions, it is the variable that controls the for loop. If you want to initialize the state of the loop with the value 1, you would change expression1 to be
i = 1;
The second expression,
i <= 100;
checks the current value of variable i against the wanted ending value of 100. If this expression resolves to logic True, another pass through the loop code is made. That is, the for loop's statement block is executed again.
The third expression,
i++;
is responsible for changing the state of the loop during each pass. In this case the code simply increments the value of variable i. Note that you can place any expression statement here, including a decrement if you want.
expression3 uses the expression i++. This is an example of a post-increment operator. The interpretation of the post-increment operator (i++) is:
i = i + 1;
That is, the post-increment operator takes the current value of variable i and adds 1 to it.
You can also use a post-decrement operator, which has the syntax form of i--. The interpretation of the post-decrement operator is:
i = i – 1;
The post-decrement operator, therefore, takes the current value of variable i and subtracts 1 from it.
When a increment or decrement operator “stands by itself” as it does in the for loop, only the single variable is affected. Now consider the following statement:
k = i++;
If the value of i just before this statement is 5, what's the value of k? Is k equal to 5 or 6? In other words, at what point does the increment take place? In this example, because you use the post-increment operator, the value of k is 5, but the value of i when the statement finishes executing is 6. This is why it is called a post-increment operator. You use the value first (using the assignment operator in this example) and then you perform the increment operation.
As you probably suspected, there is also a pre-increment operator. A pre-increment operation places the two plus signs before the variable name. With pre-increment operations, the increment is performed before the value is used. Therefore, if i is 5 before the following statement:
k = ++i;
the value of k is 6, as is the value of i.
C# also enables you to use pre- and post-decrement operations. If the value of i is 5 before the following statement:
k = i--;
the value of k is 5, but the value of i is 4. Again, the reasoning is the same: The decrement operation is done after the assignment is performed. If a pre-decrement operator were used,
k = −-i;
the value of both k and i are 4 because the decrement is performed before the assignment takes place.
You should convince yourself that a standalone increment or decrement expression behaves exactly the same from the program's point of view. It's only when the increment or decrement operation is used as part of some other expression (like an assignment operation) that it matters whether the pre- or post-operator is used. In the for loop, expression3 is not used with any other subexpression, so you can use either a pre- or post-increment operator.
There is a well-defined sequence for the order of statement execution in a for loop. This sequencing is illustrated in Figure 7.1.
In the figure, step 1 is usually the initialization of the variable that controls the loop. After step 1 is accomplished, program control is transferred to step 2. Note that expression1 in step 1 is never revisited. The sole purpose of expression1 is to initialize the loop; after that is done, that expression is not executed again.
Step 2 is the relational expression. Often this means the code compares two variables to decide what to do next. As shown in Figure 7.1, if the outcome of the relational test in expression2 is logic true, program control is sent to the statements in the for loop statement block. If expression2 evaluates to logic false, program control is sent to the first statement following the closing curly brace of the for loop statement block, and the loop body is skipped.
Assuming step 2 is logic True, the code between the opening and closing curly braces is executed. When the last statement of the for loop statement block is executed, step 3 sends program control to expression3. Normally, expression3 is responsible to change the state of the for loop. Perhaps the most common statement for expression3 is an increment operation of the variable that was initialized in step 1.
After expression3 is executed, program control is immediately sent back to expression2. Again, the code in expression2 is tested to see if another pass through the loop statement block is needed. If the test is logic True, another pass through the for loop statement block is made. This repeated sequence continues until expression2 evaluates to logic False, at which time the for loop terminates.
Just like the if statement block you studied in the last chapter, when the for loop controls only a single program statement, the curly braces can be left out and the C# compiler won't complain. However, just like you should always use braces with the if statement, you should always use curly braces with for loops for the same reasons: The braces make loops easier to read, and quite often you need to add more statements later.
Now that you understand what each of the three expressions in a for loop does, compare their tasks with the three conditions required of a well-behaved loop. See any relationship? The three expressions needed to use a for loop are the three required conditions of a well-behaved loop!
In the following Try It Out you write a program that uses a for loop to generate a table of squares for a sequence of numbers. Note how all of the conditions for a well-behaved loop are stated on a single line of the program.
Listing 7-1: Table of squares program (frmMain.cs)
using System; using System.Windows.Forms; public class frmMain : Form { private Label label2; private TextBox txtStart; private TextBox txtEnd; private Button btnCalculate; private Button btnClear; private Button btnClose; private ListBox lstOutput; private Label label3; private Label label4; private Label label1; #region Windows code // This code is hidden… public frmMain() { InitializeComponent(); } public static void Main() { frmMain main = new frmMain(); Application.Run(main); } private void btnCalculate_Click(object sender, EventArgs e) { bool flag; int i; int start; int end; string buff; //================ Gather inputs ====================== // Convert start from text to int flag = int.TryParse(txtStart.Text, out start); if (flag == false) { MessageBox.Show("Numeric data only", "Input Error"); txtStart.Focus(); return; } // Convert end from text to int flag = int.TryParse(txtEnd.Text, out end); if (flag == false) { MessageBox.Show("Numeric data only", "Input Error"); txtEnd.Focus(); return; } if (start >= end) // Reasonable values? { MessageBox.Show("Start greater than end.", "Input Error"); txtStart.Focus(); return; } //================= Process and Display ============== for (i = start; i <= end; i++) { buff = string.Format("{0, 5}{1, 20}", i, i * i); lstOutput.Items.Add(buff); } } private void btnClose_Click(object sender, EventArgs e) { Close(); } private void btnClear_Click(object sender, EventArgs e) { txtStart.Clear(); txtEnd.Clear(); lstOutput.Items.Clear(); } }
for (i = start; i <= end; i++) { buff = string.Format("{0, 5}{1, 20}", i, i * i); lstOutput.Items.Add(buff); }
i <= end;
0 <= 100;
buff = string.Format("{0, 5}{1, 20}", i, i * i);
FORMAT OPTION STRING | ARGUMENT | OUTPUT |
{0} | "Katie" | |Katie| |
{0, 15} | "John" | | John| |
{0, -15} | "Tammy" | |Tammy | |
{0,15:C} | 5.10 | | $5.10| |
{0,-15:C} | 5.10 | |$5.10 | |
{0, mm} | 9:15 | |15| |
{0, 5:hh} | 12:15 | | 12| |
{0, 15:hh mm} | 12:15 | | 12:15| |
{0, dddd MMMM} | 1/1/2008 | |Tuesday January| |
lstOutput.Items.Add(buff);
i <= end;
The most common use of for loops is to count something or perform a sequence of instructions a specific number of times. That is, the terminating condition as stated in expression2 of the for loop is usually known when the loop is entered. In the preceding program, for example, you knew that the variable end determines how many passes should be made through the for loop statement block.
You will discover hundreds of situations in which the for loop offers the perfect way to solve a given problem. Of the various looping structures C# makes available to you, it is probably the one used most often. A bonus feature of the for loop is that all three requirements for a well-behaved loop are specified in the three expressions used in a for statement. Having all three requirements for a well-behaved loop in one place makes it difficult to forget any of them.
Sometimes you need to solve problems that require a loop within a loop. When one loop appears inside another loop, it is called a nested loop. Now modify your table-of-squares program by putting a new twist on it. Many years ago the author discovered, quite by accident, that you can square a number in a different way from simple multiplication. The algorithm is:
Reread the algorithm again and think about it. For example, suppose you want to find the square of the number 3. You already know the answer is 9, but use the new algorithm to calculate the answer:
N2 = 1 + 3 + 5 N2 = 9
Note how you simply added up three positive odd integers, starting with 1, to arrive at the square of 3, which is 9. Suppose you want to find the square of 5:
N2 = 1 + 3 + 5 + 7 + 9 N2 = 25
Although this is an RDC way to square a number, it is an interesting test to see if you can figure out how to implement the algorithm in code.
Next let's take the table of squares program discussed earlier in this chapter and improve it slightly with a few modifications. The result should look a little better when viewed on the display.
for (i = start; i <= end; i++) { nextOddInteger = 1; // Set first odd integer square = 0; // Always start with square = 0 for (j = 0; j < i; j++) // Nested j loop { square += nextOddInteger; // Sum the odd integer nextOddInteger += 2; // Set the next odd integer } buff = string.Format("{0, 5}{1, 20}", i, square); lstOutput.Items.Add(buff); }
int j; int square; int nextOddInteger;
for (i = start; i <= end; i++) { nextOddInteger = 1; // Set first odd integer square = 0; // Always start with square = 0 for (j = 0; j < i; j++) // Nested j loop { square += nextOddInteger; // Sum the odd integer nextOddInteger += 2; // Set the next odd integer } buff = string.Format("{0, 5}{1, 20}", i, square); lstOutput.Items.Add(buff); }
for (j = 0; j < 3; j++) // Nested j loop { square += nextOddInteger; // Sum the odd integer nextOddInteger += 2; // Set the next odd integer }
for (j = 0; j < 3; j++) // Nested j loop { square = 1; // Sum the odd integer nextOddInteger = 3; // Set the next odd integer }
square = 1 nextOddInteger = 1 + 2 = 3 On the second pass through the loop, you find: for (j = 0; j < 3; j++) // Nested j loop { square = 4; // Sum the odd integer nextOddInteger = 5; // Set the next odd integer }
square = 1 + 3 = 4 nextOddInteger = 3 + 2 = 5
square = 1 + 3 + 5 = 9 nextOddInteger = 5 + 2 = 7
buff = string.Format("{0, 5}{1, 20}", i, square); lstOutput.Items.Add(buff);
The best way for you to see how loops work is to single-step through the program. For example, place the cursor on the following for statement:
for (i = start; i <= end; i++)
Press the F9 key to set a breakpoint at that statement. (The text background changes to red for that statement line.) Now press the F5 key to run the program. Eventually, you'll get to the breakpoint statement. (You may have to click in the Source window to see the breakpoint, which now has the statement displayed with a yellow text background.)
Now start pressing the F10 key to single-step through the program code. Make sure you have the Locals debugger window visible (Debug Windows → Locals or Ctrl+D, L) so that you can see the values of the variables change as you go. Using the debugger is a great way to understand how the flow of the program changes based upon the values of the variables.
Another type of program loop is the while loop. The general syntax for a while loop is as follows:
while (expression2) { // while loop statement block }
For a while loop to be well behaved, it must follow the same three rules you applied to a for loop. However, unlike the for loop, the while loop does not make all three conditions part of the syntax of the loop structure. Only the second condition (expression2) is part of a while loop's syntax.
The first expression (expression1) of a well-behaved while loop that initializes the loop control variable is set before you enter the loop structure. The third expression (expression3) is set within the while loop statement block.
You can modify the earlier table of squares program to use a while loop, too. That's the topic of the next Try It Out.
Listing 7-2: Table of Squares, while loop variation. (frmMain.cs)
private void btnCalculate_Click(object sender, EventArgs e) { bool flag; int i; int start; int end; string buff; //============ Gather inputs ====================== // Convert start from text to int flag = int.TryParse(txtStart.Text, out start); if (flag == false) { MessageBox.Show("Numeric data only", "Input Error"); txtStart.Focus(); return; } // Convert end from text to int flag = int.TryParse(txtEnd.Text, out end); if (flag == false) { MessageBox.Show("Numeric data only", "Input Error"); txtEnd.Focus(); return; } if (start >= end) // Reasonable values? { MessageBox.Show("Start less than end.", "Input Error"); txtStart.Focus(); return; } //============= Process and Display ============== i = start; // Initialize loop counter: condition 1 while (i <= end) // Another iteration: condition 2 { buff = string.Format("{0, 5}{1, 20}", i, i * i); lstOutput.Items.Add(buff); i++; // Change state of loop: condition 3 } }
Any code that uses a for loop structure can be rewritten as a while loop, so why does C# bother with two kinds? Simply stated, C# gives you a choice of loop types because the nuances of different programming tasks may suit one loop type better than another. For example, there will be many times where you look for a specific piece of data in a list, such as a specific customer name in a list of customers, and you don't know exactly where it appears. Most programmers would code such a task as a while loop because they can't be sure of exactly how many iterations it's going to take to find the name. On the other hand, if you need to read 14 sensors on a machine and react to those sensors, you would likely use a for loop because the number of required passes through the loop is known. (Would it be a good idea to code the second expression of the loop using a symbolic constant? Hint: Yes. That would make it easier to read and change the code if the need arises.)
The ability to choose different loop structures simply makes certain programming tasks easier. Although you might contort all your code to fit into a single loop structure, having choices means you don't need to. Multiple loop structures means you have multiple tools at your disposal to attack different programming problems. After all, if the only tool you have is a hammer, it's not surprising that all your problems start to look like a nail. Multiple tools make for more elegant solutions to different programming problems.
C# provides a third loop variation that you can add to your loop toolkit called a do-while loop. The syntax for a do-while loop is as follows:
do { // do-while statement block } while (expression2);
The do-while loop variant is similar to the while loop with one major exception: A do-while loop always makes at least one pass through the loop statement block. If you look at the other two loop variations, the test of expression2 takes place before the loop statement block is ever executed. With the for and while loop statements, it is quite possible for expression2 to evaluate to logic False at the start of the loop. If expression2 is false for either the for or while loop, no pass is made through the statements in the loop body.
This is not true for a do-while loop. A do-while loop always makes at least one pass through its statement block. This is because the expression that evaluates whether another pass should be made through the loop is made at the bottom of the loop and is arrived at after the statements in the loop body have been executed at least once. In other words, program control must pass through the do-while statement block at least one time.
While you could use the table of squares program again to use the do-while statement, in the following Try It Out you shake things up a bit and generate a series of random numbers instead. Note that the conditions for a well-behaved loop still exist, the conditions simply are not in one place in the code.
Listing 7-3: Random numbers program. (frmMain.cs)
using System; using System.Windows.Forms; public class frmMain : Form { const int MAXITERATIONS = 200000; // Limit loop passes private Button btnClose; private Label lblAnswer; private Label label1; private TextBox txtMax; private Button btnStart; #region Windows code public frmMain() { //========== Program Initialize Step ============== InitializeComponent(); } public static void Main() { frmMain main = new frmMain(); Application.Run(main); } private void btnStart_Click(object sender, EventArgs e) { bool flag; int counter; // Pass counter int max; // Max value for random number int last; int current; Random randomNumber = new Random(); //========= Program Input Step ================ flag = int.TryParse(txtMax.Text, out max); if (flag == false) { MessageBox.Show("Digit characters only.", "Input Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); txtMax.Focus(); return; } //======== Program Process Step ============== counter = 0; last = (int) randomNumber.Next(max); do { current = randomNumber.Next(max); if (last == current) { break; } last = current; counter++; } while (counter < MAXITERATIONS); //========= Program Output Step ============== if (counter < MAXITERATIONS) { lblAnswer.Text = "It took " + counter.ToString() + " passes to match"; } else { lblAnswer.Text = "No back-to-back match"; } } //============ Program Termination Step ======== private void btnClose_Click(object sender, EventArgs e) { Close(); } }
const int MAXITERATIONS = 200000; // Limit on loop passes
Random randomNumber = new Random();
counter = 0; last = randomNumber.Next(max); do { current = randomNumber.Next(max); if (last == current) { break; } last = current; counter++; } while (counter < MAXITERATIONS);
if (counter < MAXITERATIONS)
while (counter < MAXITERATIONS);
Sometimes situations arise in which you need to do something special within a loop before you continue processing it. For example, you might have a loop polling the fire sprinkler sensors mentioned at the beginning of this chapter. However, instead of breaking out of the loop when a sensor is tripped, perhaps you want to continue reading the rest of the sensors to see how fast the fire is spreading. The code might look like this:
while (true) { id++; state = readSensor(id); if (state == true) { soundAlarm(); callFireDepartment(); continue; } if (id == MAXSENSORS) { id = 0; // 0 so increment operator sets it to 1 } }
In this code fragment, you establish an infinite while loop by design by stating that the test expression (expression2) is always logic True, as shown in the first program statement. The code increments a sensor identification number (id) and then reads the appropriate sensor. If the sensor does not return true, the program makes another pass through the loop. If there is a fire and the sensor returns true, the alarm is sounded, the fire department is called, and then the code continues execution at expression2 of the while loop because of the continue statement. Because expression2 is always logic True, the loop continues to execute the code.
When a while or do-while loop is used, the continue statement always sends control to the expression that determines whether another pass through the loop is necessary (that is, expression2).
The same type of code using a for loop would be the following:
for (id = 0; true; id++) { state = readSensor(id); if (state == true) { soundAlarm(); callFireDepartment(); continue; } if (id == MAXSENSORS) { id = 0; // 0 so increment operator sets it to 1 } }
In this situation, if there is a fire, variable state is true, and the alarm and fire department methods are executed; then continue is executed. (Look at expression2 of the for loop.) The continue statement sends program control to expression3 (id++) of the for statement, which increments the sensor id. Program control is then sent to expression2 to determine if another pass through the loop is needed. Because expression2 is always logic True, the code makes another pass through the loop. You should convince yourself that this is an infinite loop.
Unlike the break statement that transfers program control out of the loop, a continue statement keeps program control in the loop to decide if another pass is warranted. As an experiment, you might write dummy methods for the soundAlarm() and callFireDepartment() methods and then use the debugger to single-step through the code. Again, using the debugger is a great way to gain a more complete understanding of what the code is doing.
For the sake of completeness, you should know that C# supports the goto keyword, which can be used with a label to create a loop structure. However, because goto's are such an ugly coding structure (that is, a coding hack), you aren't shown how to write such a loop. If you actually want to know, you can probably find an example online. (Warning: Rumor is that you can get warts by just looking at goto statements.)
Loop structures are one of the fundamental elements in most programs. You should study the concepts presented in this chapter until you are confident how and when to use each loop type. You should also make sure you do these exercises and perhaps make up a few examples of your own.
You can find the answers to the following exercises in Appendix A.
5! = 5 * 4 * 3 * 2 * 1 5! = 120
TOPIC | KEY POINTS |
Well-behaved loops | The necessary and sufficient conditions for writing a well-behavedprogram loop. |
for, while, do-while loops | The three types of program loops used in C#. |
Which loop to use | How to decide which loops structure provides the best way to iterate over a set of program statements. |
break, continue | How to use these keywords in program loops. |
13.58.25.75