The branching tests in switches are more limited than those available to if-statements, but they are typically compact and succinct. Switches are somewhat specialized and not the most frequently used flow of control statement, but they play an important role when needed. A basic switch statement is relatively simple and easy to understand. However, several variations can make switches complicated, tricky, and difficult to understand. Their complexity aside, all switches consist of two required elements.
The code fragment and flowchart below illustrate a simple switch statement. Notice that each case and the default all end with break;
. Strictly speaking, the break;
isn't required for the last section - execution just "falls out" of the switch - but in my opinion, based on my experience maintaining code as a professional software engineer, it is good programming style to end a switch with a break
.
switch (expression) { case c1: statement0; statement1; statement2; break; case c2: statement4; statement5; break; case c3: statement6; statement7; statement8; statement9; break; case c4: statement10; statement11; statement12; break; default: statement13; statement14; statement15; break; } |
Why do I recommend ending a switch with a break? Imagine that a switch does not end with a break, and while maintaining the code, a programmer adds a new case or default at the end of the switch. If the programmer does not notice that the switch doesn't end with a break and fails to add one, the programmer inadvertently adds a potentially disastrous bug to the code. If this omission is so potentially dangerous, why is it not caught by the compiler? The answer lies at the heart of the variations that can make switch statements tricky.
A switch first evaluates the test expression and then compares the expression value with each case, beginning with the topmost one and working downward. When a case matches the test expression value, the program executes the associated statements. The program continues to execute code until it encounters a break statement or reaches the end of the switch statement. How does a switch behave if a case does not end with a break?
. . . case c2: statement0; statement1; statement2; // fall through case c3: statement3; statement4; break; . . . |
The following example is a special case of the advanced "case fall through" example above. This form is used often but is also very easy to understand. The program treats two or more adjacent cases as a logical-OR.
. . . case c2: case c3: statement0; statement1; break; . . . |
A case does not create a new scope. The only time that this situation is a concern is when we need or want to define variables inside a case.
switch (expr) { case c1: int counter; . . . break; case c2: int counter; . . . break; . . . } |
switch (expr) { case c1: { int counter; . . . break; } case c2: { int counter; . . . break; } . . . } |
int counter; switch (expr) { case c1: . . . break; case c2: . . . break; . . . } |
(a) Does Not Compile | (b) Compiles | (c) Compiles |
---|
Defining different variables (with different names) in each case can create situations with subtle, puzzling behavior.
switch (expr) { case c1: int min; . . . break; case c2: int max; . . . break; . . . } |
switch (expr) { case c1: int min = 100; . . . break; case c2: int max = 200; . . . break; . . . } |
(a) Compiles | (b) Does Not Compile |
---|
Variable initializations are crucial operations. Consequently, the compiler ensures that they occur as intended. As a result, Figure 6(b) produces a fatal error that aborts the compilation of the program because it is possible to skip the initialization. On the other hand, Figure 7(a) accomplishes the same result but with a separate initialization operation. So, what's the difference? Figure 6(b) is a compile-time initialization, and Figure 7(a) is a run-time initialization. Compile-time initialization is more efficient but less flexible. Nevertheless, creating separate scopes for each variable, as illustrated in Figures 5(b) and 7(b), provides the safest and the most robust solution.
switch (expr) { case c1: int min; min = 100; . . . break; case c2: int max; max = 200; . . . break; . . . } |
switch (expr) { case c1: { int min = 100; . . . break; } case c2: { int max = 200; . . . break; } . . . } |
(a) Correction: Separate Initialization | (b) Correction: Multiple Scopes |
---|
Research comparing excellent adult learners with less capable ones also confirmed that the most successful learners elaborate what they read and construct explanations for themselves... Students were classified on the basis of their performance on a test given after they studied a chapter in a physics text, and of their activities during learning as they studied the example problems. The better students treated the examples quite differently, constructing explanations of solutions in terms of problem goals and physics principles discussed in the texts, rather than simply attending to the sequence of steps in solutions, as the poorer students tended to do. (Greeno, Collins, & Resnick, 1996, p. 19).
To elaborate means "to add details in writing, speaking, etc.; give additional or fuller treatment." Previous examples provide detailed descriptions of what each statement in the example does. These details are my elaboration of the example. Elaborations help us understand concepts sufficiently well that we can use them to solve problems that we haven't seen before. Elaboration is also a skill that you can and must develop through practice. Study the two following examples until you can describe the behavior of each, line by line, to someone else. Subsequent sections present completed examples demonstrating switch-statements: temp.cpp (switch), temp3.cpp, and wc.cpp.
char c; . . . switch (c) { case 'y' : cout << "Yes" << endl; break; case 'n' : cout << "No" << endl; break; default : cout << "Unknown response" << endl ; break; } |
int i = 1; switch (i) { default : cout << "sixth\n"; break; case 0 : cout << "first\n"; break; case 1 : cout << "second\n"; case 10 : cout << "third\n"; case 100 : cout << "fourth\n"; break; case 1000 : cout << "fifth\n"; break; } |
second third fourth |