switch
StatementA switch
statement provides a convenient way of selecting among a (possibly large) number of fixed alternatives. As one example, suppose that we want to count how often each of the five vowels appears in some segment of text. Our program logic is as follows:
• Read every character in the input.
• Compare each character to the set of vowels.
• If the character matches one of the vowels, add 1 to that vowel’s count.
• Display the results.
For example, when we run the program on the text of this chapter, the output is
Number of vowel a: 3195
Number of vowel e: 6230
Number of vowel i: 3102
Number of vowel o: 3289
Number of vowel u: 1033
We can solve our problem most directly using a switch
statement:
// initialize counters for each vowel
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
char ch;
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: " << aCnt << '
'
<< "Number of vowel e: " << eCnt << '
'
<< "Number of vowel i: " << iCnt << '
'
<< "Number of vowel o: " << oCnt << '
'
<< "Number of vowel u: " << uCnt << endl;
A switch
statement executes by evaluating the parenthesized expression that follows the keyword switch
. That expression may be an initialized variable declaration (§ 5.2, p. 174). The expression is converted to integral type. The result of the expression is compared with the value associated with each case
.
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.
We’ll look at break
statements in detail in § 5.5.1 (p. 190), but, briefly, a break
interrupts the current control flow. In this case, the break
transfers control out of the switch
. In this program, the switch
is the only statement in the body of a while
. Breaking out of this switch
returns control to the enclosing while
. Because there are no other statements in that while
, execution continues at the condition in the while
.
If no match is found, execution falls through to the first statement following the switch
. As we already know, in this example, exiting the switch
returns control to the condition in the while
.
The case
keyword and its associated value together are known as the case
label. case
labels must be integral constant expressions (§ 2.4.4, p. 65):
char ch = getVal();
int ival = 42;
switch(ch) {
case 3.14: // error: noninteger as case label
case ival: // error: nonconstant as case label
// . . .
It is an error for any two case
labels to have the same value. There is also a special-case label, default
, which we cover on page 181.
switch
It is important to understand that execution flows across case
labels. After a case
label is matched, execution starts at that label and continues across all the remaining case
s or until the program explicitly interrupts it. To avoid executing code for subsequent case
s, we must explicitly tell the compiler to stop execution. Under most conditions, the last statement before the next case
label is break
.
However, there are situations where the default switch
behavior is exactly what is needed. Each case
label can have only a single value, but sometimes we have two or more values that share a common set of actions. In such instances, we omit a break
statement, allowing the program to fall through multiple case
labels.
For example, we might want to count only the total number of vowels:
unsigned vowelCnt = 0;
// ...
switch (ch)
{
// any occurrence of a, e, i, o, or u increments vowelCnt
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
++vowelCnt;
break;
}
Here we stacked several case
labels together with no intervening break
. The same code will be executed whenever ch
is a vowel.
Because C++ programs are free-form, case
labels need not appear on a new line. We can emphasize that the case
s 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;
}
Omitting a break
at the end of a case
happens rarely. If you do omit a break
, include a comment explaining the logic.
break
Is a Common Source of BugsIt is a common misconception to think that only the statements associated with the matched case
label are executed. 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;
}
To understand what happens, assume that the value of ch
is 'e'
. Execution jumps to the code following the case 'e'
label, which increments eCnt
. Execution continues across the case
labels, incrementing iCnt
, oCnt
, and uCnt
as well.
Although it is not necessary to include a break
after the last label of a switch
, the safest course is to provide one. That way, if an additional case
is added later, the break
is already in place.
default
LabelThe statements following the default
label are executed when no case
label matches the value of the switch
expression. 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': case 'e': case 'i': case 'o': case 'u':
++vowelCnt;
break;
default:
++otherCnt;
break;
}
}
In this version, if ch
is not a vowel, execution will start at the default
label and we’ll increment otherCnt
.
It can be useful to define a default
label even if there is no work for the default
case. Defining an empty default
section indicates to subsequent readers that the case was considered.
A label may not stand alone; it must precede a statement or another case
label. If a switch
ends with a default
case that has no work to do, then the default
label must be followed by a null statement or an empty block.
switch
As we’ve seen, execution in a switch
can jump across case
labels. When execution jumps to a particular case
, any code that occurred inside the switch
before that label is ignored. The fact that code is bypassed raises an interesting question: What happens if the code that is skipped includes a variable definition?
The answer is that it is illegal to jump from a place where a variable with an initializer is out of scope to a place where that variable is in scope:
case true:
// this switch statement is illegal because these initializations might be bypassed
string file_name; // error: control bypasses an implicitly initialized variable
int ival = 0; // error: control bypasses an explicitly initialized variable
int jval; // ok: because jval is not initialized
break;
case false:
// ok: jval is in scope but is uninitialized
jval = next_num(); // ok: assign a value to jval
if (file_name.empty()) // file_name is in scope but wasn't initialized
// ...
If this code were legal, then any time control jumped to the false
case, it would bypass the initialization of file_name
and ival
. Those variables would be in scope. Code following false
could use those variables. However, these variables would not have been initialized. As a result, the language does not allow us to jump over an initialization if the initialized variable is in scope at the point to which control transfers.
If we need to define and initialize a variable for a particular case
, we can do so by defining the variable inside a block, thereby ensuring that the variable is out of scope at the point of any subsequent label.
case true:
{
// ok: declaration statement within a statement block
string file_name = get_file_name();
// ...
}
break;
case false:
if (file_name.empty()) // error: file_name is not in scope
Exercise 5.9: Write a program using a series of if
statements to count the number of vowels in text read from cin
.
Exercise 5.10: There is one problem with our vowel-counting program as we’ve implemented it: It doesn’t count capital letters as vowels. Write a program that counts both lower- and uppercase letters as the appropriate vowel—that is, your program should count both 'a'
and 'A'
as part of aCnt
, and so forth.
Exercise 5.11: Modify our vowel-counting program so that it also counts the number of blank spaces, tabs, and newlines read.
Exercise 5.12: Modify our vowel-counting program so that it counts the number of occurrences of the following two-character sequences: ff
, fl
, and fi
.
Exercise 5.13: Each of the programs in the highlighted text on page 184 contains a common programming error. Identify and correct each error.