考虑以下switch
声明:
switch( value )
{
case 1:
return 1;
default:
value++;
// fall-through
case 2:
return value * 2;
}
此代码编译,但C90 / C99是否有效(=已定义的行为)?我从未见过默认案例不是最后一种情况的代码。
修改
正如 Jon Cage 和 KillianDS 所写:这是非常丑陋和令人困惑的代码,我很清楚它。我只对一般语法(它定义了吗?)和预期输出感兴趣。
答案 0 :(得分:74)
case语句和default语句可以在switch语句中以任何顺序出现。 default子句是一个可选子句,如果case语句中没有任何常量可以匹配,则匹配该子句。
好例子: -
switch(5) {
case 1:
echo "1";
break;
case 2:
default:
echo "2, default";
break;
case 3;
echo "3";
break;
}
Outputs '2,default'
非常有用,如果你希望你的案例在代码中以逻辑顺序呈现(如,不是说案例1,案例3,案例2 /默认),你的案例很长,所以你不想重复默认情况下底部的整个案例代码
答案 1 :(得分:72)
C99标准没有明确说明这一点,但将所有事实放在一起,这是完全有效的。
case
和default
标签相当于goto
标签。请参见6.8.1标记语句。特别有趣的是6.8.1.4,这使得已经提到的Duff的设备:
任何陈述都可以以a开头 声明标识符为的前缀 标签名称。标签本身就是 不改变控制的流程,这 他们继续畅通无阻。
编辑:交换机中的代码没有什么特别之处;它是if
语句中的正常代码块,带有其他跳转标签。这解释了堕落行为以及为什么break
是必要的。
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的对象存在 具有自动存储持续时间 (在街区内)但永远不会 初始化,因此如果 控制表达式具有非零值 value,对printf函数的调用 将访问不确定的值。 同样,调用函数f 无法联系到。
大小写常量在switch语句中必须是唯一的:
6.8.4.2.3每个案例标签的表达式应为整数常量 表达并没有两个案例 常数表达式相同 switch语句应具有相同的 转换后的价值。可能有 交换机中最多一个默认标签 言。
评估所有案例,然后跳转到默认标签,如果给定:
6.8.4.2.5整数提升是在控制上执行的 表达。不断表达 每个案例标签都转换为 提升控制类型 表达。如果是转换后的值 匹配晋升的 控制表情,控制跳跃 到匹配后的声明 案例标签。否则,如果有 默认标签,控制跳转到 标签声明。如果没有转换 case常量表达式匹配和 没有默认标签,没有默认标签 执行开关体。
答案 2 :(得分:42)
它在某些情况下有效且非常有用。
请考虑以下代码:
switch(poll(fds, 1, 1000000)){
default:
// here goes the normal case : some events occured
break;
case 0:
// here goes the timeout case
break;
case -1:
// some error occurred, you have to check errno
}
关键是上述代码比级联if
更具可读性和效率。您可以将默认设置放在最后,但它没有意义,因为它会将注意力集中在错误情况而不是正常情况(这里是default
情况)。
实际上,这不是一个很好的例子,在投票中你知道最多可能发生多少事件。我的真实观点是是案例,其中有一组已定义的输入值,其中存在“异常”和正常情况。如果最好将异常或正常情况放在前面是一个选择问题。
在软件领域,我想到了另一个非常常见的情况:带有一些终端值的递归。如果您可以使用开关表达它,default
将是包含递归调用和区分元素(个别情况)终端值的常用值。通常无需关注终端价值。
另一个原因是案例的顺序可能会改变编译的代码行为,这对性能很重要。大多数编译器将按照与交换机中出现的代码相同的顺序生成编译的汇编代码。这使得第一种情况与其他情况大不相同:除了第一种情况之外的所有情况都将涉及跳转并且将清空处理器流水线。你可能会理解它像分支预测器默认运行交换机中第一个出现的情况。如果一个案例比其他案件更常见,那么你有充分的理由把它作为第一个案例。
阅读评论这是原始海报在阅读Intel compiler Branch Loop reorganisation关于代码优化后提出问题的具体原因。
然后将成为代码可读性和代码性能之间的一些仲裁。可能更好地发表评论,向未来的读者解释为什么首先出现案例。
答案 3 :(得分:15)
是的,这是有效的,在某些情况下它甚至是有用的。通常,如果您不需要它,请不要这样做。
答案 4 :(得分:8)
switch语句中没有定义的顺序。您可以将案例视为命名标签,例如goto
标签。与人们在这里想到的相反,在值为2的情况下,默认标签不会跳转到。用一个经典的例子来说明,这里是Duff's device,它是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);
}
}
答案 5 :(得分:7)
我认为将一个'default'放在case语句结尾之外的某个地方是合适的一种情况是在状态机中,无效状态应该重置机器并继续进行,就好像它是初始状态一样。例如:
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启用在大多数非空闲状态下禁用的“启动窗口小部件”菜单选项。
答案 6 :(得分:6)
使用另一个示例:如果“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
}
答案 7 :(得分:5)
在某些情况下,如果要将文件转换为/从文件中读取/读取文件,请将ENUM转换为字符串或将字符串转换为枚举。
有时您需要将其中一个值设为默认值,以涵盖手动编辑文件所产生的错误。
switch(textureMode)
{
case ModeTiled:
default:
// write to a file "tiled"
break;
case ModeStretched:
// write to a file "stretched"
break;
}
答案 8 :(得分:2)
“default”条件可以是switch中可以存在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
答案 9 :(得分:1)
这是有效的,但相当讨厌。我认为允许堕落通常是不好的,因为它可能导致一些非常混乱的意大利面条代码。
将这些案例分解为几个switch语句或更小的函数几乎肯定更好。
[edit] @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:你的第二个例子可能是一个很好用于跟进的最简洁的例子:
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];
}