Team LiB
Previous Section Next Section

6.6. The switch Statement

6.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:

假设要统计五个元音在文本里分别出现的次数,程序逻辑结构如下:

  • Read each character until there are no more characters to read

    按顺序读入每个字符直到读入完成为止。

  • Compare each character to the set of vowels

    把每个字符与元音字符集做比较。

  • If the character matches one of the vowels, add 1 to that vowel's count

    如果该字符与某个元音匹配,则该元音的计数器加 1。

  • Display the results

    显示结果。

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 switch

6.6.1. 使用 switch

We 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 switch

6.6.2. switch 中的控制流

It is essential to understand that execution flows across case labels.

了解 case 标号的执行流是必要的。

It is a common misunderstanding to expect that only the statements associated with the matched case label are executed. However, execution continues across case boundaries until the end of the switch statement or a break is encountered.

存在一个普遍的误解:以为程序只会执行匹配的 case 标号相关联的语句。实际上,程序从该点开始执行,并跨越 case 边界继续执行其他语句,直到 switch 结束或遇到 break 语句为止。



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 标号继续执行,同时将 oCntuCnt 的值都加了 1.如果 ch'e' 的话,那么 eCntiCntoCnt 以及 uCnt 的值都会加 1。

Forgetting to provide a break is a common source of bugs in switch statements.

对于 switch 结构,漏写 break 语句是常见的程序错误。



Although it is not strictly necessary to specify a break statement after the last label of a switch, the safest course is to provide a break after every label, even the last. If an additional case label is added later, then the break is already in place.

尽管没有严格要求在 switch 结构的最后一个标号之后指定 break 语句,但是,为了安全起见,最好在每个标号后面提供一个 break 语句,即使是最后一个标号也一样。如果以后在 switch 结构的末尾又需要添加一个新的 case 标号,则不用再在前面加 break 语句了。



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 语句。

Deliberately omitting a break at the end of a case happens rarely enough that a comment explaining the logic should be provided.

故意省略 case 后面的 break 语句是很罕见的,因此应该提供一些注释说明其逻辑。



6.6.3. The default Label

6.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。

It can be useful always to define a default label even if there is no processing to be done in the default case. Defining the label indicates to subsequent readers that the case was considered but that there is no work to be done.

哪怕没有语句要在 default 标号下执行,定义 default 标号仍然是有用的。定义 default 标号是为了告诉它的读者,表明这种情况已经考虑到了,只是没什么要执行的。



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 Labels

6.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 switch

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

Exercise 6.7:

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 vowelthat is, your program should count both 'a' and 'A' as part of aCnt, and so forth.

前面已实现的统计元音的程序存在一个问题:不能统计大写的元音字母。编写程序统计大小写的元音,也就是说,你的程序计算出来的 aCnt,既包括 'a' 也包括 'A' 出现的次数,其他四个元音也一样。

Exercise 6.8:

Modify our vowel-count program so that it also counts the number of blank spaces, tabs, and newlines read.

修改元音统计程序使其可统计出读入的空格、制表符和换行符的个数。

Exercise 6.9:

Modify our vowel-count program so that it counts the number of occurrences of the following two-character sequences: ff, fl, and fi.

修改元音统计程序使其可统计以下双字符序列出现的次数:fffl 以及 fi

Exercise 6.10:

Each of the programs in the highlighted text on page 206 contains a common programming error. Identify and correct each error.

下面每段代码都暴露了一个常见编程错误。请指出并修改之。

Code for Exercises in Section 6.6.5

     (a) switch (ival) {
             case 'a': aCnt++;
             case 'e': eCnt++;
             default: iouCnt++;
         }

     (b) switch (ival) {
             case 1:
                 int ix = get_value();
                 ivec[ ix ] = ival;
                 break;
             default:
                 ix = ivec.size()-1;
                 ivec[ ix ] = ival;
         }

     (c) switch (ival) {
             case 1, 3, 5, 7, 9:
                 oddcnt++;
                 break;
             case 2, 4, 6, 8, 10:
                 evencnt++;
                 break;
         }

     (d) int ival=512 jval=1024, kval=4096;
         int bufsize;
         // ...
         switch(swt) {
             case ival:
                 bufsize = ival * sizeof(int);
                 break;
             case jval:
                 bufsize = jval * sizeof(int);
                 break;
             case kval:
                 bufsize = kval * sizeof(int);
                 break;
         }



Team LiB
Previous Section Next Section