为什么不能在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


解决这种情况是两个

  1. 使用{}使用新的作用域

     case 1: { int x=10; printf(" x is %d", x); } break; 
  2. 或者使用带有标签的虚拟语句

     case 1: ; int x=10; printf(" x is %d",x); break; 
  3. 在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; } 

参见http://codepad.org/PA1quYX3

你不能这样做,因为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-expressionstatement

而在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只在括号内有范围。

干杯,拉尔夫

一个switchif/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; }