在C+中使用switch语句

Switch statement fallthrough in C#?

switch语句fallthrough是我喜欢switchif/else if构造的主要原因之一。下面是一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static string NumberToWords(int number)
{
    string[] numbers = new string[]
        {"","one","two","three","four","five",
         "six","seven","eight","nine" };
    string[] tens = new string[]
        {"","","twenty","thirty","forty","fifty",
         "sixty","seventy","eighty","ninety" };
    string[] teens = new string[]
        {"ten","eleven","twelve","thirteen","fourteen","fifteen",
         "sixteen","seventeen","eighteen","nineteen" };

    string ans ="";
    switch (number.ToString().Length)
    {
        case 3:
            ans += string.Format("{0} hundred and", numbers[number / 100]);
        case 2:
            int t = (number / 10) % 10;
            if (t == 1)
            {
                ans += teens[number % 10];
                break;
            }
            else if (t > 1)
                ans += string.Format("{0}-", tens[t]);
        case 1:
            int o = number % 10;
            ans += numbers[o];

            break;
        default:
            throw new ArgumentException("number");
    }
    return ans;
}

聪明人之所以畏缩,是因为string[]应该在函数之外声明:好吧,他们就是这样,这只是一个例子。

编译器失败,错误如下:

1
2
Control cannot fall through from one case label ('case 3:') to another
Control cannot fall through from one case label ('case 2:') to another

为什么?如果没有三个if的话,有没有办法做到这一点?


(复制/粘贴我在别处提供的答案)

通过switchcase可以通过在case中没有代码(见case 0或使用特殊的goto case形式(见case 1goto default形式(见case 2来实现:

1
2
3
4
5
6
7
8
9
10
11
12
switch (/*...*/) {
    case 0: // shares the exact same code as case 1
    case 1:
        // do something
        goto case 2;
    case 2:
        // do something else
        goto default;
    default:
        // do something entirely different
        break;
}


"为什么"是为了避免意外摔倒,对此我很感激。在C和Java中,这是一个不常见的bug来源。

解决方法是使用goto,例如

1
2
3
4
5
6
7
8
switch (number.ToString().Length)
{
    case 3:
        ans += string.Format("{0} hundred and", numbers[number / 100]);
        goto case 2;
    case 2:
    // Etc
}

在我看来,交换机/机箱的总体设计有点不幸。它离C太近了——在作用域等方面可以进行一些有用的更改。可以说,更智能的开关可以进行模式匹配等是有用的,但这实际上是从开关变为"检查一系列条件"——此时可能会调用不同的名称。


切换失败历来是现代软件中的主要缺陷源之一。语言设计人员决定强制在案例结束时跳转,除非您默认直接进入下一个案例而不进行处理。

1
2
3
4
5
switch(value)
{
    case 1:// this is still legal
    case 2:
}


为了增加这里的答案,我认为有必要结合这个问题来考虑另一个问题,即。为什么C一开始就允许摔倒?好的。

当然,任何编程语言都有两个目标:好的。

  • 向计算机提供说明。
  • 留下程序员意图的记录。
  • 因此,任何编程语言的创建都是如何最好地服务于这两个目标之间的平衡。一方面,它更容易转化为计算机指令(无论是机器代码、像IL这样的字节码,还是在执行时解释的指令),那么编译或解释过程将更有效率、可靠和紧凑的输出。从极端的角度来看,这个目标导致我们只是在汇编、IL甚至原始操作代码中编写代码,因为最简单的编译就是根本没有编译的地方。好的。

    相反,语言表达程序员意图的越多,而不是为此目的所采取的手段,在编写和维护程序时,程序就越容易理解。好的。

    现在,switch可以通过将其转换为if-else块或类似块的等效链来编译,但它的设计允许编译为一个特定的通用汇编模式,在这种模式下,取一个值,计算它的偏移量(无论是通过查找一个由值的完全散列索引的表,还是通过实际值的算术运算*)。值得注意的是,今天C编译有时会将EDCOX1〔0〕转换成等效的EDCOX1〔1〕,有时使用基于哈希的跳转方法(同样地,用C、C++和其他具有可比较语法的语言)。好的。

    在这种情况下,允许坠落的原因有两个:好的。

  • 不管怎样,这是自然发生的:如果您将一个跳转表构建成一组指令,并且早期的一批指令中没有包含某种跳转或返回,那么执行将自然地进入下一批。如果您使用机器代码将使用C的switch转换为使用跳跃表的话,"刚刚发生的事情"就是允许坠落。好的。

  • 在汇编中编写代码的程序员已经习惯了同样的做法:在汇编中手工编写跳转表时,他们必须考虑给定的代码块是以返回、跳出表还是继续下一个块结尾。因此,让编码人员在必要时添加一个明确的break,对编码人员来说也是"自然的"。好的。

  • 因此,在当时,平衡计算机语言的两个目标是一种合理的尝试,因为它既与生成的机器代码有关,也与源代码的表达有关。好的。

    不过,四十年后,情况并不完全相同,原因有几个:好的。

  • 今天C语言的程序员可能很少或根本没有汇编经验。许多其他C风格语言的编码人员甚至不太可能(特别是javascript!)"人们从组装中习惯了什么"的任何概念都不再相关。
  • 优化方面的改进意味着,由于认为switch方法可能最有效,因此switch被转变为if-else的可能性更高,或者转变为跳台方法的一种特别深奥的变体的可能性更高。高层次和低层次方法之间的映射并不像以前那样强大。
  • 经验表明,失败往往是少数情况,而不是正常情况(一项对Sun编译器的研究发现,3%的switch块使用了同一块上不同于多个标签的失败,并且认为这里的用例意味着这3%实际上比正常情况高得多)。因此,所研究的语言使得这种不同寻常的语言比普通语言更容易被迎合。
  • 经验表明,无论是在意外情况下,还是在维护代码的人错过了正确的故障时,故障往往都是问题的根源。后者是对与故障相关联的错误的一个微妙的补充,因为即使您的代码完全没有故障,故障仍然会导致问题。
  • 关于最后两点,请考虑一下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.

    Ok.

    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.

    Ok.

    所以,从马的口中,通过C是有问题的。最好的做法是总是用注释来记录掉掉的地方,这是一种应用一般原则的做法,即一个人应该记录自己做了不寻常的事情的地方,因为这将使以后的代码检查和/或使你的代码看起来像是有一个新手的错误,当它实际上是正确的时候。好的。

    当你想到这一点时,就要这样编码:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    switch(x)
    {
      case 1:
       foo();
       /* FALLTHRU */
      case 2:
        bar();
        break;
    }

    添加一些东西使代码中的fall-through显式化,但编译器无法检测到(或检测到其缺失)。好的。

    因此,事实上,on必须在c中明确表示fall through,这不会给那些用其他c风格语言写得很好的人增加任何惩罚,因为他们在fall through中已经明确了。好的。

    最后,这里使用goto已经是C语言和其他此类语言的规范:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    switch(x)
    {
      case 0:
      case 1:
      case 2:
        foo();
        goto below_six;
      case 3:
        bar();
        goto below_six;
      case 4:
        baz();
        /* FALLTHRU */
      case 5:
      below_six:
        qux();
        break;
      default:
        quux();
    }

    在这种情况下,如果我们希望一个块包含在为值执行的代码中,而不仅仅是将值带到前面的块中,那么我们已经不得不使用goto。(当然,有很多方法和方法可以用不同的条件来避免这个问题,但这对于与这个问题有关的所有事情都是正确的)。因此,C建立在已经很正常的方法之上,以处理一种情况,即我们希望在一个switch中命中多个代码块,并将其概括为覆盖fall-through。这也使得这两种情况更加方便和自我记录,因为我们必须在C中添加一个新的标签,但可以在C中使用case作为标签。在C中,我们可以去掉below_six标签,使用goto case 5,这更清楚地说明了我们在做什么。(我们还必须为default添加break,我只是为了使上面的C代码明显不是C代码而省略了这一点)。好的。

    综上所述:好的。

  • C不再像40年前的C代码那样直接与未经优化的编译器输出相关(现在的C代码也不如此),这使得"失败"的灵感之一变得无关紧要。
  • C与C兼容的不仅仅是隐式break,更容易被熟悉类似语言的人学习,更容易移植。
  • C删除了一个可能的bug源或误解的代码,这些代码在过去40年中被充分记录为导致问题的代码。
  • C使现有的C(文档贯穿)最佳实践可由编译器强制执行。
  • C使异常情况成为代码更明确的情况,通常情况下是代码自动写入的情况。
  • c使用与c中使用的相同的基于goto的方法从不同的case标签击中同一块。它只是将其概括为其他一些情况。
  • c通过允许case语句作为标签,使得基于goto的方法比C中的方法更方便、更清晰。
  • 总之,一个相当合理的设计决策好的。

    *有些形式的basic允许人们做像GOTO (x AND 7) * 50 + 240这样的事情,虽然很脆弱,因此对于禁止goto来说,这是一个特别有说服力的案例,但它确实显示了一种更高的语言等价物,这种语言等价于较低级别的代码可以根据一个值的算术进行跳转的方式,当结果是而不是必须手动维护的内容。Duff设备的实现尤其适合使用等效的机器代码或IL,因为每个指令块的长度通常相同,而不需要添加nop填充符。好的。

    ?达夫的装置再次出现在这里,作为一个合理的例外。事实上,有了这种模式和类似的模式,就有了重复的操作,即使没有对这种效果做出明确的评论,也能使fall-through的使用相对清晰。好的。好啊。


    您可以"转到案例标签"http://www.blackwasp.co.uk/csharpgoto.aspx

    The goto statement is a simple command that unconditionally transfers the control of the program to another statement. The command is often criticised with some developers advocating its removal from all high-level programming languages because it can lead to spaghetti code. This occurs when there are so many goto statements or similar jump statements that the code becomes difficult to read and maintain. However, there are programmers who point out that the goto statement, when used carefully, provides an elegant solution to some problems...


    他们在设计上忽略了这种行为,以避免在遗嘱没有使用这种行为,但却造成了问题。

    它只能在案例部分没有语句时使用,如:

    1
    2
    3
    4
    5
    6
    switch (whatever)
    {
        case 1:
        case 2:
        case 3: boo; break;
    }


    他们改变了C语句的切换语句(从C//爪哇/ C++)的行为。我想原因是人们忘记了摔倒和失误。我读到的一本书说用goto来模拟,但这对我来说似乎不是一个好的解决方案。


    开关(C参考)表示

    C# requires the end of switch sections, including the final one,

    因此,您还需要将一个break;添加到您的default部分,否则仍会有编译器错误。


    只需添加一个简短的注释,Xamarin的编译器实际上弄错了这个错误,并且它允许执行fallthrough。它应该已经被修复了,但还没有被释放。在一些代码中发现了这一点,编译器没有抱怨。


    你可以通过GOTO关键字实现像C++一样的下降。

    前任:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    switch(num)
    {
       case 1:
          goto case 3;
       case 2:
          goto case 3;
       case 3:
          //do something
          break;
       case 4:
          //do something else
          break;
       case default:
          break;
    }


    在每个case语句之后,即使它是默认情况,也需要break或goto语句。


    A jump statement such as a break is
    required after each case block,
    including the last block whether it is
    a case statement or a default
    statement. With one exception, (unlike
    the C++ switch statement), C# does not
    support an implicit fall through from
    one case label to another. The one
    exception is if a case statement has
    no code.

    --c switch()文档


    C不支持通过switch/case语句执行fall through。不知道为什么,但确实没有人支持。连杆机构


    您忘记在案例3中添加"break;"语句。在案例2中,您将它写到if块中。因此,请尝试以下方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    case 3:            
    {
        ans += string.Format("{0} hundred and", numbers[number / 100]);
        break;
    }


    case 2:            
    {
        int t = (number / 10) % 10;            
        if (t == 1)            
        {                
            ans += teens[number % 10];                
        }            
        else if (t > 1)                
        {
            ans += string.Format("{0}-", tens[t]);        
        }
        break;
    }

    case 1:            
    {
        int o = number % 10;            
        ans += numbers[o];            
        break;        
    }

    default:            
    {
        throw new ArgumentException("number");
    }