考虑下面的switch语句:
switch( value )
{
case 1:
return 1;
default:
value++;
// fall-through
case 2:
return value * 2;
}
此代码编译,但它是有效的(=定义的行为)C90/C99?我从未见过默认情况不是最后一个情况的代码。
编辑:
正如Jon Cage和KillianDS所写的:这真的是丑陋而令人困惑的代码,我很清楚这一点。我只对通用语法(有定义吗?)和预期的输出感兴趣。
这是合理的,但相当令人讨厌。我的建议是,允许出现漏洞通常是不好的,因为它会导致一些非常混乱的意大利面条代码。
将这些情况分解成几个switch语句或更小的函数几乎肯定更好。
@Tristopia:你的例子:
Example from UCS-2 to UTF-8 conversion
r is the destination array,
wc is the input wchar_t
switch(utf8_length)
{
/* Note: code falls through cases! */
case 3: r[2] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x800;
case 2: r[1] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x0c0;
case 1: r[0] = wc;
}
(我认为)如果它是这样写的,它的意图会更清楚:
if( utf8_length >= 1 )
{
r[0] = wc;
if( utf8_length >= 2 )
{
r[1] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x0c0;
if( utf8_length == 3 )
{
r[2] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x800;
}
}
}
[edit2] @Tristopia:你的第二个例子可能是follow-through用法最清晰的例子:
for(i=0; s[i]; i++)
{
switch(s[i])
{
case '"':
case '\'':
case '\\':
d[dlen++] = '\\';
/* fall through */
default:
d[dlen++] = s[i];
}
}
..但就我个人而言,我会把评论识别分解成它自己的功能:
bool isComment(char charInQuestion)
{
bool charIsComment = false;
switch(charInQuestion)
{
case '"':
case '\'':
case '\\':
charIsComment = true;
default:
charIsComment = false;
}
return charIsComment;
}
for(i=0; s[i]; i++)
{
if( isComment(s[i]) )
{
d[dlen++] = '\\';
}
d[dlen++] = s[i];
}
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);
}
}
在一种情况下,我认为将默认情况设置在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启用。
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.
另一个例子:如果“default”是一个意外的情况,并且您希望记录错误,但也要做一些合理的事情,那么这可能很有用。示例来自我自己的一些代码:
switch (style)
{
default:
MSPUB_DEBUG_MSG(("Couldn't match dash style, using solid line.\n"));
case SOLID:
return Dash(0, RECT_DOT);
case DASH_SYS:
{
Dash ret(shapeLineWidth, dotStyle);
ret.m_dots.push_back(Dot(1, 3 * shapeLineWidth));
return ret;
}
// more cases follow
}