我一直在想这个问题——为什么不能在switch语句的case标签后面声明变量呢?在c++中,你可以在任何地方声明变量(并且在第一次使用时声明它们显然是一件好事),但以下仍然不能工作:
switch (val)
{
case VAL:
// This won't work
int newVal = 42;
break;
case ANOTHER_VAL:
...
break;
}
上面给了我以下错误(MSC):
'newVal'的初始化被'case'标签跳过
这在其他语言中似乎也是一个限制。为什么这是一个问题?
这个问题最初同时被标记为c和c++。原始代码在C和c++中都是无效的,但原因完全不同,互不相关。
In C++ this code is invalid because the case ANOTHER_VAL: label jumps into the scope of variable newVal bypassing its initialization. Jumps that bypass initialization of automatic objects are illegal in C++. This side of the issue is correctly addressed by most answers.
However, in C language bypassing variable initialization is not an error. Jumping into the scope of a variable over its initialization is legal in C. It simply means that the variable is left uninitialized. The original code does not compile in C for a completely different reason. Label case VAL: in the original code is attached to the declaration of variable newVal. In C language declarations are not statements. They cannot be labeled. And this is what causes the error when this code is interpreted as C code.
switch (val)
{
case VAL: /* <- C error is here */
int newVal = 42;
break;
case ANOTHER_VAL: /* <- C++ error is here */
...
break;
}
Adding an extra {} block fixes both C++ and C problems, even though these problems happen to be very different. On the C++ side it restricts the scope of newVal, making sure that case ANOTHER_VAL: no longer jumps into that scope, which eliminates the C++ issue. On the C side that extra {} introduces a compound statement, thus making the case VAL: label to apply to a statement, which eliminates the C issue.
In C case the problem can be easily solved without the {}. Just add an empty statement after the case VAL: label and the code will become valid
switch (val)
{
case VAL:; /* Now it works in C! */
int newVal = 42;
break;
case ANOTHER_VAL:
...
break;
}
Note that even though it is now valid from C point of view, it remains invalid from C++ point of view.
Symmetrically, in C++ case the the problem can be easily solved without the {}. Just remove the initializer from variable declaration and the code will become valid
switch (val)
{
case VAL:
int newVal;
newVal = 42;
break;
case ANOTHER_VAL: /* Now it works in C++! */
...
break;
}
Note that even though it is now valid from C++ point of view, it remains invalid from C point of view.
从C23开始,C语言中的所有标签都将被解释为标签隐含的空语句(N2508),也就是说,在C语言中不能将标签放在声明前面的问题将不再存在,并且不再需要上述基于;的修复。
这个问题的答案是我写的。然而,当我完成它,我发现答案已经关闭。所以我把它贴在这里,也许喜欢参考标准的人会发现它很有用。
问题的原始代码:
int i;
i = 2;
switch(i)
{
case 1:
int k;
break;
case 2:
k = 1;
cout<<k<<endl;
break;
}
实际上有两个问题:
1. 为什么我可以在case标签后声明一个变量?
这是因为在c++中标签必须是这样的:
N3337 6.1/1
标记语句:
…
属性说明符-seqopt case常量表达式:语句
…
在c++中声明语句也被认为是语句(与C相反):
N3337 6/1:
声明:
...
说明语句
...
2. 为什么我可以跳过变量声明,然后使用它?
因为:
N3337 6.7 / 3
It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A
program that jumps
(The transfer from the condition of a switch statement to a case label is considered a jump in this respect.)
from a point where a variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has scalar type, class type with a trivial default
constructor and a trivial destructor, a cv-qualified version of one of these types, or an array of one of the
preceding types and is declared without an initializer (8.5).
因为k是标量类型,并且在声明时没有初始化,跳过它的声明是可能的。这在语义上是等价的:
goto label;
int x;
label:
cout << x << endl;
然而,如果x在声明点初始化,这将是不可能的:
goto label;
int x = 58; //error, jumping over declaration with initialization
label:
cout << x << endl;
您不能这样做,因为case标签实际上只是包含块的入口点。
达夫的装置最清楚地说明了这一点。以下是一些来自维基百科的代码:
strcpy(char *to, char *from, size_t 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);
}
}
请注意大小写标签是如何完全忽略块边界的。是的,这是邪恶的。但这就是为什么您的代码示例不起作用。跳转到case标签与使用goto相同,因此不允许跳过带有构造函数的局部变量。
正如其他几张海报所指出的那样,你需要放入自己的方块:
switch (...) {
case FOO: {
MyObject x(...);
...
break;
}
...
}
到目前为止,答案都是c++。
对于c++,你不能跳过初始化。但是,在C语言中,声明不是语句,大小写标签后面必须跟着语句。
所以,有效(但丑陋)的C,无效的c++
switch (something)
{
case 1:; // Ugly hack empty statement
int i = 6;
do_stuff_with_i(i);
break;
case 2:
do_something();
break;
default:
get_a_life();
}
相反,在c++中,声明是一个语句,因此下面的语句是有效的c++,无效的C
switch (something)
{
case 1:
do_something();
break;
case 2:
int i = 12;
do_something_else();
}