148

给定一个简单的 switch 语句

switch (int)
{
    case 1 :
    {
        printf("1\n");
        break;
    }

    case 2 : 
    {
        printf("2\n");
    }

    case 3 : 
    {
        printf("3\n");
    }
}

在 case 2 中没有 break 语句,意味着在 case 3 的代码中继续执行。这不是偶然的;它就是这样设计的。为什么做出这个决定?与块的自动中断语义相比,这有什么好处?理由是什么?

4

9 回答 9

158

许多答案似乎都集中在失败的能力上作为要求声明的原因。break

我相信这只是一个错误,主要是因为在设计 C 语言时,并没有太多关于如何使用这些结构的经验。

Peter Van der Linden 在他的《Expert C Programming》一书中提出了这个问题:

我们分析了 Sun C 编译器源代码,以了解使用默认失败的频率。Sun ANSI C 编译器前端有 244 个 switch 语句,每个语句平均有 7 个 case。在所有这些案例中,只有 3% 发生了失败。

换句话说,正常的开关行为在97% 的情况下都是错误的。它不仅在编译器中 - 相反,在此分析中使用失败通常用于在编译器中比在其他软件中更频繁地发生的情况,例如,在编译可以具有一个或两个操作数的运算符时:

switch (operator->num_of_operands) {
    case 2: process_operand( operator->operand_2);
              /* FALLTHRU */

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

案例失败被广泛认为是一种缺陷,甚至还有一个特殊的注释约定,如上所示,它告诉 lint “这确实是需要失败的 3% 案例之一。”

我认为对于 C# 来说,在每个 case 块的末尾需要一个明确的跳转语句是个好主意(同时仍然允许堆叠多个 case 标签——只要只有一个语句块)。在 C# 中,您仍然可以让一个案例落入另一个案例 - 您只需通过使用goto.

太糟糕了Java没有抓住机会打破C语义。

于 2008-10-31T06:18:53.567 回答
31

在很多方面,c 只是标准汇编习惯用法的干净接口。在编写跳转表驱动流控制时,程序员可以选择是跳出还是跳出“控制结构”,而跳出需要显式指令。

所以,c做同样的事情......

于 2008-10-31T03:06:25.220 回答
25

要实现 Duff 的设备,显然:

dsend(to, from, count)
char *to, *from;
int count;
{
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
               } while (--n > 0);
    }
}
于 2008-10-31T03:03:07.973 回答
19

如果案例被设计为隐式中断,那么您就不可能失败。

case 0:
case 1:
case 2:
    // all do the same thing.
    break;
case 3:
case 4:
    // do something different.
    break;
default:
    // something else entirely.

如果开关设计为在每个案例之后隐式中断,您将别无选择。开关盒结构的设计方式更加灵活。

于 2008-10-31T03:01:23.077 回答
16

switch 语句中的 case 语句只是简单的标签。

当您打开一个值时,switch 语句本质上会转到具有匹配值的标签。

这意味着中断是必要的,以避免传递到下一个标签下的代码。

至于以这种方式实现的原因 - switch 语句的贯穿特性在某些情况下可能很有用例如:

case optionA:
    // optionA needs to do its own thing, and also B's thing.
    // Fall-through to optionB afterwards.
    // Its behaviour is a superset of B's.
case optionB:
    // optionB needs to do its own thing
    // Its behaviour is a subset of A's.
    break;
case optionC:
    // optionC is quite independent so it does its own thing.
    break;
于 2008-10-31T03:02:43.357 回答
9

允许这样的事情:

switch(foo) {
case 1:
    /* stuff for case 1 only */
    if (0) {
case 2:
    /* stuff for case 2 only */
    }
    /* stuff for cases 1 and 2 */
case 3:
    /* stuff for cases 1, 2, and 3 */
}

case关键字视为goto标签,它会自然而然地出现。

于 2010-11-12T22:42:22.023 回答
3

当多个案例需要执行相同的代码(或按顺序执行相同的代码)时,它消除了代码重复。

由于在汇编语言级别上,它并不关心您是否在每一个之间中断,无论如何,失败案例的开销为零,所以为什么不允许它们,因为它们在某些情况下提供了显着的优势。

于 2008-10-31T03:01:30.143 回答
3

我碰巧遇到了将向量中的值分配给结构的情况:必须以这样一种方式完成,即如果数据向量短于结构中数据成员的数量,则其余成员将保留在他们的默认值。在那种情况下,省略break是非常有用的。

switch (nShorts)
{
case 4: frame.leadV1    = shortArray[3];
case 3: frame.leadIII   = shortArray[2];
case 2: frame.leadII    = shortArray[1];
case 1: frame.leadI     = shortArray[0]; break;
default: TS_ASSERT(false);
}
于 2018-08-14T10:14:38.233 回答
0

正如这里许多人所指定的,它允许单个代码块适用于多种情况。对于您的 switch 语句,这应该比您在示例中指定的“每个案例的代码块”更常见。

如果您每个案例都有一个代码块而没有失败,也许您应该考虑使用 if-elseif-else 块,因为这似乎更合适。

于 2008-10-31T05:29:25.510 回答