6.6. The switch StatementDeeply nested if else statements can often be correct syntactically and yet not correctly reflect the programmer's logic. For example, mistaken else if matchings are more likely to pass unnoticed. Adding a new condition and associated logic or making other changes to the statements is also hard to get right. A switch statement provides a more convenient way to write deeply nested if/else logic. Suppose that we have been asked to count how often each of the five vowels appears in some segment of text. Our program logic is as follows:
The program was used to analyze this chapter. Here is the output: Number of vowel a: 3499 Number of vowel e: 7132 Number of vowel i: 3577 Number of vowel o: 3530 Number of vowel u: 1185 6.6.1. Using a switchWe can solve our problem most directly using a switch statement: char ch; // initialize counters for each vowel int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0; while (cin >> ch) { // if ch is a vowel, increment the appropriate counter switch (ch) { case 'a': ++aCnt; break; case 'e': ++eCnt; break; case 'i': ++iCnt; break; case 'o': ++oCnt; break; case 'u': ++uCnt; break; } } // print results cout << "Number of vowel a: \t" << aCnt << '\n' << "Number of vowel e: \t" << eCnt << '\n' << "Number of vowel i: \t" << iCnt << '\n' << "Number of vowel o: \t" << oCnt << '\n' << "Number of vowel u: \t" << uCnt << endl; A switch statement executes by evaluating the parenthesized expression that follows the keyword switch. That expression must yield an integral result. The result of the expression is compared with the value associated with each case. The case keyword and its associated value together are known as the case label. Each case label's value must be a constant expression (Section 2.7, p. 62). There is also a special case label, the default label, which we cover on page 203. If the expression matches the value of a case label, execution begins with the first statement following that label. Execution continues normally from that statement through the end of the switch or until a break statement. If no match is found, (and if there is no default label), execution falls through to the first statement following the switch. In this program, the switch is the only statement in the body of a while. Here, falling through the switch returns control to the while condition. We'll look at break statements in Section 6.10 (p. 212). Briefly, a break statement interrupts the current control flow. In this case, the break TRansfers control out of the switch. Execution continues at the first statement following the switch. In this example, as we already know, transferring control to the statement following the switch returns control to the while. 6.6.2. Control Flow within a switchIt is essential to understand that execution flows across case labels.
Sometimes this behavior is indeed correct. We want to execute the code for a particular label as well as the code for following labels. More often, we want to execute only the code particular to a given label. To avoid executing code for subsequent cases, the programmer must explicitly tell the compiler to stop execution by specifying a break statement. Under most conditions, the last statement before the next case label is break. For example, here is an incorrect implementation of our vowel-counting switch statement: // warning: deliberately incorrect! switch (ch) { case 'a': ++aCnt; // oops: should have a break statement case 'e': ++eCnt; // oops: should have a break statement case 'i': ++iCnt; // oops: should have a break statement case 'o': ++oCnt; // oops: should have a break statement case 'u': ++uCnt; // oops: should have a break statement } To understand what happens, we'll trace through this version assuming that value of ch is 'i'. Execution begins following case 'i'thus incrementing iCnt. Execution does not stop there but continues across the case labels incrementing oCnt and uCnt as well. If ch had been 'e', then eCnt, iCnt, oCnt, and uCnt would all be incremented.
break Statements Aren't Always AppropriateThere is one common situation where the programmer might wish to omit a break statement from a case label, allowing the program to fall through multiple case labels. That happens when two or more values are to be handled by the same sequence of actions. Only a single value can be associated with a case label. To indicate a range, therefore, we typically stack case labels following one another. For example, if we wished only to count vowels seen rather than count the individual vowels, we might write the following: int vowelCnt = 0; // ... switch (ch) { // any occurrence of a,e,i,o,u increments vowelCnt case 'a': case 'e': case 'i': case 'o': case 'u': ++vowelCnt; break; } Case labels need not appear on a new line. We could emphasize that the cases represent a range of values by listing them all on a single line: switch (ch) { // alternative legal syntax case 'a': case 'e': case 'i': case 'o': case 'u': ++vowelCnt; break; } Less frequently, we deliberately omit a break because we want to execute code for one case and then continue into the next case, executing that code as well.
6.6.3. The default LabelThe default label provides the equivalent of an else clause. If no case label matches the value of the switch expression and there is a default label, then the statements following the default are executed. For example, we might add a counter to track how many nonvowels we read. We'll increment this counter, which we'll name otherCnt, in the default case: // if ch is a vowel, increment the appropriate counter switch (ch) { case 'a': ++aCnt; break; // remaining vowel cases as before default: ++otherCnt; break; } } In this version, if ch is not a vowel, execution will fall through to the default label, and we'll increment otherCnt.
A label may not stand alone; it must precede a statement. If a switch ends with the default case in which there is no work to be done, then the default label must be followed by a null statement. 6.6.4. switch Expression and Case LabelsThe expression evaluated by a switch can be arbitrarily complex. In particular, the expression can define and intialize a variable: switch(int ival = get_response()) In this case, ival is initialized, and the value of ival is compared with each case label. The variable ival exists throughout the entire switch statement but not outside it. Case labels must be constant integral expressions (Section 2.7, p. 62). For example, the following labels result in compile-time errors: // illegal case label values case 3.14: // noninteger case ival: // nonconstant It is also an error for any two case labels to have the same value. 6.6.5. Variable Definitions inside a switchVariables can be defined following only the last case or default label: case true: // error: declaration precedes a case label string file_name = get_file_name(); break; case false: // ... The reason for this rule is to prevent code that might jump over the definition and initialization of a variable. Recall that a variable can be used from its point of definition until the end of the block in which it is defined. Now, consider what would happen if we could define a variable between two case labels. That variable would continue to exist until the end of the enclosing block. It could be used by code in case labels following the one in which it was defined. If the switch begins executing in one of these subsequent case labels, then the variable might be used even though it had not been defined. If we need to define a variable for a particular case, we can do so by defining the variable inside a block, thereby ensuring that the variable can be used only where it is guaranteed to have been defined and initialized: case true: { // ok: declaration statement within a statement block string file_name = get_file_name(); // ... } break; case false: // ... |