为什么在case语句中标签应该是常量?

24

在JavaScript中,以下语句是有效的。

switch(true) {
    case a > b:
        max = a;
        break;
    case a < b:
        max = b;
        break;
    default:
       max = a;
}

但在C/C++编程语言中,当我写下这个语句时,编译器会显示一个错误,指出case语句必须由常量值组成。在特定情况下,编写此类switch-case语句非常有用,但C/C++不允许这样做。

现在我很好奇为什么不允许在case语句中使用变量值?


17
抱歉,但 switch(true) 的意义是什么? - Beta Carotin
19
我不确定这是设计选择的原因还是结果,但要求它们是常量允许实现 switch 语句作为非常高效的编译时查找表。 - juanchopanza
7
@BetaCarotin - 它检查任何情况是否解析为“true”,就像将任何内容传递给开关一样。 - adeneo
10
我可以问您相反的问题:允许在case语句中使用变量值有什么意义呢? 您的代码可以使用“if / else”结构更清晰地编写。 - milleniumbug
12
允许这种结构必然会带来一些奇妙的代码混淆。如果每个case在运行时都被评估,那么想象一下使用具有副作用的case的可能性是多么大。让我感到非常温暖。 - EOF
显示剩余11条评论
4个回答

32
C++从C发展而来,其中switch语句被构想为跳转表(分支表)。为了实现跳转表,switch条件应该是常量,这样就可以轻松地将其转换为标签。
尽管标准没有规定switch语句应该如何实现,但最重要的是case标签应该在编译时进行评估。在C和C++中,switch语句评估表达式并将控制传递到一个或多个case语句值,这些值评估为条件表达式的值。 6.4.2 switch语句 [stmt.switch]

根据条件的值,switch语句使控制转移到多个语句之一。

这种行为使它与其他支持case语句中条件的语言不同。
例如,Javascript将switch ... case语句描述为:

MDN switch

switch语句评估表达式,将表达式的值与case子句匹配,并执行与该case相关联的语句。

因此,您实际上正在尝试比较两个不同的结构,并期望行为相同。

至于回答“不允许在switch语句中使用变量值的原因?”,这会使switch .. case成为一个效率较低的结构,在每次迭代/实例中,都需要重新评估case标签以确定是否与条件表达式匹配。


1
当然,如果情况是恒定的,渲染更高效的代码是可能的。但是,是否有任何阻止编译器实现语言的扩展版本,其中表达式不必是常量?我认为这就是大多数新语言功能的起点。编译器可以在编译时决定表达式是否为常量,并在其为常量的情况下呈现更高效的代码。 - kasperd
@kasperd:需要使用switch的原因是为了实现分支表的高效实现,就像Fortran的Computed Goto一样。如果将case设置为非const,则对于一系列“if”语句来说它就是多余的。我不确定你提到的是哪种现代语言。JavaC#不支持条件case,Ruby和Python也不支持,而我们知道javascript没有做好很多事情。Crockford,你在吗? - Abhijit
@Abhijit 今天的语言设计受到与设计C时期不同的其他关注点的驱动。但是你的评论似乎在回复我在评论中写的内容之外的东西,我无法理解你在最后一条评论中想要表达什么。 - kasperd
1
@Abhijit 其他支持此功能的语言的存在并不会改变扩展C/C++以具备此功能的可能性,这是我所讨论的全部内容。声称动态类型语言无法从优化中受益是误导性的,因为它完全取决于编译器开发人员是否实现了它。生成的代码可以与两个嵌套的switch语句完全相同,其中外部语句只有常量标签和else部分包含一个内部switch语句,其中只包含动态标签。 - kasperd
@Abhijit 我不知道你为什么问其他语言。我从未说过存在其他具有此类支持的语言。然而,我脑海中首先想到的语言是BETA,它确实具有动态标签。 - kasperd
显示剩余2条评论

15

C标准(而不是C++标准)说:

C11:6.8.4.2 switch语句(p3)

每个case标签的表达式必须是整数常量表达式,且在同一switch语句中的任何两个case常量表达式在转换后的值不能相同。[...]

如果在case中允许使用表达式,则有可能存在两个表达式具有相同的值。

C++标准(而不是C标准)也是这样规定的:

C++11:6.4.2第2段:

[...] 在同一switch中,case常量不得在转换为switch条件的提升类型后具有相同的值。


1
这并没有解释为什么会是这样。 - juanchopanza
3
这解释了C/C++标准的一个精确选择!这个选择是为了避免在switch()指令中对于每个传递的值执行多个case!如果case中有变量或操作,肯定会有需要执行多个case的情况!这种行为也使得某些编译器能够更好地优化指令! - Sir Jo Black
@SergioFormiggini - 不存在“C/C++标准”。C和C++是两种不同的编程语言,有着各自独立的标准。 - Pete Becker

10

对于存在if/else覆盖所有其他情况的理由,switch/case存在的原因是提供类似于较低级别“跳转”语法的模拟,以便您可以为您的代码创建快速、静态的“跳转表”。如果允许运行时表达式,则该原因将消失。

询问为什么switch执行此操作类似于询问if执行另一操作,因此是无意义的。


9

仅仅因为你能做到某件事,并不意味着那是有意义的。特别地,switch 语句并不能等同于 if/else 语句。

if/else 语句比 switch 语句更加通用,而 switch 语句则旨在基于 switch 表达式的值来“选择要执行的操作”。

练习 - 下面这段代码会做什么?

var a = 5;
var b = 0;
switch(a)
{
    case 5: b = 1; break;
    case 5: b = 2; break;
    case 1: b = 3; break;
}

现在的b等于2还是1?如果它同时满足两个分支,那么就不会“选择一个操作”,如果只满足其中一个分支,则是任意决定。
将case值限制为常量表达式可以使编译器在此代码上发出错误。如果这些值根据运行时的值而不同,则编译器无法验证两个case具有相同的值,这是Halting问题的结果。

3
至少有人明白switch语句和一系列if/else语句在语义上是不等价的。我曾经遇到过一个人说,对于“菜单/选项选择”类代码,使用switch语句是“过早优化”,而不是if/else阶梯(?!),因为(出于所有原因中的)选项不够多,这很痛苦。+1 - Thomas
1
@Thomas,听起来他们想要进行过早的悲观主义; 在找到原因之前使事情变得糟糕。(在某些情况下用if…else替换可能更好,这是编译器应该识别的)。 - Jon Hanna
1
@Thomas:我认为C语言确实需要一种标准的方式来指定一系列选项。这可能意味着单个跳转表的效率会降低(例如,如果一个switch有七个case分别对应1..9、10..99、100..999、1000..9999等,那么一个有10000000个条目的跳转表可能不是最好的方法),但编译器仍然可以使用一些技巧,而if语句无法使用(例如,如果CPU有一条指令用于计算前导零,则编译器可以基于此生成跳转表,其中某些情况使用单个if)。 - supercat
1
@JonHanna 跳转表在这里是一个误导。跳转表是C语言中存在switch的历史原因(使编译器能够进行无法完成的优化,因为它不够聪明),但现在它基本上不相关了,就像registerinline一样——编译器变得更加智能。现在主要是关于语义。 - milleniumbug
1
@milleniumbug 没错。现在我们大多数时候使用 switch,因为它更清晰明了。即使是在具有类似结构并且会对编译器或脚本引擎行为产生影响的语言中,这也是优先选择 switch 的原因。唯一不使用 switch 的原因是为了使事情变得不那么清晰。除非您已经确定了使事情变得不清晰的原因,否则这是过早的悲观主义。 - Jon Hanna
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接