要在此处添加答案,我认为值得与此同时考虑相反的问题,即。为什么C首先允许失败?
任何编程语言当然都有两个目标:
- 向计算机提供说明。
- 记录程序员的意图。
因此,任何编程语言的创建都是如何最好地实现这两个目标之间的平衡。一方面,越容易将其转换为计算机指令(无论是机器码,IL之类的字节码,还是在执行时对指令进行解释),那么越有可能使编译或解释过程变得高效,可靠且输出紧凑。达到极限,这个目标导致我们只用汇编,IL甚至原始操作码编写代码,因为最简单的编译就是根本没有编译。
相反,语言表达的语言越多,表明程序员的意图,而不是为此目的而采取的手段,则在编写和维护过程中程序就越容易理解。
现在,switch
始终可以通过将其转换为等效的if-else
块链或类似链来进行编译,但是它被设计为允许编译为一种特殊的通用装配模式,在该模式中,一个值将获取一个值,然后从中计算一个偏移量(无论是通过查找表来进行)通过值的完美哈希值或通过对值*的实际算术索引)。值得注意的是,今天,C#编译有时会switch
变成等效的if-else
,有时会使用基于哈希的跳转方法(对于C,C ++和其他具有类似语法的语言也是如此)。
在这种情况下,允许掉线有两个很好的理由:
无论如何,它都是自然发生的:如果将跳转表构建到一组指令中,而较早的一批指令中不包含某种跳转或返回,则执行将自然而然地进入下一批。如果将switch
使用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#中明确显示on的事实并不会给使用其他C风格语言写得很好的人增加任何惩罚,因为他们在错误中已经很明确了。†
最后,在goto
这里使用C已经成为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#建立在已经正常的方式上,可以处理一种情况,即我们想在中击中多个代码块switch
,并将其概括化以涵盖穿透性。由于我们必须在C中添加一个新标签,但可以case
在C#中将其用作标签,这也使这两种情况更加方便且具有自记录功能。在C#中,我们可以摆脱below_six
标签并使用goto case 5
关于我们正在做的事情更清晰的标签。(我们还必须添加break
对default
,这是我离开了只是为了让上面的C代码显然不是C#代码)。
因此,总而言之:
- C#不再像40年前的C代码一样直接与未经优化的编译器输出相关(如今C也不是如此),这使得掉线的灵感之一不相关。
- C#不仅与C保持兼容,而且具有隐式
break
,以便熟悉类似语言的人更轻松地学习该语言,并更轻松地进行移植。
- C#消除了可能的错误或误解代码的来源,这些错误或误解的代码在过去的40年中一直被证明会引起问题。
- C#使编译器可以执行C(文档掉入)的现有最佳实践。
- C#使异常情况下的代码更加明确,而通常情况下代码中的代码只是自动编写。
- C#使用与C中相同的
goto
基于方法,从不同的case
标签中击中相同的块。只是将其推广到其他一些情况。
goto
通过允许case
语句充当标签,C#使基于C 的方法比C中的方法更方便,更清晰。
总而言之,一个相当合理的设计决策
*某些形式的BASIC将允许执行GOTO (x AND 7) * 50 + 240
类似的操作,而这种操作虽然很脆弱,但因此是一个特别有说服力的禁止案例goto
,但确实显示了较高语言,相当于较低级代码可以基于以下方式进行跳转的方式对值进行算术运算,这是编译的结果,而不是必须手动维护的值,这更加合理。Duff设备的实现尤其适合使用等效的机器代码或IL,因为每个指令块通常具有相同的长度,而无需添加nop
填充符。
†作为合理的例外,达夫的装置再次出现在这里。通过这种操作和类似的模式,可以重复执行操作,即使没有对此效果进行明确评论,也可以使穿透功能的使用相对清晰。