考虑下面的switch语句:
switch( value )
{
case 1:
return 1;
default:
value++;
// fall-through
case 2:
return value * 2;
}
此代码编译,但它是有效的(=定义的行为)C90/C99?我从未见过默认情况不是最后一个情况的代码。
编辑:
正如Jon Cage和KillianDS所写的:这真的是丑陋而令人困惑的代码,我很清楚这一点。我只对通用语法(有定义吗?)和预期的输出感兴趣。
C99标准没有明确说明这一点,但综合所有事实来看,它是完全有效的。
case和default标签等同于goto标签。参见6.8.1标记语句。特别有趣的是6.8.1.4,它启用了前面提到的达夫装置:
任何语句前都可以加上
声明标识符为的前缀
标签名称。标签本身
不改变流控制,其中
继续畅通无阻地穿过它们。
编辑:开关内的代码没有什么特别的;它是一个正常的if语句代码块,带有额外的跳转标签。这解释了跌倒行为,以及为什么休息是必要的。
6.8.4.2.7甚至给出了一个例子:
switch (expr)
{
int i = 4;
f(i);
case 0:
i=17;
/*falls through into default code */
default:
printf("%d\n", i);
}
在人工程序片段中
存在标识符为I的对象
具有自动存储期限
(在块内)但从来没有
初始化,因此如果
控制表达式有一个非零
值,对printf函数的调用
将访问一个不确定值。
类似地,对函数f的调用
无法联系上。
case常量在switch语句中必须是唯一的:
6.8.4.2.3各大小写标号的表达式为整数常数
表达和没有两种的情况
常数表达式是一样的
开关语句也应相同
转换后的值。可能有
一个交换机最多只能有一个默认标签
声明。
所有的情况都被评估,然后它跳转到默认标签,如果给定:
6.8.4.2.5 The integer promotions are performed on the controlling
expression. The constant expression in
each case label is converted to the
promoted type of the controlling
expression. If a converted value
matches that of the promoted
controlling expression, control jumps
to the statement following the matched
case label. Otherwise, if there is a
default label, control jumps to the
labeled statement. If no converted
case constant expression matches and
there is no default label, no part of
the switch body is executed.
switch语句中没有定义的顺序。你可以把这些情况看作一个命名标签,一个goto标签。与人们在这里的想法相反,在值2的情况下,默认标签不会跳转到。为了用一个经典的例子来说明,这里是Duff的设备,它是C语言中switch/case的极端代表。
send(to, from, count)
register short *to, *from;
register count;
{
register 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);
}
}
我有一个有趣的例子,把默认值放在顶部保存的程序空间。它是为Arduino Nano设计的,节省了8字节的闪存(RAM是一样的)。
供你参考,这两组代码是
#if 1 // toggle this 0 or 1
// 3138/265 bytes
uint8_t g_BuiltinLedGlowState = 0; // dropping '= 0' saves nothing
void AdvanceBuiltinLedGlow_3Ph(){
switch(++g_BuiltinLedGlowState){
default:
g_BuiltinLedGlowState = 0;
// drop through // break;
case 0: // bright
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
break;
case 1: // dim
pinMode(LED_BUILTIN, INPUT_PULLUP);
break;
case 2: // off
pinMode(LED_BUILTIN, INPUT);
break;
}
}
#elif 1
// 3146/265 bytes
uint8_t g_BuiltinLedGlowState = 0; // dropping '= 0' saves nothing
void AdvanceBuiltinLedGlow_3Ph(){
switch(++g_BuiltinLedGlowState){
case 1: // bright
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
break;
case 2: // dim
pinMode(LED_BUILTIN, INPUT_PULLUP);
break;
case 3: // off
pinMode(LED_BUILTIN, INPUT);
// drop through // break;
default:
g_BuiltinLedGlowState = 0;
break;
}
}
#endif
// the loop function runs over and over again forever
void loop() {
Serial.println(g_BuiltinLedGlowState, DEC);
AdvanceBuiltinLedGlow_3Ph();
delay(1000);
}
在一种情况下,我认为将默认情况设置在switch语句的末尾以外的地方是合适的,即在状态机中,无效状态应该重置机器并继续执行,就像它是初始状态一样。例如:
switch(widget_state)
{
default: /* Fell off the rails--reset and continue */
widget_state = WIDGET_START;
/* Fall through */
case WIDGET_START:
...
break;
case WIDGET_WHATEVER:
...
break;
}
另一种安排,如果无效状态不应该重置机器,但应该容易识别为无效状态:
switch(widget_state)
{
case WIDGET_IDLE:
widget_ready = 0;
widget_hardware_off();
break;
case WIDGET_START:
...
break;
case WIDGET_WHATEVER:
...
break;
default:
widget_state = WIDGET_INVALID_STATE;
/* Fall through */
case WIDGET_INVALID_STATE:
widget_ready = 0;
widget_hardware_off();
... do whatever else is necessary to establish a "safe" condition
}
其他地方的代码可以检查widget_state == WIDGET_INVALID_STATE,并提供任何合适的错误报告或状态重置行为。例如,状态条形码可以显示一个错误图标,在大多数非空闲状态下禁用的“启动小部件”菜单选项可以为WIDGET_INVALID_STATE和WIDGET_IDLE启用。
默认条件可以是开关中case子句可以存在的任何位置。它不需要是最后一个子句。我曾见过将默认值作为第一个子句的代码。情况2:正常执行,即使默认子句在它上面。
作为测试,我把示例代码放在一个名为test(int value){}的函数中,并运行:
printf("0=%d\n", test(0));
printf("1=%d\n", test(1));
printf("2=%d\n", test(2));
printf("3=%d\n", test(3));
printf("4=%d\n", test(4));
输出结果为:
0=2
1=1
2=4
3=8
4=10