为什么变量不能在Switch语句中声明?

我一直想知道为什么不能在Switch语句中的case标签之后声明变量?C++你几乎可以在任何地方声明变量(并且在第一次使用时声明它们显然是一件好事),但以下方法仍然不起作用:

switch (val){case VAL:// This won't workint newVal = 42;break;case ANOTHER_VAL:...break;}

上面给出了以下错误(MSC):

'newVal'的初始化被'case'标签跳过

这在其他语言中似乎也是一个限制。为什么会有这样的问题?

360789 次浏览

整个Switch语句都在同一个范围内。要绕过它,请这样做:

switch (val){case VAL:{// This **will** workint newVal = 42;}break;
case ANOTHER_VAL:...break;}

说明括号。

试试这个:

switch (val){case VAL:{int newVal = 42;}break;}

我相信手头的问题是语句被跳过了,你试图在其他地方使用var,它不会被声明。

您可以在启动新块时在Switch语句如果中声明变量:

switch (thing){case A:{int i = 0;  // Completely legal}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;}...}

newVal存在于开关的整个范围内,但只有在VAL分支被击中时才会初始化。如果您在VAL中围绕代码创建一个块,应该没问题。

开关的整个部分是一个声明上下文。您不能在这样的case语句中声明变量。请尝试以下操作:

switch (val){case VAL:{// This will workint newVal = 42;break;}case ANOTHER_VAL:...break;}

Case语句只是标签。这意味着编译器会将其解释为直接跳转到标签。在C++,这里的问题是作用域之一。你的花括号将作用域定义为switch语句中的所有内容。这意味着你留下了一个作用域,在其中跳过初始化的代码将进一步执行跳转。

处理这个问题的正确方法是定义一个特定于case语句的范围并在其中定义您的变量:

switch (val){case VAL:{// This will workint newVal = 42;break;}case ANOTHER_VAL:...break;}

如果你的代码说“int newVal=42”,那么你可以合理地期望newVal永远不会未初始化。但是如果你去看这个语句(这就是你正在做的),那么这正是发生的事情-newVal在范围内但没有被分配。

如果这是你真正想要发生的,那么语言需要通过说“int newVal; newVal=42;”来明确它。否则,你可以将newVal的范围限制为单一情况,这更可能是你想要的。

如果你考虑同样的例子,它可能会澄清事情,但使用“const int newVal=42;”

考虑:

switch(val){case VAL:int newVal = 42;default:int newVal = 23;}

在没有rest语句的情况下,有时newVal会被声明两次,直到运行时你才知道它是否会这样做。我的猜测是这种限制是因为这种混乱。newVal的范围是什么?惯例将规定它将是整个开关块(在大括号之间)。

我不是C++程序员,但在C中:

switch(val) {int x;case VAL:x=1;}

工作正常。在开关块内声明变量是可以的。在大小写保护之后声明不是。

新变量只能在块范围内进行十进制。你需要这样写:

case VAL:// This will work{int newVal = 42;}break;

当然,newVal只在括号内有范围…

干杯,拉尔夫

好的。只是为了澄清这与声明无关。它只与“跳过初始化”有关(ISOC++'03 6.7/3)

这里很多帖子都提到跳过声明可能会导致变量“未被声明”。这不是真的。POD对象可以在没有初始化器的情况下声明,但它的值会不确定。例如:

switch (i){case 0:int j; // 'j' has indeterminate valuej = 0; // 'j' set (not 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 valuebreak;}

如果对象是非POD或聚合,编译器会隐式添加一个初始化器,因此不可能跳过这样的声明:

class A {public:A ();};
switch (i)  // Error - jumping over initialization of 'A'{case 0:A j;   // Compiler implicitly calls default constructorbreak;case 1:break;}

此限制不仅限于Switch语句。使用'goto'跳过初始化也是错误的:

goto LABEL;    // Error jumping over initializationint j = 0;LABEL:;

一点琐事是,这是C++和C之间的区别。在C中,跳过初始化不是错误。

正如其他人提到的,解决方案是添加一个嵌套块,以便变量的生命周期仅限于单个case标签。

到目前为止,大多数回复在一个方面都是错误的:你可以在case语句之后声明变量,但你不能初始化它们:

case 1:int x; // Worksint y = 0; // Error, initialization is skipped by casebreak;case 2:...

如前所述,解决这个问题的一个好方法是使用大括号为您的案例创建一个范围。

我最喜欢的邪恶开关技巧是使用if(0)跳过不需要的case标签。

switch(val){case 0:// Do somethingif (0) {case 1:// Do something else}case 2:// Do something in all cases}

但非常邪恶。

我只是想强调苗条。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中,声明不是语句,大小写标签后面必须跟着语句。

所以,有效(但丑陋)C,无效C++

switch (something){case 1:; // Ugly hack empty statementint 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年前的呵呵),声明不是导致错误的原因,即使有逻辑。初始化是问题。如果变量被初始化并在不同的行上声明,它会编译。

在阅读了所有的答案和更多的研究之后,我得到了一些东西。

Case statements are only 'labels'

在C中,根据规范,

§6.8.1标记语句:

labeled-statement:identifier : statementcase constant-expression : statementdefault : 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. Or use dummy statement with label

    case 1: ;int x=10;printf(" x is %d",x);break;
  3. Declare the variable before switch() and initialize it with different values in case statement if it fulfills your requirement

    main(){int x;   // Declare beforeswitch(a){case 1: x=10;break;
    case 2: x=20;break;}}

Some more things with switch statement

Never write any statements in the switch which are not part of any label, because they will never executed:

switch(a){printf("This will never print"); // This will never executed
case 1:printf(" 1");break;
default:break;}

http://codepad.org/PA1quYX3

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 errorill_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 initializerint 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;}

匿名对象可以似乎是在Switch case语句中声明或创建的,因为它们不能被引用,因此不能进入下一个案例。考虑这个例子在GCC 4.5.3和Visual Studio 2008上编译(可能是一个合规性问题,所以专家请权衡)

#include <cstdlib>
struct Foo{};
int main(){int i = 42;
switch( i ){case 42:Foo();  // Apparently validbreak;
default:break;}return EXIT_SUCCESS;}

这个问题最初被同时标记为。原始代码在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;}

    添加一个额外的{}块可以修复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的角度来看它仍然是无效的。

从C23开始,C语言中的所有标签都将被解释为标记隐含空语句(N2508),即无法在C语言中的声明前面放置标签的问题将不再存在,并且上述基于;的修复将不再需要。

switch与一系列#1块不同。我很惊讶没有其他答案清楚地解释它。

考虑这个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;elsegoto 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)。

我最初为这个问题写了这个答案。但是当我完成它时,我发现答案已经关闭了。所以我把它贴在这里,也许喜欢参考标准的人会觉得它很有帮助。

有问题的原始代码:

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

标签语句:

  • 属性-说明符-seq选择caseconstant-expressionstatement

C++中,声明声明也被认为是声明(与C相反):

N3337 6/1:

声明

声明-声明

2.为什么我可以跳过变量声明然后使用它?

因为:N3337 6.7/3

可以转移到块中,但不是以绕过带有初始化的声明的方式。A跳转的程序(在这方面,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;