6.6. The switch Statement6.6. switch 语句Deeply 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. 深层嵌套的 if else 语句往往在语法上是正确的,但逻辑上去却没有正确地反映程序员的意图。例如,错误的 else if 匹配很容易被忽略。添加新的条件和逻辑关系,或者对语句做其他修改,都很难保证正确。switch 语句提供了一种更方便的方法来实现深层嵌套的 if/else 逻辑。 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 switch6.6.1. 使用 switchWe can solve our problem most directly using a switch statement: 直接使用 switch 语句解决上述问题: 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. 通过对圆括号内表达式的值与其后列出的关键字做比较,实现 switch 语句的功能。表达式必须产生一个整数结果,其值与每个 case 的值比较。关键字 case 和它所关联的值称为 case 标号。每个 case 标号的值都必须是一个常量表达式(第 2.7 节)。除此之外,还有一个特殊的 case 标号——default 标号,我们将在第 6.6.3 节介绍它。 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. 如果表达式与其中一个 case 标号的值匹配,则程序将从该标号后面的第一个语句开始依次执行各个语句,直到 switch 结束或遇到 break 语句为止。如果没有发现匹配的 case 标号(并且也没有 default 标号),则程序从 switch 语句后面的第一条继续执行。在这个程序中,switch 语句是 while 循环体中唯一的语句,于是,switch 语句匹配失败后,将控制流返回给 while 循环条件。 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.10 节将讨论 break 语句。简单地说,break 语句中断当前的控制流。对于 switch 的应用,break 语句将控制跳出 switch,继续执行 switch 语句后面的第一个语句。在这个例子中,正如大家所知道的,将会把控制转移到 switch 后面的下一语句,即交回给 while。 6.6.2. Control Flow within a switch6.6.2. switch 中的控制流It is essential to understand that execution flows across case labels. 了解 case 标号的执行流是必要的。
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: 有时候,这种行为的确是正确的。程序员也许希望执行完某个特定标号的代码后,接着执行后续标号关联的语句。但更常见的是,我们只需要执行某个特定标号对应的代码。为了避免继续执行其后续 case 标号的内容,程序员必须利用 break 语句清楚地告诉编译器停止执行 switch 中的语句。大多数情况下,在下一个 case 标号之前的最后一条语句是 break。例如,下面统计元音出现次数的 switch 语句是不正确的: // 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. 为了搞清楚该程序导致了什么结果,假设 ch 的值是 'i' 来跟踪这个版本的代码。程序从 case 'i' 后面的语句开始执行,iCnt 的值加 1。但是,程序的执行并没有在这里停止,而是越过 case 标号继续执行,同时将 oCnt 和 uCnt 的值都加了 1.如果 ch 是 'e' 的话,那么 eCnt、iCnt、oCnt 以及 uCnt 的值都会加 1。
break Statements Aren't Always Appropriate慎用 break 语句,它并不总是恰当的There 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: 有这么一种常见的情况,程序员希望在 case 标号后省略 break 语句,允许程序向下执行多个 case 标号。这时,两个或多个 case 值由相同的动作序列来处理。由于系统限定一个 case 标号只能与一个值相关联,于是为了指定一个范围,典型的做法是,把 case 标号依次排列。例如,如果只希望计算文本中元音的总数,而不是每一个元音的个数,则可以这样写: 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: 每个 case 标号不一定要另起一行。为了强调这些 case 标号表示的是一个要匹配的范围,可以将它们全部在一行中列出:
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. 比较少见的用法是,为了执行某个 case 的代码后继续执行下一个 case 的代码,故意省略 break 语句。
6.6.3. The default Label6.6.3. default 标号The 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: default 标号提供了相当于 else 子句的功能。如果所有的 case 标号与 switch 表达式的值都不匹配,并且 default 标号存在,则执行 default 标号后面的语句。例如,在上述例子中添加一个计数器 otherCnt 统计读入多少个辅音字母,为 switch 结构增加 default 标号,其标志的分支实现 otherCnt 的自增: // 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. 在这个版本的代码中,如果 ch 不是元音,程序流程将执行 default 标号的相关语句,使 otherCnt 的值加 1。
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. 一个标号不能独立存在,它必须位于语句之前。如果 switch 结构以 default 标号结束,而且 default 分支不需要完成任何任务,那么该标号后面必须有一个空语句。 6.6.4. switch Expression and Case Labels6.6.4. switch 表达式与 case 标号The expression evaluated by a switch can be arbitrarily complex. In particular, the expression can define and intialize a variable: switch 求解的表达式可以非常复杂。特别是,该表达式也可以定义和初始化一个变量: 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. 在这个例子中,ival 被初始化为 get_response 函数的调用结果,其值将要与每个 case 标号作比较。变量 ival 始终存在于整个 switch 语句中,在 switch 结构外面该变量就不再有效了。 Case labels must be constant integral expressions (Section 2.7, p. 62). For example, the following labels result in compile-time errors: case 标号必须是整型常量表达式(第 2.7 节)。例如,下面的标号将导致编译时的错误: // 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. 如果两个 case 标号具有相同的值,同样也会导致编译时的错误。 6.6.5. Variable Definitions inside a switch6.6.5. switch 内部的变量定义Variables can be defined following only the last case or default label: 对于 switch 结构,只能在它的最后一个 case 标号或 default 标号后面定义变量:
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. 回顾变量的作用域,变量从它的定义点开始有效,直到它所在块结束为止。现在考虑如果在两个 case 标号之间定义变量会出现什么情况。该变量会在块结束之前一直存在。对于定义该变量的标号后面的其他 case 标号,它们所关联的代码都可以使用这个变量。如果 switch 从那些后续 case 标号开始执行,那么这个变量可能还未定义就要使用了。 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 定义变量,则可以引入块语句,在该块语句中定义变量,从而保证这个变量在使用前被定义和初始化。
case true:
{
// ok: declaration statement within a statement block
string file_name = get_file_name();
// ...
}
break;
case false:
// ...
![]() |
Exercises Section 6.6.5
|