切换声明是我喜爱switch
与if/else if
构造的个人主要原因之一.这里有一个例子:
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[]
s应该在函数之外声明:嗯,他们是,这只是一个例子.
编译器失败并出现以下错误:
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
s的情况下获得这种行为?
(复制/粘贴我在其他地方提供的答案)
通过下降switch
- case
S可通过不具有代码在一个来实现case
(见case 0
),或者使用特殊的goto case
(见case 1
)或goto default
(见case 2
)形式:
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中不常见的错误来源.
解决方法是使用goto,例如
switch (number.ToString().Length) { case 3: ans += string.Format("{0} hundred and ", numbers[number / 100]); goto case 2; case 2: // Etc }
在我看来,开关/外壳的总体设计有点不幸.它离C太近 - 有一些有用的变化可以在范围等方面进行.可以说,一个更聪明的开关可以进行模式匹配等会有所帮助,但这确实从开关变为"检查一系列条件" - 此时可能会要求使用其他名称.
交换机漏洞历史上是现代软件中错误的主要来源之一.语言设计者决定强制要求在案例结束时跳转,除非您在没有处理的情况下直接默认为下一个案例.
switch(value) { case 1:// this is still legal case 2: }
为了在这里添加答案,我认为值得考虑与此相关的相反问题,即.为什么C首先允许掉线?
任何编程语言当然都有两个目标:
向计算机提供说明.
留下程序员的意图记录.
因此,任何编程语言的创建都是如何最好地服务于这两个目标之间的平衡.一方面,变得更容易变成计算机指令(无论是机器代码,字节码如IL,还是指令在执行时被解释),那么编译或解释过程将更加高效,可靠和紧凑的输出.尽管如此,这个目标导致我们只是在汇编,IL或甚至原始操作码中编写,因为最简单的编译是根本没有编译的地方.
相反,语言表达程序员的意图越多,而不是为此目的采取的手段,在编写和维护期间程序就越容易理解.
现在,switch
总是可以通过将其转换为等效的if-else
块链或类似链来编译,但它被设计为允许编译成特定的公共汇编模式,其中一个获取值,计算它的偏移量(无论是通过查找表通过值的完美散列索引,或通过值*的实际算术索引.值得注意的是,今天,C#编译有时会switch
变成等效的if-else
,有时会使用基于散列的跳转方法(同样使用C,C++和其他具有可比语法的语言).
在这种情况下,有两个很好的理由允许掉期:
它无论如何都是自然发生的:如果你将一个跳转表构建成一组指令,并且其中一个早期批量指令不包含某种跳转或返回,那么执行将自然地进入下一批.如果你将switch
-using C转换为使用机器码的跳转表,那么允许直通就是"刚刚发生" .
在汇编中编写的编码器已经习惯了等价物:当在汇编中手动编写跳转表时,他们必须考虑给定的代码块是以返回结束,跳出表格还是继续到下一个街区.因此,让编码器break
在必要时添加明确的内容对于编码器来说也是"自然的".
因此,在计算机语言的两个目标之间进行平衡是合理的尝试,因为它与生成的机器代码和源代码的表达性有关.
四十年后,情况并不完全相同,原因有以下几点:
今天C中的编码员可能很少或根本没有装配经验.许多其他C风格语言的编码器甚至不太可能(尤其是Javascript!).任何"人们习惯于装配"的概念都不再相关.
优化的改进意味着,由于被认为可能最有效的方法,或者转变为跳转表方法的特别深奥的变体,所以switch
被转变的可能性if-else
更高.较高级别和较低级别方法之间的映射不像以前那样强大.
经验表明,跌倒往往是少数情况而不是常态(对Sun的编译器的研究发现,3%的switch
块使用了同一块上的多个标签以外的跌倒,并且认为使用 -这里的案例意味着这3%实际上远高于正常水平).因此,所研究的语言使得不寻常的事情比普通的更容易照顾.
经验表明,在意外完成的情况下,以及在维护代码的人错过正确的漏报的情况下,跌倒往往是问题的根源.后者是与掉落相关的错误的一个微妙的补充,因为即使您的代码完全没有错误,您的堕落仍然可能导致问题.
与最后两点相关,请考虑当前版本的K&R中的以下引用:
从一个案例落到另一个案例并不健全,在修改程序时容易崩溃.除了单个计算的多个标签之外,应谨慎使用漏洞并进行评论.
作为一个好的形式,在最后一个案例(默认在这里)之后休息,即使它在逻辑上是不必要的.有一天,当最后添加另一个案例时,这一点防御性编程将为您节省开支.
因此,从马的口中,C中的跌落是有问题的.总是记录带有注释的漏洞是一种良好的做法,这是应用一般原则的应用,应该记录一个人做一些不寻常的事情,因为这将是后来检查代码和/或使你的代码看起来像什么的当它实际上是正确的时,它有一个新手的错误.
当你考虑它时,代码如下:
switch(x) { case 1: foo(); /* FALLTHRU */ case 2: bar(); break; }
被添加的东西,使落空在代码中明确的,它只是没有东西可以被检测(或者其缺乏可检测)的编译器.
因此,事实上必须明确表示C#中的堕落并不会给那些在其他C风格的语言中写得很好的人增加任何惩罚,因为他们已经在他们的堕落中明确表示.†
最后,goto
这里的使用已经是C和其他类似语言的标准:
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#建立在已经正常的方式来处理我们想要在a中击中多个代码块的一种情况switch
,并且只是将其概括为覆盖掉落.它还使这两种情况更方便和自我记录,因为我们必须在C中添加一个新标签,但可以case
在C#中使用它作为标签.在C#中,我们可以摆脱below_six
标签和使用goto case 5
,这更清楚我们正在做什么.(我们也不得不添加break
的default
,这是我离开了只是为了让上面的C代码显然不是C#代码).
因此总结如下:
C#不再像40年前的C代码那样直接与未经优化的编译器输出相关(这些天也不是C),这使得跌倒的灵感之一无关紧要.
C#与C语言保持兼容,不仅具有隐含性break
,便于熟悉类似语言的人学习语言,并且更容易移植.
C#删除了一个可能的错误来源或被误解的代码,这些代码在过去四十年中已被充分记录为导致问题.
C#通过编译器强制执行C(文档落实)现有的最佳实践.
C#使得不常见的情况是具有更明确代码的情况,通常情况下代码之一只是自动写入.
C#使用相同goto
的方法从C中使用的不同case
标签中命中相同的块.它只是将其推广到其他一些情况.
goto
通过允许case
语句充当标签,C#使得基于该方法的方法比在C中更方便,更清晰.
总而言之,这是一个非常合理的设计决策
*某些形式的BASIC可以让人们做出GOTO (x AND 7) * 50 + 240
类似的,而脆弱的,因此一个特别有说服力的禁止的情况goto
,确实有助于显示更高级的语言相当于低级代码可以跳过的方式算术一个值,当它是编译的结果而不是必须手动维护的东西时更合理.Duff设备的实现尤其适用于等效的机器代码或IL,因为每个指令块通常具有相同的长度而无需添加nop
填充物.
†Duff的设备再次出现在这里,作为一个合理的例外.事实上,即使没有对这种效果的明确评论,使用这种和相似的模式重复操作也可以使得使用跌倒相对清晰.
你可以'转到案例标签' http://www.blackwasp.co.uk/CSharpGoto.aspx
goto语句是一个简单的命令,它无条件地将程序的控制权转移到另一个语句.该命令经常受到一些开发人员的批评,他们主张将其从所有高级编程语言中删除,因为它可能导致意大利面条代码.当有如此多的goto语句或类似的跳转语句使代码难以阅读和维护时,会发生这种情况.但是,有些程序员指出goto语句在仔细使用时可以为某些问题提供优雅的解决方案......
他们通过设计省略了这种行为,以避免它不被意志使用但引起问题.
只有在案例部分中没有语句时才能使用它,例如:
switch (whatever) { case 1: case 2: case 3: boo; break; }