为什么不能在switch语句中声明variables?
我一直在想这个 – 为什么你不能在switch语句的case标签之后声明variables? 在C ++中,你可以在任何地方声明variables(并声明它们接近第一次使用显然是一件好事),但下面的代码仍然不起作用:
switch (val) { case VAL: // This won't work int newVal = 42; break; case ANOTHER_VAL: ... break; }
以上给我以下错误(MSC):
'newVal'的初始化由'case'标签跳过
这似乎也是其他语言的限制。 为什么会出现这样的问题?
Case
陈述只是标签 。 这意味着编译器会将其解释为直接跳转到标签。 在C ++中,这里的问题是范围之一。 您的大括号将范围定义为switch
语句内的所有内容。 这意味着你只剩下一个范围,跳过这个代码跳过初始化。 处理这个问题的正确方法是定义一个特定于该case
语句的范围,并在其中定义您的variables。
switch (val) { case VAL: { // This will work int newVal = 42; break; } case ANOTHER_VAL: ... break; }
这个问题同时被标记为[C]和[C ++]。 原来的代码在C和C ++中都是无效的,但是完全不同的不相干的原因。 我相信这个重要的细节被现有的答案遗漏了(或混淆了)。
-
在C ++中,这个代码是无效的,因为
case ANOTHER_VAL:
label跳转到variablesnewVal
的范围,绕过了它的初始化。 跳过本地对象的初始化在C ++中是非法的。 大多数答案正确解决了这个问题的这一方面。 -
但是,在C语言中,绕过variables初始化并不是一个错误。 跳到它的variables范围初始化在C中是合法的。它只是意味着variables没有被初始化。 由于完全不同的原因,原始代码不能在C中编译。 标签
case VAL:
在原始代码中附加到variablesnewVal
的声明中。 在C语言中,声明不是语句。 他们不能被贴上标签。 当这段代码被解释为C代码时,这就是导致错误的原因。switch (val) { case VAL: /* <- C error is here */ int newVal = 42; break; case ANOTHER_VAL: /* <- C++ error is here */ ... break; }
添加额外的{}
块可以修复C ++和C两个问题,即使这些问题发生了很大的变化。 在C ++方面,它限制了newVal
的范围,确保case ANOTHER_VAL:
不再跳入该范围,从而消除了C ++问题。 在C方面,extra {}
引入了一个复合语句,从而使得case VAL:
label应用于语句,从而消除了C问题。
-
在C情况下,如果没有
{}
,问题可以很容易地解决。 只要在case VAL:
label的case VAL:
添加一个空的语句,代码就会变成有效的switch (val) { case VAL:; /* Now it works in C! */ int newVal = 42; break; case ANOTHER_VAL: ... break; }
请注意,即使从C的angular度来看它现在是有效的,但从C ++的angular度来看它仍然是无效的。
-
对称地,在C ++的情况下,这个问题可以很容易地解决没有
{}
。 只需从variables声明中删除初始化程序,代码将变为有效switch (val) { case VAL: int newVal; newVal = 42; break; case ANOTHER_VAL: /* Now it works in C++! */ ... break; }
请注意,即使从C ++的angular度来看,它现在是有效的,但从C的angular度来看,它仍然是无效的。
好。 只是为了澄清这与声明严格无关。 它只涉及“跳过初始化”(ISO C ++ '03 6.7 / 3)
这里的很多post都提到跳过声明可能导致variables“不被声明”。 这不是真的。 一个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中,跳过初始化不是一个错误。
正如其他人所提到的,解决scheme是添加一个嵌套块,以便variables的生命周期限于个别的案例标签。
整个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()之前声明variables,并在case语句中用不同的值初始化它,如果它符合你的要求的话
main() { int x; // Declare before switch(a) { case 1: x=10; break; case 2: x=20; break; } }
还有一些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
是一样的,所以你不能用一个构造函数跳过局部variables。
正如其他几个海报所表明的那样,你需要把自己的一块块:
switch (...) { case FOO: { MyObject x(...); ... break; } ... }
到目前为止,大多数答案在一个方面是错误的:你可以在case语句之后声明variables,但是你不能初始化它们:
case 1: int x; // Works int y = 0; // Error, initialization is skipped by case break; case 2: ...
正如前面提到的,一个很好的解决方法是使用花括号为你的情况创build一个范围。
我最喜欢的邪恶转换技巧是使用if(0)跳过不需要的情况标签。
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语句中声明variables:
switch (thing) { case A: { int i = 0; // Completely legal } break; }
原因是分配(和回收)堆栈上的空间来存储本地variables。
考虑:
switch(val) { case VAL: int newVal = 42; default: int newVal = 23; }
在没有break语句的情况下,有时候newVal会被声明两次,并且直到运行时才知道它是否会被使用。 我的猜测是这个限制是因为这种混乱。 newVal的范围是什么? 公约将规定它将是整个开关块(在大括号之间)。
我不是C ++程序员,但在C:
switch(val) { int x; case VAL: x=1; }
工作正常。 在开关块中声明variables是很好的。 在一个案子后面申报不是。
整个交换机部分是一个单一的声明上下文。 你不能像这样在case语句中声明一个variables。 试试这个:
switch (val) { case VAL: { // This will work int newVal = 42; break; } case ANOTHER_VAL: ... break; }
如果你的代码显示“int newVal = 42”,那么你会合理地期望newVal永远不会被初始化。 但是,如果你跳过这个声明(这是你正在做的),那么这就是发生了什么 – newVal是在范围内,但尚未分配。
如果这就是你真正想要发生的事情,那么语言需要通过说“int newVal; newVal = 42;”来使其明确。 否则,您可以将newVal的范围限制为单个案例,这更可能是您想要的。
如果你考虑同样的例子,但可以用“const int newVal = 42;”来澄清事情。
到目前为止,答案是C ++。
对于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(); }
有趣的是,这很好:
switch (i) { case 0: int j; j = 7; break; case 1: break; }
…但这不是:
switch (i) { case 0: int j = 7; break; case 1: break; }
我得到一个修复很简单,但我不明白为什么第一个例子不打扰编译器。 正如前面提到的(2年前,呵呵), 声明不是什么原因造成的错误,即使是逻辑。 初始化是问题。 如果variables被初始化并在不同的行上声明,则编译。
我为这个问题写了这个答案。 但是,当我完成它,我发现答案已经closures。 所以我把它发布在这里,也许有人喜欢引用标准会发现它有帮助。
有问题的原始代码:
int i; i = 2; switch(i) { case 1: int k; break; case 2: k = 1; cout<<k<<endl; break; }
其实有两个问题:
1.为什么我可以在标签后面声明一个variables?
这是因为在C ++标签必须在forms:
N3337 6.1 / 1
标记的语句:
…
- attribute-specifier-seqopt
case
constant-expression
:statement
…
而在C++
声明中声明也被认为是声明 (而不是C
):
N3337 6/1:
声明 :
…
声明语句
…
2.为什么我可以跳过variables声明然后使用它?
因为:N3337 6.7 / 3
可以将其转换为块, 但不能以绕过具有初始化的声明的方式 。 一个跳转的程序( 从 switch语句到case标签的转换被认为是这方面的一个跳跃 )。
从具有自动存储持续时间的variables不在范围内的点到其在范围内的点是不合格的, 除非variables具有标量types ,具有简单默认构造函数和简单析构函数的类types,则cv限定版本这些types之一或前面types之一的数组,并声明没有初始值设定项(8.5)。
由于k
是标量types的 ,并且在声明跳过点处没有被初始化,所以它的声明是可能的。 这在语义上是等同的:
goto label; int x; label: cout << x << endl;
但是,如果x
在声明点初始化,那么这是不可能的:
goto label; int x = 58; //error, jumping over declaration with initialization label: cout << x << endl;
我只是想强调苗条的一点 。 一个开关构造创build了一个完整的,一stream的公民范围。 因此,在第一个case标签之前,在switch语句中声明(并初始化)一个variables是可行的, 不需要额外的括号对:
switch (val) { /* This *will* work, even in C89 */ int newVal = 42; case VAL: newVal = 1984; break; case ANOTHER_VAL: newVal = 2001; break; }
新variables只能在块范围内进行decalared。 你需要写这样的东西:
case VAL: // This will work { int newVal = 42; } break;
当然,newVal只在括号内有范围。
干杯,拉尔夫
一个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
调用。 括号创build一个新的范围,现在很容易看到为什么你不能在一个switch
块中声明两个同名的variables。
它可能看起来很奇怪,但有必要支持fallthrough (也就是说,不要使用break
来让执行继续到下一个case
)。
newVal存在于交换机的整个范围内,但只有在命中VAL肢体时才会初始化。 如果你在VAL中的代码周围创build一个块,它应该是OK的。
C ++标准有:有可能转移到一个块,但不能绕过具有初始化的声明。 从具有自动存储持续时间的局部variables不在范围内的点跳转到在范围内的点跳转的程序是格式不正确的,除非variables具有PODtypes(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语句中声明或创build,原因是它们不能被引用,因此不能进入下一个案例。 考虑一下这个例子在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; }