为什么C ++需要在switch语句中断?


Why does C++ require breaks in switch statements?

本问题已经有最佳答案,请猛点这里访问。

在C++中编写开关语句时,似乎需要在每一种情况下都包含一个中断。否则,代码将继续运行到下一个案例中。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
int x = 1;
switch (x)
{
    case 0:
        std::cout <<"x is 0." << std::endl;
    case 1:
        std::cout <<"x is 1." << std::endl;
    case 2:
        std::cout <<"x is 2." << std::endl;
    default:
        std::cout <<"x is neither 0, 1 nor 2." << std::endl;
}

将返回:

1
2
>> x is 1.
>> x is 2.

然而:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int x = 1;
switch (x)
{
    case 0:
        std::cout <<"x is 0." << std::endl;
        break;
    case 1:
        std::cout <<"x is 1." << std::endl;
        break;
    case 2:
        std::cout <<"x is 2." << std::endl;
        break;
    default:
        std::cout <<"x is neither 0, 1 nor 2." << std::endl;
        break;
}

将返回:

1
>> x is 1.

我的问题是:如果有必要为每一个案例都包含中断,那么C++为什么需要明确地编写它呢?为什么不在C++中默认每一种情况下中断开关语句呢?有没有什么例子表明这种行为实际上是不可取的?


这有利于"突破"案件:

1
2
3
4
5
6
7
switch (x)
{
    case 0:
    case 1:
        std::cout <<"x is 0 or 1." << std::endl;
        break;
}

在本例中,如果x为0或1,则执行case语句。


这是因为switch val将被转换成一个跳转到case (some value):所在的特定地址,然后CPU将继续正常执行代码,因此取下一个地址并继续执行,取下一个地址并继续执行,case块在内存中是连续的地址,因此执行将失败。break;会告诉CPU再次跳转,这一次超出了开关盒块,不再执行casees。

失败可能是受益者,在一些新的语言中,你必须明确地说你想要它,在C和C++中跌倒是隐含的。

我的机器示例:

1
2
3
4
5
6
7
8
9
int i=1;
switch (i) {
case 1:
        i=5;
        break;
default:
        i=6;
}
return 0;

将成为

1
2
3
4
5
6
7
8
9
10
0x100000eeb:  movl   $0x1, -0x8(%rbp) ;int i=1;
0x100000ef2:  xorl   %eax, %eax
0x100000ef4:  movb   %al, %cl
0x100000ef6:  testb  %cl, %cl         ;switch(i)
0x100000ef8:  jne    0x100000f0f      ;i is not 1 go to default:
0x100000efe:  jmp    0x100000f03      ;i is 1 go to case 1:
0x100000f03:  movl   $0x5, -0x8(%rbp) ;case 1: i=5;
0x100000f0a:  jmp    0x100000f16      ;break;
0x100000f0f:  movl   $0x6, -0x8(%rbp) ;default: i=6;
0x100000f16:  movl   $0x0, %eax       ;first instruction after switch-case

如果在i=5;之后没有跳转,那么CPU也会执行default:


因为这种行为是从C继承的,而C使用显式的break。切换Fallthrough在当时更有用,这就是为什么它被选为"默认"行为的原因。

编程时间要长得多,机器时间要短得多,因此设计最大的潜在效率而不是可读性就显得更有意义了。编译器的优化能力(在许多方面)要小得多。您可以查看像达夫的设备这样的东西,以获取如何使用这种行为的示例。


首先,第一个程序的输出看起来像

1
2
3
x is 1.
x is 2.
x is neither 0, 1 nor 2.

C++允许通过没有断言语句的所有case标签传递控件。例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
char c;

std::cin >> c;

switch ( c )
{
case 'y':
case 'Y':
    std::cout <<"Y was pressed" << std::endl;
    break;

case 'n':
case 'N':
    std::cout <<"N was pressed" << std::endl;
    break;

default:
    std::cout <<"Neither Y nor N was pressed" << std::endl;
    break;
}

其他一些语言,例如C,不允许这样做。然而,在任何情况下,它们都需要break语句。:)


可能会出现这样的情况:你可能需要或想要得到两个相同的结果,或者更多的情况下,你不需要休息。像这样:

1
2
3
4
5
6
7
8
9
10
11
switch (x)
{
case 1:
case 2:
case 3:
 some task
 break;
deafult:
 do some other task
 break;
}

上述代码最终与以下代码相同:

1
2
3
4
5
6
7
8
9
10
11
12
switch (x) {
    case 0: // The case 1 code is shared here
    case 1:
        // code
        goto case 2;
    case 2:
        //some code here
        goto default;
    default:
        //some other code
        break;
}

来自K&R

Falling through from one case to another is not robust, being prone to
disintegration when the program is modified. With the exception of
multiple labels for a single computation, fall-throughs should be used
sparingly, and commented.

As a matter of good form, put a break after the last case (the default
here) even though it's logically unnecessary. Some day when another
case gets added at the end, this bit of defensive programming will
save you.

正如小狗提到的,巴哈维奥语是C语言的固有语言,所以引用了《C编程专家》一书中的一句话。

We analyzed the Sun C compiler sources
to see how often the default fall
through was used. The Sun ANSI C
compiler front end has 244 switch
statements, each of which has an
average of seven cases. Fall through
occurs in just 3% of all these cases.

In other words, the normal switch
behavior is wrong 97% of the time.
It's not just in a compiler - on the
contrary, where fall through was used
in this analysis it was often for
situations that occur more frequently
in a compiler than in other software,
for instance, when compiling operators
that can have either one or two
operands:

1
2
3
4
5
6
7
switch (operator->num_of_operands) {
    case 2: process_operand( operator->operand_2);
              /* FALLTHRU */

    case 1: process_operand( operator->operand_1);
    break;
}

Case fall through is so widely
recognized as a defect that there's
even a special comment convention,
shown above, that tells lint"this is
really one of those 3% of cases where
fall through was desired."


一个非常常见的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
switch (month) {
 case 2:
   if(isLeapYear)
    days = 29;  
   else
    days = 28;
    break;
  case 4:
  case 6:
  case 9:    // do the same thing
  case 11:
    days = 30;
     break;
  default:
    days = 31;
 }

从上面的例子中——您得到了一个更干净的代码,而且,如果每次情况发生后都需要隐式地中断开关,那么您就拥有了比所需要的更多的灵活性。


是的,在每个开关箱之后都必须包括一个break或一个return。一个很有用的例子是,并非每种情况都有自动中断,当您获得关键事件时,某些关键应该做同样的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
switch (current_key)
{
  case KEY_W:
  case KEY_UP:
  case KEY_SPACE:
       player->jump();
       break;
  case KEY_S:
  case KEY_DOWN:
  case KEY_SHIFT:
       player->cover();
       break;
  default:
       //Do something
       break;
}

您可以为您编写这样的代码:

1
2
3
4
5
6
7
8
9
10
11
12
const char* make_x_to_string(int x)
{
   switch (x)
   {
    case 0:
      return"x is zero";
    case 1:
      return"x is one";
    default:
      return"x is neither zero or one";
   }
}

然后简单的打电话

1
cout << make_x_to_string(0) << endl;

你不需要break,因为return退出了函数。