是否使用if(0)跳过应该起作用的开关中的情况?


122

我有一种情况,我希望C ++ switch语句中的两种情况都落入第三种情况。具体而言,第二种情况将落入第三种情况,并且第一种情况也将落入第三种情况而不会通过第二种情况。

我有一个愚蠢的想法,尝试了一下,就成功了!我把第二个盒子包裹在一个if (0) {...中}。看起来像这样:

#ifdef __cplusplus
#  include <cstdio>
#else
#  include <stdio.h>
#endif

int main(void) {
    for (int i = 0; i < 3; i++) {
        printf("%d: ", i);
        switch (i) {
        case 0:
            putchar('a');
            // @fallthrough@
            if (0) {        // fall past all of case 1 (!)
        case 1:
            putchar('b');
            // @fallthrough@
            }
        case 2:
            putchar('c');
            break;
        }
        putchar('\n');
    }
    return 0;
}

运行它时,我得到所需的输出:

0: ac
1: bc
2: c

我在C和C ++(都使用clang)中都尝试过,并且做同样的事情。

我的问题是:这是有效的C / C ++吗?它应该做什么吗?


34
是的,这是有效的,并且与Duff的设备运行的原因几乎相同。
dxiv

42
请注意,这样的代码将使您摆脱只关心可读性和可维护性的环境中的任何代码演练。
安德鲁·亨利

16
这太可怕了。请注意,这比Duff的设备还要可怕。相关的,我最近也看到了类似的东西switch(x) { case A: case B: do_this(); if(x == B) also_do_that(); ... }。海事组织,这也太可怕了。拜托,只要像陈述一样写出类似的内容,即使这意味着您必须在两个地方重复一行。使用函数和变量(和文档!)来减少以后仅在一个地方意外更新的风险。
ilkkachu

50
:-)对于那些因查看该代码而受伤或致残的人,我并不是说这是个主意。实际上,我说这是一个愚蠢的主意。
马克·阿德勒

4
请注意,像这样的交换机内部构造在RAII中的效果
不佳

Answers:


58

是的,这是允许的,并且可以满足您的要求。对于switch声明,C ++标准

case和default标签本身不会改变控制流程,这种控制流程在这些标签之间继续不受阻碍。要退出开关,请参阅中断。

[注1:通常,作为开关主题的子语句是复合的,并且case和默认标签出现在(compound)子语句中包含的顶级语句上,但这不是必需的。声明可以出现在switch语句的子语句中。—尾注]

因此,在对if语句进行评估时,控制流程将根据if语句规则进行,而无需干预案例标签。


13
作为一个特定的例子,可以看一下Boost.Coroutines中的一组胃动宏,这些宏利用此规则(以及C ++的其他六种极端情况)来利用C ++ 03规则实现协程。直到C ++ 20才使它们成为语言的一部分,但是只要您先服用了抗酸剂,这些宏就可以使它们起作用。
Cort Ammon

60

是的,这应该可行。C语言中switch语句的case标签几乎完全类似于goto标签(有关它们如何与嵌套switch语句一起使用的一些注意事项)。特别是,它们本身并不会为您认为位于“案例内”的语句定义块,而是可以像使用goto一样使用它们跳入块的中间。跳转到块的中间时,与跳转有关的初始化等注意事项适用于goto。

话虽如此,实际上,使用goto语句编写此代码可能更清晰,如:

    switch (i) {
    case 0:
        putchar('a');
        goto case2;
    case 1:
        putchar('b');
        // @fallthrough@
    case2:
    case 2:
        putchar('c');
        break;
    }

1
“当跳转到块的中间时,与goto相同的警告适用于跳过变量的初始化等问题。”这些警告是什么?您不能跳过变量的初始化。
带翼的小行星

4
@AsteroidsWithWings,您是从符合标准的角度还是从编译器不允许的实际角度出发,表示“不能”?因为我的GCC确实允许它在C模式下显示警告。但是,它不允许在C ++模式下使用。
ilkkachu

5
@quetzalcoatl gcc-9和clang-6都允许使用此代码,警告可能未初始化bee(在C模式下;在C ++中,它们在“无法从switch语句跳转到这种情况下,标签/跳转绕过变量初始化”时出错)。
罗斯兰

7
您知道使用goto清洁剂解决方案时做错了什么。
康拉德·鲁道夫

9
@KonradRudolph:goto非常讨厌,但是如果您不手动制作复杂的控制结构,那么实际上没有什么令人反感的。整个“ goto被认为是有害的”是关于编写看起来像编译器发出的asm的HLL(例如C)代码(在整个地方来回跳转)。有许多像只进(概念上并不比不同的goto的良好结构的用途break或早期return),不太可能重试-环-只(避免模糊的不足nested-公用流路及补偿continue)等
R., GitHub STOP HELPING ICE

28

正如其他答案提到的那样,标准在技术上允许这样做,但是对于将来的代码阅读者来说,这是非常混乱和不清楚的。

这就是为什么switch ... case通常应使用函数调用而不是大量内联代码编写语句的原因。

switch(i) {
case 0:
    do_zero_case(); do_general_stuff(); break;
case 1:
    do_one_case(); do_general_stuff(); break;
case 2:
    do_general_stuff(); break;
default:
    do_default_not_zero_not_one_not_general_stuff(); break;
}

请注意,问题中的代码并非do_general_stuff默认设置,适用于情况2(以及0和1)。但是,它永远不会i在0..2范围之外运行开关。
彼得·科德斯

@PeterCordes-请注意,答案中的代码并非do_general_stuff默认设置,仅适用于情况2(以及0和1)。
皮特·贝克尔

一个建议-也许应该删除或重命名默认案例?错过不同的函数名称很容易。
I.A.A.Guy

@PeteBecker:哦,IDK,我是多么想念这个generaldefault是不同的话。太太,这是一个众所周知的事实,如果中间字母被打乱,人们通常仍然可以阅读一个单词。我们倾向于查看起点和终点,因此仅中间有所不同可能对可略写性并不理想。也许删除do_前缀。
彼得·科德斯

1
@supercat:“众所周知的事实”不是要用不同的字母代替中间的字母,而只是要混淆当时的顺序。“如果雅各布语中的对决是正确的,那你就应该被推翻。”
迈克尔·凯彻
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.