switch语句的非传统用法

时间:2016-01-22 14:52:09

标签: c switch-statement

最近我发现开关的主体可以是任何声明(C99 6.8.4)。这个想法最初是由我建议的: https://stackoverflow.com/a/9220598/515212

所以可以有像

这样的开关语句
void f(int n)
{
    switch (n)
    case 0:
        printf("zero\n");
}

甚至放if s,while s等

void f(int n)
{
    switch (n) 
    if (1)
    {
        case 0:
            printf("zero\n");
    }
    else
        while (--n)
        {
            default:
                printf("non-zero\n");
        }
}

出于兴趣,我想知道这种语法是否有一些用法,或者只是关于如何在标准中定义switch语句的工件?

4 个答案:

答案 0 :(得分:3)

您可以将switch语句视为带有标签的代码块(case(s)确实是标签),其中控件通过goto语句传递。

这样的东西
void f(int n)
{
    if ( n == 0 ) goto Label_case_0;
    else goto Label_default;

    {
        if ( 1 )
        {
            Label_case_0:
            printf("zero\n");
        }
        else 
            while (--n)
            {
                Label_default:
                printf("non-zero\n");
            }
    }
}

在我看来,将案例标签放在其他控制结构中不是一个好主意,因为这会使代码难以阅读并导致错误。

答案 1 :(得分:2)

这是有效的C代码。 它起源于汇编,其中每个条件语句都使用if,goto和label跳转。

使用此功能实现阵列复制称为Duff's Device

void copy(char *from, char *to, int count)
{
    int n = (count + 7) / 8;

    switch(count % 8) {
        case 0: do { *to++ = *from++;
        case 7:      *to++ = *from++;
        case 6:      *to++ = *from++;
        case 5:      *to++ = *from++;
        case 4:      *to++ = *from++;
        case 3:      *to++ = *from++;
        case 2:      *to++ = *from++;
        case 1:      *to++ = *from++;
                   } while(--n > 0);
    }
}

while替换为ifgoto

void copy(char *from, char *to, int count)
{
    int n = (count + 7) / 8;

    switch(count % 8) {
        case 0: 
        loop:        *to++ = *from++;
        case 7:      *to++ = *from++;
        case 6:      *to++ = *from++;
        case 5:      *to++ = *from++;
        case 4:      *to++ = *from++;
        case 3:      *to++ = *from++;
        case 2:      *to++ = *from++;
        case 1:      *to++ = *from++;
        if(--n > 0) goto loop;
    }
}

然后将switch替换为ifgoto

void copy(char *from, char *to, int count)
{
    int n = (count + 7) / 8;

    if(count%8==7)goto case7;
    if(count%8==6)goto case6;
    if(count%8==5)goto case5;
    if(count%8==4)goto case4;
    if(count%8==3)goto case3;
    if(count%8==2)goto case2;
    if(count%8==1)goto case1;
    if(count%8==0)goto case0; // this can be omitted

    case0:                    // this can be omitted
    loop:        *to++ = *from++;
    case7:       *to++ = *from++;
    case6:       *to++ = *from++;
    case5:       *to++ = *from++;
    case4:       *to++ = *from++;
    case3:       *to++ = *from++;
    case2:       *to++ = *from++;
    case1:       *to++ = *from++;

    if(--n > 0) goto loop;
}

在功能上(几乎)相当于

void copy(char *from, char *to, int count)
{
    while(--n > 0) { 
        *to++ = *from++;
    }
}

它几乎是等效的,因为在上一次实现中,循环检查的执行次数是常规的8倍,这会对性能产生影响。

答案 2 :(得分:1)

您可以查看here,例如switch语句的异常使用。但是不要在真实的代码中这样做。 链接示例:

int duffs_device(char *from, char *to, int count)
{
    {
        int n = (count + 7) / 8;

        switch(count % 8) {
            case 0: do { *to++ = *from++;
                        case 7: *to++ = *from++;
                        case 6: *to++ = *from++;
                        case 5: *to++ = *from++;
                        case 4: *to++ = *from++;
                        case 3: *to++ = *from++;
                        case 2: *to++ = *from++;
                        case 1: *to++ = *from++;
                    } while(--n > 0);
        }
    }

    return count;
}

它被称为Duff's device。此函数复制char数组。它使用了称为"循环展开的技巧"。

长循环可能很慢,因为每次迭代都需要做额外的工作,如比较和变量增量。因此加速它们的一种方法是复制重复代码。就像它在例子中所做的那样。

但是现代编译器可以更好地做到这一点,并且不建议使用这样的代码,因为它只会让阅读它的人感到困惑。

答案 3 :(得分:1)

以下是我今晚在玩的switch()case的一些不寻常用法,目的是看看当我遇到这个问题时它会延伸多远。其中大多数来自各种SO帖子和答案。

最著名的不寻常示例是达夫的装置,该问题的其他答案已经涵盖了该装置。

switch() 的基本知识

switch()语句的基本形式为:

switch (<expression>) <statement>;

其中

  • <expression>是一个计算结果为整数值的表达式
  • <statement>是带有标签的语句或复合语句

<statement>是用分号结尾的单个语句,或者是用花括号括起来的一系列语句(复合语句)。如果常量值等于case中指定的<expression>的值,则指定常量值的可选switch()标签可用作跳转目标。

case标签类似于goto标签,语法相似,并且switch()语句可以认为是计算的goto语句,其中{ {1}}确定执行<expression>范围内的哪个标签。

switch()标签必须指定一个整数常量值或一个表达式,编译器可以根据该表达式创建一个整数常量值。 case标签中指定的每个值在case范围内必须唯一,否则switch()的特殊标签可用于指定除指定的default以外的任何值。标签值。 case标签的顺序无关紧要。

不需要指定casecase标签,但是default不会执行switch(i) return 0;,因为没有标签可以跳转到。另一方面,无论变量return的值是什么,switch(i) default: return 0;都将始终返回。

以下是不错的i语句:

switch()

switch (i) case 1: case 2: return i; switch (i) { case 3: i++; break; case 1: // FALL THROUGH case 2: return i; default: i -= ((i < 10) ? 2 : 5); break; } // an enum is an integral type so its possible to use them with switch() typedef enum { TYPE_1 = 0, TYPE_2, TYPE_3 } MyTypes; MyTypes jjkk = TYPE_1; switch (jjkk) { case TYPE_1: doType1(22); break; case TYPE_2: doType2(33); break; } // you can use #define constants so long as they evaluate to an integral valued constant // the Preprocessor will do the text substitution for you. #define JKJK1 1 #define JKJK2 2 switch (i) case JKJK1: case JKJK2: printf("JKJK1 or JKJK2\n"); // following use of logical express should transform logical true/false result // into an integral value of 1 (true) or 0 (false). Expect a warning about // this usage though. switch (i < 4) case 1: i *= 2; // logical true evaluated as integral value of 1 用法示例

第一个示例使用switch(),以便多个案例使用相同的do {} while(0)语句,但是案例准备要在输出中使用的案例特定数据。

假设我们在销售点中有一个数据结构,其中包含外币招标,例如客户在巴黎的一家机场商店里,欧元是标准货币,但商店也接受美元,并且销售点将美元转换为欧元。在收据打印中,如果客户使用的是欧元,而客户使用的是美元,那么我们只想打印欧元,那么我们要打印美元金额,汇率和欧元金额。

我们有一个包含招标数据的结构,如下所示:

printf()

在打印逻辑中,我们必须确定如何格式化此招标的收据行。是的,我知道还有其他更易读的方法可以做到这一点。

typedef struct {
    unsigned char   uchMajorCode;
    unsigned char   uchMinorCode;       // indicates which tender
    long    lTenderAmt;         // amount of tender in Euros
    long    lRate;              // conversion rate
    long    lForeignCurrency;   // amount of the foreign currency
    // .... other data
} ItemTender;

注意:请注意,使用switch (pItem->uchMinorCode) { char *mnemonic; case FOREIGN_1: do { mnemonic = getMnemonic(TRN_FOREIGN_1); break; // break from do {} while() case FOREIGN_2: mnemonic = getMnemonic(TRN_FOREIGN_2); break; // break from do {} while() case FOREIGN_3: mnemonic = getMnemonic(TRN_FOREIGN_3); break; // break from do {} while() } while(0); printf ("%s\t%ld %ld @ %ld\n", mnemonic, pItem->lTenderAmt, pItem->lForeignCurrency, pItem->lRate); break; // break from switch() case LOCAL: // FALL THROUGH default: printf ("%s\t%ld\n", getMnemonic(TRN_LOCAL), pItem->lTenderAmt); break; // break from switch() } 跳转到循环中会绕过带有某些循环结构(例如switch())的任何循环初始化表达式。当跳入循环主体时,循环条件检查表达式也将被绕过。如果for()标签位于if的正文中,则类似的注释适用于case语句。

if

,或者如果switch (i) { default: if (iVal > 3) { // when i is not equal to 5 then iVal is checked case 5: iVal++; // when i equals 5 then iVal is not checked } } 标签位于循环内,则使用for()循环,则绕过初始化。

case

下一个不寻常的用法是测试一系列值,并对这些值执行一些步骤。这使用上面相同的数据结构。如果我们使用花括号,我们也可以有多于一行。如果没有花括号,则第一个终止分号也将终止switch (i) { default: for (iVal= 0; iVal< 4; iVal++) { // i not equal to 5 so iVal is initialized case 5: doSomething (iVal); // i equals 5 so iVal is not initialized } break; }

switch()

第三种异常用法是在// in the case of a foreign tender perform the currency conversion switch (pItem->uchMinorCode) case FOREIGN_1: case FOREIGN_2: case FOREIGN_3: pItem->lTenderAmt = pItem->lForeignCurrency * pItem->lRate; 循环内生成一组值,并根据该值执行某些操作。此示例在某种程度上旨在展示语法的可能性。我没有在野外看到这样的东西,但是在调用一个函数并根据错误代码进行重试之后可能会进行错误检查。

while()

作为第四个示例,我们使用while ((iVal = f(iVal))) { // get next value in a sequence and do something with it. switch (iVal) { int j; // define temporary variable used below. case 1: continue; // continue the while() to get next value case 2: break; // break from switch() to do printf() below case 0: do { j = 17; break; // break from do() default: j = 12; break; // break from do() } while (0); // bottom of the do(). print follows printf(" do while - iVal = %d, j = %d\n", iVal, j); break; // break from switch() to do printf() below } printf(" while - iVal = %d\n", iVal); break; // break from while() loop } 语句测试各种位。由于switch()必须是在编译时计算的整数值,因此,如果编译器(大多数现代的编译器会在编译时执行计算),则可以将常量与按位运算符一起使用。

case

参考

How does switch statement work?

Does the C standard explicitly indicate truth value as 0 or 1?

Using continue in a switch statement