用 if 和 goto 模拟 switch

2023/06/30 PHP

很多编程语言都会有switch语句。switch作为选择语句的一种,也是可以用if-else语句来表示。在实现了switch的大部分编程语言中,通常跟break跳转语句配合来进行控制,其一般语法如下:

switch (expression) {
    case constant1:
        statements1;
        break;
    case constant2:
        statements2;
        break;
    .
    .
    .
    default:
        defaultStatements;
}

switch语句的执行过程:首先计算expression的值,接着跟constant1进行比较,如果相等,则执行statements1直至遇到break语句,当遇到break语句时,也就意味着switch语句结束;如果没有遇到break语句,则会一直往下执行statements2defaultStatements直至switch结束。

如果expression的值不等于constant1,则跟constant2进行比较,后续跟以上过程一样。

最后,如果expression的值跟所有的constant都不相等,如果存在default标签,程序就会执行default标签后的defaultStatements;否则结束。

以下是phpswitch示例:

<?php

$expression = 'a';
switch ($expression) {
    case 'a':
        echo "expression == 'a'\n";
        break;
    case 'b':
        echo "expression == 'b'\n";
        break;
    case 'c':
        echo "expression == 'c'\n";
        break;
    default:
        echo "expression do not match any cases\n";
}

break语句的作用是什么?跳转到switch语句的结束位置,我们可以用goto来模拟;至于case 'a'default这些,看起来有点熟悉啊,这不就跟goto语句的标签参数一样吗?行动起来,改写吧。

为了让goto语句可以结束掉switch语句,我们得先给switch语句搞两个标签:SWITCH_BEGINSWITCH_END。至于case 'a'等等,就用CASE_A_BEGINCASE_A_END等对应的一组标签代替,defaultDEFAULT_LABEL代替。

<?php

$expression = 'a';
SWITCH_BEGIN:
CASE_A_BEGIN:
    goto SWITCH_END;
CASE_A_END:
CASE_B_BEGIN:
    goto SWITCH_END;
CASE_B_END:
CASE_C_BEGIN:
    goto SWITCH_END;
CASE_C_END:
DEFAULT_LABEL:
SWITCH_END:

现在看起来是不是像模像样了?条件比较在哪里?别着急,马上安排。

<?php

$expression = 'a';
SWITCH_BEGIN:
CASE_A_BEGIN:
    if ($expression == 'a') {
        echo "expression == 'a'\n";
    }
    goto SWITCH_END;
CASE_A_END:
CASE_B_BEGIN:
    if ($expression == 'b') {
        echo "expression == 'b'\n";
    }
    goto SWITCH_END;
CASE_B_END:
CASE_C_BEGIN:
    if ($expression == 'c') {
        echo "expression == 'c'\n";
    }
    goto SWITCH_END;
CASE_C_END:
DEFAULT_LABEL:
    echo "expression do not match any constant\n";
SWITCH_END:

现在每个标签都加了if用于判断expressionconstant的值是否相等。这样就行了吧?不对,我们还忘了switch语句很重要的特性,就是expressionconstant如果相等,并且没有遇到break语句,就会一直执行剩余部分。也就是说,假如CASE_A中,if ($expression == 'a')true,而且没有goto语句的话,CASE_B中的语句也要被执行,这里还要先对if ($expression == 'b')判断,显然是不对的。因此,我们需要一种可以绕过该判断的机制。

如果绕过该机制,则说明前面有CASE标签中的语句已经被执行过(也就是说进入过其它标签),我们可以用一个标记来记录进入CASE标签的状态,接下来加个$open标记,顺便将条件判断修改一下,让它看起来跟switch尽可能相似。

<?php

$expression = 'a';

SWITCH_BEGIN:
    $open = false;
CASE_A_BEGIN:
    if ($expression != 'a' && !$open) { goto CASE_A_END; } else { $open = true; }

    echo "expression == 'a'\n";
    goto SWITCH_END; // break;
CASE_A_END:
CASE_B_BEGIN:
    if ($expression != 'b' && !$open) { goto CASE_B_END; } else { $open = true; }

    echo "expression == 'b'\n";
    goto SWITCH_END; // break;
CASE_B_END:
CASE_C_BEGIN:
    if ($expression != 'c' && !$open) { goto CASE_C_END; } else { $open = true; }

    echo "expression == 'c'\n";
    goto SWITCH_END; // break;
CASE_C_END:
DEFAULT_LABEL:
    echo "expression do not match any constant\n";
SWITCH_END:

至此,模拟代码已经完整。

嗯嗯,看起来就很麻烦,而且如果实际应用于项目上,还得考虑标签唯一性,$open也得特供,还是用switch语句吧,本文仅用于辅助理解switch的控制逻辑。

可能switch的逻辑对初学者不太好理解,而且通常情况下都要加break来中断语句,所以Go语言直接将break给省略了,如果要继续执行后面标签中的语句,需要额外加fallthrough

哈哈哈,不知道为啥,我从这简单的模拟代码中看到了编译原理的影子,学久了看啥都觉得像它。

Search

    Table of Contents