首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >开关语句的非传统用途

问开关语句的非传统用途
EN

Stack Overflow用户
提问于 2016-01-22 14:52:08
回答 4查看 180关注 0票数 3

最近,我发现开关的主体可以是任何语句(C99 6.8.4)。这个想法是第一次向我提出的:https://stackoverflow.com/a/9220598/515212

所以我们可以使用如下的开关语句

代码语言:javascript
复制
void f(int n)
{
    switch (n)
    case 0:
        printf("zero\n");
}

甚至把ifs、whiles等放进去。

代码语言:javascript
复制
void f(int n)
{
    switch (n) 
    if (1)
    {
        case 0:
            printf("zero\n");
    }
    else
        while (--n)
        {
            default:
                printf("non-zero\n");
        }
}

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

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2020-12-09 06:47:12

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

最著名的不寻常的例子是达夫的装置,它已经涵盖在对这个问题的其他答案中。

switch()的基础

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

代码语言:javascript
复制
switch (<expression>) <statement>;

哪里

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

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

case标签与具有类似语法的goto标签相似,switch()语句可以看作是一个计算的goto语句,其中<expression>的值决定switch()执行范围内的哪个标签将跳转。

case标签必须指定一个整数常量值或一个表达式,编译器可以从中创建一个积分常量值。case标签中指定的每个值在switch()范围内必须是唯一的,或者可以使用default的特殊标签来指定指定的case标签值以外的任何值。case标签的顺序并不重要。

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

因此,以下是好的switch()语句:

代码语言:javascript
复制
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()语句,但是这些案例准备在输出中使用特定案例的数据。

假设我们有一个包含外币投标的销售点的数据结构,例如,客户在巴黎的一家机场商店,那里的欧元是标准货币,但商店也接受美元,销售点将美元兑换成欧元。在收据打印中,如果客户使用欧元,则只打印欧元,如果客户使用美元,则需要打印美元金额、换算率和欧元金额。

我们有一个包含招标数据的结构,看起来类似于:

代码语言:javascript
复制
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;

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

代码语言:javascript
复制
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标签位于if的主体中,则类似的注释适用于if语句。

代码语言:javascript
复制
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标签在循环中,那么初始化就会被绕过。

代码语言:javascript
复制
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()。

代码语言:javascript
复制
// 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()循环中生成一组值,并根据值执行一些操作。这个例子在某种程度上是为了显示语法的可能性。我还没有在野外看到过这样的情况,但是在调用函数之后进行错误检查并根据错误代码进行重试是可能的。

代码语言:javascript
复制
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必须是编译时计算的整数值,所以如果编译器和大多数现代编译器将在编译时执行计算,则可以在按位运算符中使用常量。

代码语言:javascript
复制
#define VAL_1  0x0001
#define VAL_2  0x0002
#define VAL_3  0x0004
#define VAL_4  0x0008

switch (iBitMask & 0x000f) {
case VAL_1:            // only VAL_1 is set
    printf("  only VAL_1 found\n");
    break;
case VAL_1 | VAL_2:    // both and only both VAL_1 and VAL_2 are set
    printf("  both VAL_1 and VAL_2 found\n");
    break;
case VAL_1 | VAL_3:    // both and only both VAL_1 and VAL_3 are set
    printf("  both VAL_1 and VAL_3 found\n");
    break;
case 0x000f & ~VAL_1:
    printf("  everything except for VAL_1 found\n");
    break;
}

参考资料

开关语句是如何工作的?

C标准是否将真值显式地表示为0或1?

在开关语句中使用“继续”

票数 1
EN

Stack Overflow用户

发布于 2016-01-22 15:00:51

您可以将switch语句视为带有标签(大小写实际上是标签)的代码块,其中控件与goto语句一起传递。

有点像

代码语言:javascript
复制
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");
            }
    }
}

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

票数 3
EN

Stack Overflow用户

发布于 2016-01-22 15:19:18

这是有效的C代码。它起源于程序集,每个条件语句都使用if、goto和label跳转。

使用此特性实现数组复制称为达夫装置。

代码语言:javascript
复制
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替换为if和goto时

代码语言:javascript
复制
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替换为if和goto

代码语言:javascript
复制
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;
}

在功能上(几乎)相当于

代码语言:javascript
复制
void copy(char *from, char *to, int count)
{
    while(--n > 0) { 
        *to++ = *from++;
    }
}

这几乎是等价的,因为在上一次实现循环检查中执行的次数要多8倍,这对性能有什么影响。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/34949503

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档