Control structures: selection, loops and guarded commands
Two-way and multiple-way selection (switch fall-through), counter-controlled and logically-controlled loops, break/continue, iterator loops, goto and Dijkstra's guarded commands. The output-tracing questions are here.
Control structures determine which statement the program executes and when. In theory very little is needed for this: Böhm and Jacopini (1966) showed that every algorithm can be written with sequence, selection and a pretest logical loop; no goto is needed. The reason languages offer more than this is writability and readability. On exams, switch/loop outputs and language design differences are asked most.
Golden rules:
- In a switch, without break you fall into the cases below.
- In the C/Java for loop the condition is re-evaluated before every iteration.
- while runs 0 times, do-while at least 1 time.
- continue skips the iteration but the update (
i++) still runs. - In a guarded command, if more than one guard is true, the choice is nondeterministic.
Selection
Two-way selection
Design questions: what can be the type of the condition expression? Is the then/else part a single statement or a block? How is the meaning of nested selection determined?
- C89 and C++ accept an arithmetic expression as the condition (0 is false). Java and C# require a boolean.
- Dangling else:
if (a) if (b) s1; else s2;which if does the else belong to? Java, C, C++, C#: the nearest unmatched if. Perl requires curly braces on every branch; Python determines it with indentation, Ruby and Ada withend/end if.
int y = 7;
if (y > 5)
if (y > 10) System.out.println("big");
else System.out.println("medium");
medium
Multiple-way selection: switch
int t = 0;
for (int k = 0; k < 5; k++) {
switch (k % 3) {
case 0: t += 1; // no break: falls into case 1
case 1: t += 10; break;
default: t += 100;
}
}
System.out.println(t);
142
k k%3 what runs t
0 0 +1, +10 (fell) 11
1 1 +10 21
2 2 default +100 121
3 0 +1, +10 132
4 1 +10 142
| Language | Fall-through | Note |
|---|---|---|
| C, C++, Java, JavaScript | Yes, if you forget break it silently falls through | Flexibility (writability) |
| C# | No: every non-empty segment must end with break, goto case, or return | Reliability |
Ada case, Ruby case | No | All values must be covered (Ada) |
Writing empty cases one after another (case 1: case 2:) is allowed in every language. Multiple-way selection can always be written with nested else if too; in Python elif, and match since Python 3.10.
Counter-controlled loops
The loop variable is controlled by a start, end and step value (loop parameters). Design questions:
- What are the type and scope of the loop variable?
- Can the loop variable or parameters be changed in the body, does it affect the number of iterations?
- Are the parameters evaluated once or on every iteration?
- Is the value of the variable defined after the loop ends?
| Language | Answers |
|---|---|
Ada for I in 1..N loop | I is declared implicitly, scope = the loop, cannot be assigned in the body, the range is evaluated once |
C for (e1; e2; e3) | No explicit loop variable, everything can change in the body, e2 and e3 are evaluated on every iteration, all three can be empty: for (;;) |
| Java, C# | Like C, but e2 must be boolean |
Python for x in range(...) | Actually an iterator loop; the optional else runs if the loop ends without a break |
int n = 10, iters = 0;
for (int i = 0; i < n; i++) { n--; iters++; }
System.out.println(iters + " " + n);
5 5
Recipe for translating a C/Java for into a while (as in operational semantics):
e1;
while (e2) {
body;
e3;
}
Difference: the scope of a variable declared at the start of the for is limited to the loop; in the while version it remains outside. Also, a continue in the body goes to e3 in a for, but skips e3 in this translation.
Logically-controlled loops
int a = 10, w = 0, d = 0;
while (a < 10) { w++; a++; } // pretest
a = 10;
do { d++; a++; } while (a < 10); // posttest
System.out.println(w + " " + d);
0 1
A pretest (while) may never run the body; a posttest (do-while) runs it at least once. Java has no opposite of the posttest loop; Pascal’s repeat ... until stops when the condition becomes true.
User-located exit: break and continue
int count = 0;
outer:
for (int i = 1; i <= 4; i++) {
for (int j = 1; j <= 4; j++) {
if (j == 3) continue outer;
if (i == 3) break outer;
count++;
}
}
System.out.println(count);
4
break: exits the innermost loop (or switch).continue: skips the rest of that iteration, the loop continues.- Labeled
break/continue: Java, C#, Perl (last/next). Not in C. - A
continueinside a switch applies not to the switch but to the nearest loop.
A labeled break is the “controlled” form of goto: it jumps only out of a loop, to a known point.
Iterator-based loops
They traverse the elements of a data structure in the structure’s own order: Java for (int v : arr), C# foreach, Python for x in seq, JavaScript for (const x of arr).
int[] arr = {1, 2, 3};
for (int v : arr) { v *= 2; }
System.out.println(arr[0] + arr[1] + arr[2]);
6
v is a copy of the element on each iteration. To change the array you need an indexed loop.
goto
The most powerful and most dangerous control statement. Unrestricted goto kills readability: the reader cannot know where control came from. Dijkstra wrote “Go To Statement Considered Harmful” in 1968. Java, Python and Ruby have no goto. C# allows goto, its most legitimate use being goto case inside a switch. The principle of structured programming: every structure has a single entry and (if possible) a single exit.
Guarded commands (Dijkstra, 1975)
Purpose: to make verification easier during development. There are two constructs.
Selection:
if <guard1> -> <stmt1>
[] <guard2> -> <stmt2>
...
fi
All guards are evaluated. If more than one is true, one is chosen nondeterministically. If none is true, there is a run-time error.
if x >= y -> max := x
[] y >= x -> max := y
fi
When x = y both guards are true, and whichever is chosen the result is the same. The programmer does not have to decide “which should I choose in case of equality?”
Loop:
do q1 > q2 -> swap(q1, q2)
[] q2 > q3 -> swap(q2, q3)
[] q3 > q4 -> swap(q3, q4)
od
On each iteration one of the true guards is chosen; it ends when none remains true. At the end it is certain that q1 ≤ q2 ≤ q3 ≤ q4. Even if you try all possible choice paths one by one from the start 4, 3, 2, 1, all end at 1, 2, 3, 4. Also the greatest common divisor:
do x > y -> x := x - y
[] y > x -> y := y - x
od x = 18, y = 12 → 6
Common mistakes
| Wrong | Correct |
|---|---|
| The switch ends when the matching case runs | Without break the ones below run too |
| A case without break compiles in C# | It does not compile (non-empty segment) |
| In a for the condition is evaluated once | In C/Java on every iteration; in Ada once |
| continue exits the loop | It skips the iteration; to exit, use break |
| do-while can run 0 times | At least 1 time |
for (int v : arr) v *= 2; changes the array | v is a copy, it does not |
| In a guarded if, if no guard is true it is skipped | Run-time error |
| In a guarded do the result depends on the choice | In the sorting example every path gives the same result |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
What is printed in Java?
int t = 0;
for (int k = 0; k < 5; k++) {
switch (k % 3) {
case 0: t += 1;
case 1: t += 10; break;
default: t += 100;
}
}
System.out.println(t);Show answer
k % 3 is 0, 1, 2, 0, 1 in turn. Case 0 has no break and falls into case 1: +11. Case 1: +10. Default: +100.
Total: 11 + 10 + 100 + 11 + 10 = 142.
What is printed in Java?
int count = 0;
outer:
for (int i = 1; i <= 4; i++) {
for (int j = 1; j <= 4; j++) {
if (j == 3) continue outer;
if (i == 3) break outer;
count++;
}
}
System.out.println(count);Show answer
i = 1: j = 1, 2 are counted, at j = 3 continue outer jumps to the next iteration of the outer loop. i = 2: again 2 counts. i = 3, j = 1: j == 3 is false, i == 3 → break outer exits both loops at once. Total 4.
What is printed in Java?
int sum = 0;
for (int i = 1; i <= 10; i++) {
if (i % 3 == 0) continue;
sum += i;
}
System.out.println(sum);Show answer
continue skips the rest of that iteration but the loop continues (i++ still runs). The sum of 1..10 is 55; the skipped ones are 3 + 6 + 9 = 18. 55 - 18 = 37.
What is printed? How many times does each body run?
int a = 10, w = 0, d = 0;
while (a < 10) { w++; a++; }
a = 10;
do { d++; a++; } while (a < 10);
System.out.println(w + " " + d);Show answer
while is pretest: the condition is false from the start, and the body never runs. do-while is posttest: the body runs once first, then the condition (11 < 10) is false. A posttest loop runs at least once.
How many iterations does the loop perform, and what is n afterwards?
int n = 10, iters = 0;
for (int i = 0; i < n; i++) { n--; iters++; }
System.out.println(iters + " " + n);Show answer
In the for of Java and C, the condition (i < n) is re-evaluated before every iteration and the body can change n. While i is 0,1,2,3,4, n is 10,9,8,7,6 → the condition holds. After the fifth iteration i = 5, n = 5: 5 < 5 is false. In Ada's for I in 1..N loop the range is evaluated once, and changes in the body do not affect the number of iterations.
What is printed in Java?
int[] arr = {1, 2, 3};
for (int v : arr) { v *= 2; }
System.out.println(arr[0] + arr[1] + arr[2]);Show answer
In an iterator-based (enhanced) for, v gets a copy of the element on each iteration. Changing v does not change the array: 1 + 2 + 3 = 6.
What is printed in Java?
String s = "b";
switch (s) {
case "a": System.out.print("A");
case "b": System.out.print("B");
case "c": System.out.print("C"); break;
default: System.out.print("D");
}Show answer
"b" matches, B is printed. Because there is no break, it falls into case "c": C. There is a break there, so default is not reached. In C#, this would not compile because the case "a" and case "b" segments have no break.
What is printed in Java? To which if does the else belong?
int y = 7;
if (y > 5)
if (y > 10) System.out.println("big");
else System.out.println("medium");Show answer
In Java the else attaches to the nearest unmatched if (indentation is irrelevant). The outer condition is true, the inner condition is false → the else runs. If the else belonged to the outer if (since y > 5 is true), nothing would be printed.
Using Python's semantics, what values does range(2, 15, 4) produce? And range(10, 0, -3)?
Show answer
range(start, stop, step): start included, stop excluded. 2, 6, 10, 14; it stops because 18 ≥ 15. With a negative step it continues while greater than the stop: 10, 7, 4, 1; -2 > 0 is false, it stops.
For the guarded selection below, which assignments may execute when x = 3, y = 3? What if x = 1, y = 4?
if x >= y -> max := x
[] y >= x -> max := y
fiShow answer
In a guarded command all guards are evaluated. If more than one is true, one is chosen nondeterministically; here both choices give the same result. For x = 1, y = 4 only the second guard is true. If none were true, it would be a run-time error.
Starting from q1 = 4, q2 = 3, q3 = 2, q4 = 1, what are the final values after the guarded loop? Does the order of choices matter?
do q1 > q2 -> swap(q1, q2)
[] q2 > q3 -> swap(q2, q3)
[] q3 > q4 -> swap(q3, q4)
odShow answer
The do-od loop chooses one of the open guards at random on each iteration and ends when no guard remains true. The termination condition is q1 ≤ q2 ≤ q3 ≤ q4, that is, sorted. The order of choices can change the number of steps but not the result. This was Dijkstra's aim: not to deal with the order when proving the result.
What is printed in Java?
int z = 100, steps = 0;
while (true) {
z /= 3;
steps++;
if (z < 5) break;
}
System.out.println(steps + " " + z);Show answer
User-located loop control: the exit point is in the middle of the body. z: 100 → 33 → 11 → 3 (int division). In the third iteration 3 < 5, break. Loops like this are the way to write a mid-test loop.
Verification: All Java outputs were run with the JDK (java verify/SEN2001/W07.java). For the guarded command questions, node tried all guards that are open at each step, walked all possible nondeterministic execution paths, and listed the final states (verify/SEN2001/07.mjs). Since Python is not installed, the range examples were produced with a JavaScript function applying the same rule.