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

时间:2020-03-06 14:21:12  来源:igfitidea点击:

我一直想知道为什么我们不能在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年),尽管有逻辑,但声明并不是导致错误的原因。初始化是问题所在。如果在不同的行上初始化并声明了变量,则会对其进行编译。