题
我一直想知道这个问题 - 为什么不能在 switch 语句中的 case 标签之后声明变量?在 C++ 中,您几乎可以在任何地方声明变量(并且在接近首次使用时声明它们显然是一件好事),但以下内容仍然不起作用:
switch (val)
{
case VAL:
// This won't work
int newVal = 42;
break;
case ANOTHER_VAL:
...
break;
}
上面给出了以下错误(MSC):
'newVal' 的初始化被 'case' 标签跳过
这似乎也是其他语言的限制。为什么会出现这样的问题呢?
解决方案
Case
陈述只是 标签. 。这意味着编译器会将其解释为直接跳转到标签。在 C++ 中,这里的问题是范围之一。您的大括号将范围定义为 switch
陈述。这意味着您剩下的范围将进一步跳转到跳过初始化的代码中。处理这个问题的正确方法是定义一个特定的范围 case
语句并在其中定义变量。
switch (val)
{
case VAL:
{
// This will work
int newVal = 42;
break;
}
case ANOTHER_VAL:
...
break;
}
其他提示
这个问题 是 最初同时被标记为[C]和[C++]。原始代码确实在 C 和 C++ 中都无效,但出于完全不同的不相关原因。我相信现有的答案遗漏了(或混淆了)这个重要的细节。
在 C++ 中,此代码无效,因为
case ANOTHER_VAL:
标签跳转到变量范围newVal
绕过其初始化。绕过本地对象初始化的跳转在 C++ 中是非法的。大多数答案都正确解决了问题的这一方面。然而,在C语言中绕过变量初始化并不是一个错误。在 C 语言中,通过初始化跳转到变量的作用域是合法的。它只是意味着该变量未初始化。由于完全不同的原因,原始代码无法在 C 中编译。标签
case VAL:
原始代码中附加到变量的声明中newVal
. 。在 C 语言中,声明不是语句。它们不能被贴上标签。这就是将此代码解释为 C 代码时导致错误的原因。switch (val) { case VAL: /* <- C error is here */ int newVal = 42; break; case ANOTHER_VAL: /* <- C++ error is here */ ... break; }
添加额外的 {}
block 修复了 C++ 和 C 问题,尽管这些问题恰好非常不同。在 C++ 方面,它限制了范围 newVal
, ,确保 case ANOTHER_VAL:
不再跳转到该范围,这消除了 C++ 问题。在 C 侧,额外的 {}
引入复合语句,从而使 case VAL:
标签应用于语句,从而消除了 C 问题。
在 C 情况下,问题可以轻松解决,无需
{}
. 。只需在后面添加一个空语句即可case VAL:
标签和代码将生效switch (val) { case VAL:; /* Now it works in C! */ int newVal = 42; break; case ANOTHER_VAL: ... break; }
请注意,即使它现在从 C 的角度来看是有效的,但从 C++ 的角度来看它仍然无效。
对称地,在 C++ 情况下,问题可以轻松解决,无需
{}
. 。只需从变量声明中删除初始值设定项,代码就会变得有效switch (val) { case VAL: int newVal; newVal = 42; break; case ANOTHER_VAL: /* Now it works in C++! */ ... break; }
请注意,即使它现在从 C++ 的角度来看是有效的,但从 C 的角度来看它仍然无效。
好的。只是为了严格澄清这一点与声明无关。它仅与“跳过初始化”相关(ISO C++ '03 6.7/3)
这里的很多帖子都提到跳过声明可能会导致变量“未声明”。这不是真的。POD 对象可以在没有初始化器的情况下声明,但它将具有不确定的值。例如:
switch (i)
{
case 0:
int j; // 'j' has indeterminate value
j = 0; // 'j' initialized to 0, but this statement
// is jumped when 'i == 1'
break;
case 1:
++j; // 'j' is in scope here - but it has an indeterminate value
break;
}
如果对象是非 POD 或聚合,编译器会隐式添加初始化程序,因此不可能跳过这样的声明:
class A {
public:
A ();
};
switch (i) // Error - jumping over initialization of 'A'
{
case 0:
A j; // Compiler implicitly calls default constructor
break;
case 1:
break;
}
此限制不仅限于 switch 语句。使用“goto”跳过初始化也是一个错误:
goto LABEL; // Error jumping over initialization
int j = 0;
LABEL:
;
需要注意的是,这是 C++ 和 C 之间的区别。在C中,跳过初始化并不是错误。
正如其他人提到的,解决方案是添加一个嵌套块,以便变量的生命周期仅限于个别案例标签。
整个 switch 语句都在同一范围内。要解决这个问题,请执行以下操作:
switch (val)
{
case VAL:
{
// This **will** work
int newVal = 42;
}
break;
case ANOTHER_VAL:
...
break;
}
笔记 括号。
阅读所有答案和更多研究后,我得到了一些东西。
Case statements are only 'labels'
在C中,根据规范,
§6.8.1 标记语句:
labeled-statement:
identifier : statement
case constant-expression : statement
default : statement
在 C 中,没有任何子句允许“带标签的声明”。它只是不是语言的一部分。
所以
case 1: int x=10;
printf(" x is %d",x);
break;
这 不会编译, , 看 http://codepad.org/YiyLQTYw. 。GCC 报错:
label can only be a part of statement and declaration is not a statement
甚至
case 1: int x;
x=10;
printf(" x is %d",x);
break;
这是 也不编译, , 看 http://codepad.org/BXnRD3bu. 。这里我也遇到同样的错误。
在C++中,根据规范,
允许使用标签声明,但不允许使用标签初始化。
看 http://codepad.org/ZmQ0IyDG.
解决这种情况的方法有两个
使用 {} 来使用新范围
case 1: { int x=10; printf(" x is %d", x); } break;
或者使用带标签的虚拟语句
case 1: ; int x=10; printf(" x is %d",x); break;
在 switch() 之前声明变量,如果满足您的要求,则在 case 语句中使用不同的值对其进行初始化
main() { int x; // Declare before switch(a) { case 1: x=10; break; case 2: x=20; break; } }
switch 语句的更多内容
切勿在 switch 中写入不属于任何标签的任何语句,因为它们永远不会执行:
switch(a)
{
printf("This will never print"); // This will never executed
case 1:
printf(" 1");
break;
default:
break;
}
你不能这样做,因为 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
标签完全忽略块边界。是的,这是邪恶的。但这就是您的代码示例不起作用的原因。跳转到一个 case
标签与使用相同 goto
, ,因此不允许您使用构造函数跳过局部变量。
正如其他几位海报所指出的,您需要放入自己的块:
switch (...) {
case FOO: {
MyObject x(...);
...
break;
}
...
}
到目前为止,大多数答复在某一方面都是错误的:你 能 在 case 语句之后声明变量,但是你 不能 初始化它们:
case 1:
int x; // Works
int y = 0; // Error, initialization is skipped by case
break;
case 2:
...
如前所述,解决此问题的一个好方法是使用大括号为您的案例创建范围。
我最喜欢的邪恶 switch 技巧是使用 if(0) 跳过不需要的 case 标签。
switch(val)
{
case 0:
// Do something
if (0) {
case 1:
// Do something else
}
case 2:
// Do something in all cases
}
但非常邪恶。
尝试这个:
switch (val)
{
case VAL:
{
int newVal = 42;
}
break;
}
您可以在 switch 语句中声明变量 如果 你开始一个新的块:
switch (thing)
{
case A:
{
int i = 0; // Completely legal
}
break;
}
原因是与在堆栈上分配(和回收)空间来存储局部变量有关。
考虑:
switch(val)
{
case VAL:
int newVal = 42;
default:
int newVal = 23;
}
在没有break语句的情况下,有时newVal会被声明两次,并且直到运行时你才知道它是否声明了。我的猜测是,这种限制是由于这种混乱造成的。newVal 的范围是什么?惯例规定它将是整个开关块(在大括号之间)。
我不是 C++ 程序员,但使用 C:
switch(val) {
int x;
case VAL:
x=1;
}
工作正常。在 switch 块内声明变量就可以了。在案件警卫之后声明则不然。
switch 的整个部分是一个声明上下文。您不能在这样的 case 语句中声明变量。试试这个:
switch (val)
{
case VAL:
{
// This will work
int newVal = 42;
break;
}
case ANOTHER_VAL:
...
break;
}
如果您的代码显示“int newVal=42”,那么您可以合理地预期 newVal 永远不会未初始化。但是,如果您转到此语句(这就是您正在做的事情),那么这正是发生的情况 - newVal 在范围内但尚未分配。
如果这就是你真正想要发生的事情,那么语言需要通过说“int newVal;”来明确表示。新值 = 42;”。否则,您可以将 newVal 的范围限制为单个案例,这更有可能是您想要的。
如果您考虑相同的示例,但使用“const int newVal = 42;”,它可能会澄清事情。
我只是想强调 苗条的的 观点. 。switch 构造创建了一个完整的、一流的公民范围。因此可以在第一个 case 标签之前的 switch 语句中声明(并初始化)变量, 没有 额外的括号对:
switch (val) {
/* This *will* work, even in C89 */
int newVal = 42;
case VAL:
newVal = 1984;
break;
case ANOTHER_VAL:
newVal = 2001;
break;
}
到目前为止,答案都是针对 C++ 的。
对于 C++,您无法跳过初始化。你可以在C.然而,在 C 中,声明不是语句,case 标签后面必须跟有语句。
所以,有效(但丑陋)的 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();
}
有趣的是,这很好:
switch (i)
{
case 0:
int j;
j = 7;
break;
case 1:
break;
}
...但这不是:
switch (i)
{
case 0:
int j = 7;
break;
case 1:
break;
}
我知道修复很简单,但我还不明白为什么第一个示例不会打扰编译器。正如之前提到的(2年前呵呵), 宣言 不是导致错误的原因,即使有逻辑。初始化是问题所在。如果变量在不同的行上初始化和声明,则它可以编译。
我最初写这个答案是为了 这个问题. 。然而,当我完成它时,我发现答案已经关闭。所以我把它贴在这里,也许喜欢参考标准的人会发现它很有帮助。
有问题的原始代码:
int i;
i = 2;
switch(i)
{
case 1:
int k;
break;
case 2:
k = 1;
cout<<k<<endl;
break;
}
其实有2个问题:
1.为什么我可以在之后声明变量 case
标签?
这是因为在 C++ 中标签必须采用以下形式:
N3337 6.1/1
标记语句:
...
- 属性说明符 seqopt
case
constant-expression
:statement
...
并且在 C++
声明声明 也被认为是 陈述 (相对于 C
):
N3337 6/1:
陈述:
...
声明-声明
...
2.为什么我可以跳过变量声明然后使用它?
因为:N3337 6.7/3
可以转移到一个块中, 但不是以绕过初始化声明的方式. 。跳跃的程序( 从转移 的条件 switch 语句到 case 标签被视为跳转 在这方面。)
从具有自动存储持续时间的变量不在范围内到其在范围内的点是格式错误的 除非变量具有标量类型, ,具有微不足道的默认构造函数和琐碎的驱动器的类类型,这些类型之一的CV合格版本,或一个前面类型之一的数组,并且在没有初始化器的情况下被声明(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 VAL:
// This will work
{
int newVal = 42;
}
break;
当然,newVal仅在大括号内有效......
干杯,拉尔夫
A switch
堵塞 与一系列的不同 if/else if
块。 我很惊讶没有其他答案能清楚地解释这一点。
考虑一下这个 switch
陈述 :
switch (value) {
case 1:
int a = 10;
break;
case 2:
int a = 20;
break;
}
这可能会令人惊讶,但编译器不会将其视为简单的 if/else if
. 。它将产生以下代码:
if (value == 1)
goto label_1;
else if (value == 2)
goto label_2;
else
goto label_end;
{
label_1:
int a = 10;
goto label_end;
label_2:
int a = 20; // Already declared !
goto label_end;
}
label_end:
// The code after the switch block
这 case
语句被转换为标签,然后调用 goto
. 。括号创建了一个新的作用域,现在很容易明白为什么不能在一个变量中声明两个同名的变量。 switch
堵塞。
可能看起来很奇怪,但是必须支持 失败 (也就是说,不使用 break
让执行继续到下一个 case
).
newVal 存在于开关的整个范围内,但仅在 VAL 分支被击中时才初始化。如果您围绕 VAL 中的代码创建一个块,应该没问题。
C++ 标准有:可以转移到块中,但不能以绕过初始化声明的方式。从具有自动存储持续时间的局部变量不在范围内的点跳转到其在范围内的点的程序是格式错误的,除非该变量具有 POD 类型 (3.9) 并且在没有初始化程序的情况下声明 (8.5)。
代码来说明这个规则:
#include <iostream>
using namespace std;
class X {
public:
X()
{
cout << "constructor" << endl;
}
~X()
{
cout << "destructor" << endl;
}
};
template <class type>
void ill_formed()
{
goto lx;
ly:
type a;
lx:
goto ly;
}
template <class type>
void ok()
{
ly:
type a;
lx:
goto ly;
}
void test_class()
{
ok<X>();
// compile error
ill_formed<X>();
}
void test_scalar()
{
ok<int>();
ill_formed<int>();
}
int main(int argc, const char *argv[])
{
return 0;
}
显示初始化效果的代码:
#include <iostream>
using namespace std;
int test1()
{
int i = 0;
// There jumps fo "case 1" and "case 2"
switch(i) {
case 1:
// Compile error because of the initializer
int r = 1;
break;
case 2:
break;
};
}
void test2()
{
int i = 2;
switch(i) {
case 1:
int r;
r= 1;
break;
case 2:
cout << "r: " << r << endl;
break;
};
}
int main(int argc, const char *argv[])
{
test1();
test2();
return 0;
}
我相信当前的问题是该语句被跳过,并且您尝试在其他地方使用 var,但它不会被声明。
看来匿名对象 能 在 switch case 语句中声明或创建,因为它们无法被引用,因此不能落入下一个 case。考虑这个示例在 GCC 4.5.3 和 Visual Studio 2008 上编译(可能是合规性问题,所以专家请权衡)
#include <cstdlib>
struct Foo{};
int main()
{
int i = 42;
switch( i )
{
case 42:
Foo(); // Apparently valid
break;
default:
break;
}
return EXIT_SUCCESS;
}