SEN2001 · Week 7 · Control Structures

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 structureselection statementtwo-way selectordangling elsemultiple-way selectorswitchfall-throughcounter-controlled looploop variablelogically controlled looppretestposttestuser-located loop controlbreakcontinuelabeled breakiteratorgotoguarded commandnondeterminismBöhm-Jacopini

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:

  1. In a switch, without break you fall into the cases below.
  2. In the C/Java for loop the condition is re-evaluated before every iteration.
  3. while runs 0 times, do-while at least 1 time.
  4. continue skips the iteration but the update (i++) still runs.
  5. 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 with end / 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
LanguageFall-throughNote
C, C++, Java, JavaScriptYes, if you forget break it silently falls throughFlexibility (writability)
C#No: every non-empty segment must end with break, goto case, or returnReliability
Ada case, Ruby caseNoAll 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:

  1. What are the type and scope of the loop variable?
  2. Can the loop variable or parameters be changed in the body, does it affect the number of iterations?
  3. Are the parameters evaluated once or on every iteration?
  4. Is the value of the variable defined after the loop ends?
LanguageAnswers
Ada for I in 1..N loopI 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 continue inside 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

WrongCorrect
The switch ends when the matching case runsWithout 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 onceIn C/Java on every iteration; in Ada once
continue exits the loopIt skips the iteration; to exit, use break
do-while can run 0 timesAt least 1 time
for (int v : arr) v *= 2; changes the arrayv is a copy, it does not
In a guarded if, if no guard is true it is skippedRun-time error
In a guarded do the result depends on the choiceIn the sorting example every path gives the same result

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1medium

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
142

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.

Question 2medium

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
4

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.

Question 3easy

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
37

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.

Question 4easy

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
0 1

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.

Question 5medium

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
5 5

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.

Question 6medium

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
6

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.

Question 7easy

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
BC

"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.

Question 8medium

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
medium; the inner if

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.

Question 9easy

Using Python's semantics, what values does range(2, 15, 4) produce? And range(10, 0, -3)?

Show answer
2, 6, 10, 14 and 10, 7, 4, 1

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.

Question 10medium

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
fi
Show answer
x = 3, y = 3: either one (nondeterministic), max = 3; x = 1, y = 4: only max := y

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.

Question 11hard

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)
od
Show answer
1, 2, 3, 4; no, every execution path ends sorted

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.

Question 12easy

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
3 3

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.

Review card

Böhm-Jacopini: sequence + selection + pretest loop is enough, goto is not needed condition: C89 accepts arithmetic, Java/C# require boolean dangling else: Java/C → nearest if; Python indentation; Ruby/Ada end; Perl {} switch: without break it FALLS THROUGH. C# forbids it (break/goto required) empty cases in a row are allowed: case 1: case 2: C for (e1; e2; e3): e1 once, e2 BEFORE EVERY ITERATION, body, e3. the body can change n Ada for I in 1..N: range once, I cannot be assigned in the body, scope the loop while = pretest (0+), do-while = posttest (1+) break: exit the loop. continue: skip the iteration (e3 still runs). labeled: Java/C#/Perl continue inside switch → applies to the nearest loop enhanced for (v : arr): v is a copy, does not change the array Python range(a, b, s): a included, b excluded guarded if: all evaluated, many true → nondeterministic, none true → ERROR guarded do: ends when none remains true