为什么不能在switch语句中声明变量?
我一直想知道为什么我们不能在switch语句中的case标签之后声明变量?在C ++中,几乎可以在任何地方声明变量(并且声明它们接近首次使用显然是一件好事),但是以下操作仍然无效:
switch (val) { case VAL: // This won't work int newVal = 42; break; case ANOTHER_VAL: ... break; }
上面给了我以下错误(MSC):
initialization of 'newVal' is skipped by 'case' label
这似乎也是其他语言的限制。为什么会有这样的问题?
解决方案
整个switch语句在同一范围内。要解决此问题,请执行以下操作:
switch (val) { case VAL: { // This **will** work int 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
标签实际上只是包含块中的入口点。
Duff的设备最清楚地说明了这一点。这是Wikipedia的一些代码:
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 work int newVal = 42; break; } case ANOTHER_VAL: ... break; }
case语句只是标签。这意味着编译器会将其解释为直接跳转到标签。在C ++中,这里的问题是范围之一。大括号将范围定义为switch
语句中的所有内容。这意味着我们将拥有一个范围,在该范围内将进一步执行跳过跳过初始化的代码。处理此问题的正确方法是定义特定于该case
语句的作用域,并在其中定义变量。
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;",可能会澄清一些问题。
考虑:
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块内声明一个变量就可以了。不是在事后保卫人员声明。
新变量只能在块范围内声明。我们需要编写如下内容:
case VAL: // This will work { int newVal = 42; } break;
当然,newVal仅在括号内有作用域...
干杯,拉尔夫
好的。仅仅为了澄清这一点与声明无关。它仅与"跳过初始化"有关(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中,跳过初始化不是错误。
正如其他人提到的那样,解决方案是添加一个嵌套块,以使变量的生存期限于单个大小写标签。
到目前为止,大多数答复在一个方面都是错误的:我们可以在case语句之后声明变量,但不能初始化它们:
case 1: int x; // Works int y = 0; // Error, initialization is skipped by case break; case 2: ...
如前所述,解决此问题的一种好方法是使用花括号为案例创建一个范围。
我最喜欢的邪恶开关技巧是使用if(0)跳过不需要的大小写标签。
switch(val) { case 0: // Do something if (0) { case 1: // Do something else } case 2: // Do something in all cases }
但是非常邪恶。
我只是想强调苗条的观点。开关构造可创建一个完整的第一类公民作用域。因此,可以在第一个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 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年),尽管有逻辑,但声明并不是导致错误的原因。初始化是问题所在。如果在不同的行上初始化并声明了变量,则会对其进行编译。