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

Rob*_*Rob 910 c++ switch-statement

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

switch (val)  
{  
case VAL:  
  // This won't work
  int newVal = 42;  
  break;
case ANOTHER_VAL:  
  ...
  break;
}  
Run Code Online (Sandbox Code Playgroud)

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

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

这似乎也是其他语言的限制.为什么会出现这样的问题?

TJ *_*oks 1097

Case陈述只是标签.这意味着编译器会将其解释为直接跳转到标签.在C++中,这里的问题是范围之一.您的花括号将范围定义为switch语句中的所有内容.这意味着您将留下一个范围,在该范围内将跳过初始化的代码进一步执行跳转.处理此问题的正确方法是定义特定于该case语句的作用域并在其中定义变量.

switch (val)
{   
case VAL:  
{
  // This will work
  int newVal = 42;  
  break;
}
case ANOTHER_VAL:  
...
break;
}
Run Code Online (Sandbox Code Playgroud)

  • 相对于打开一个新的范围 - 有利于代码的可读性和一致性.在过去,您可能已经自动获得了"额外"堆栈帧,但现在不应该是任何体面优化编译器的情况. (92认同)
  • workmad3 - 如果你没有声明任何新变量,你能找到任何能产生新堆栈帧的C++编译器吗?你简单地担心我,但G ++ 3.1,Visual C++ 7或Intel C++ 8都不会为你没有声明任何变量的新范围生成任何代码. (39认同)
  • 通过输入新的花括号块@ workmad3不会导致新的堆栈帧http://stackoverflow.com/questions/2759371/in-c-do-braces-act-as-a-stack-frame (10认同)
  • 我同意Jeff的观点 - 由于大多数人使用的缩进样式,在阅读switch语句时"假设"范围太容易了.我自己的风格是,如果每个案例/默认长度超过一行,则始终打开一个新范围. (9认同)
  • @TallJef我不知道你指的是什么"过去的日子".我从来没有遇到过编译器,在40年内输入方法时没有*分配方法*的所有堆栈空间. (2认同)
  • 我在 IAR 编译器中遇到了这种语句的特殊情况。case 里面有一个数组(有作用域),但是不管进入 case 都分配了内存,只要进入 function。由于其他情况导致堆栈比这更深,因此最终导致堆栈溢出。 (2认同)

AnT*_*AnT 314

这个问题最初标记为[C]和[C++]在同一时间.原始代码在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;
    }
    
    Run Code Online (Sandbox Code Playgroud)

添加额外的{}块可以修复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;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,即使它现在从C的角度来看是有效的,但从C++的角度来看它仍然是无效的.

  • 对称地,在C++案例中,问题可以很容易地解决,而不需要{}.只需从变量声明中删除初始化程序,代码就会生效

    switch (val)  
    {  
    case VAL: 
      int newVal;
      newVal = 42;  
      break;
    case ANOTHER_VAL:     /* Now it works in C++! */
      ...
      break;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,即使它现在从C++的角度来看是有效的,但从C的角度来看它仍然是无效的.

  • @ legends2k:是的,它仍然会跳过它.但是,当我说"它修复了问题"时,我的意思是它修复了*C++编译器错误*.在C++中,使用初始化程序*跳过标量声明*是违法的,但跳过没有初始化程序*的标量声明*是完全没错的.在`case ANOTHER_VAL:`点变量`newVal`是可见的,但具有不确定的值. (10认同)
  • 值得注意的是,添加空语句的修复仅适用于 C99 及以上版本。在 C89 中,变量必须在其封闭块的开头声明。 (4认同)
  • @AnT:我理解为什么修复C++的那个不适用于C; 但是,我无法理解它是如何修复首先跳过初始化的C++问题的?当它跳转到'ANOTHER_VAL`时,它是否仍会跳过`newVal`的声明和赋值? (3认同)
  • 迷人.我在阅读了K&R C(第二版)的"§A9.3:复合声明"后发现了这个问题.该条目提到了*复合语句*的技术定义,即`{declaration-list [opt] statement-list [opt]}`.困惑,因为我曾经认为声明是一个声明,我查了一下并立即找到了这个问题,这个例子表明这种差异变得明显,实际上**打破了一个程序.我相信另一个解决方案(对于C)将在声明之前放置另一个语句(可能是空语句?)*,以便满足*labeled-statement*. (3认同)

Ric*_*den 133

好.只是为了澄清这一点,严格与宣言无关.它只涉及"跳过初始化"(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;
}
Run Code Online (Sandbox Code Playgroud)

如果对象是非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;
}
Run Code Online (Sandbox Code Playgroud)

此限制不限于switch语句.跳过初始化时使用'goto'也是错误的:

goto LABEL;    // Error jumping over initialization
int j = 0; 
LABEL:
  ;
Run Code Online (Sandbox Code Playgroud)

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

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

  • @Mecki:在C++中它是非法的.ISO C++ '03 - 6.7/3:"......从具有自动存储持续时间的局部变量不在范围内的点跳到其在范围内的点的程序是不正确的,除非该变量具有POD类型(3.9)并且在没有初始化程序(8.5)的情况下声明." (9认同)
  • @Mecki:通常,单个编译器行为不一定反映语言实际允许的whtat.我检查了C'90和C'99,两个标准都包含一个在switch语句中跳过初始化的例子. (8认同)
  • "错误跳过初始化"??? 不是我的海湾合作委员会.当在标签下面使用j时,它可能会给出"j可能被用于单元化"的警告,但是没有错误.但是,在切换的情况下,存在错误(硬错误,而不是弱警告). (2认同)

Mar*_*ram 35

整个switch语句在同一范围内.要解决它,请执行以下操作:

switch (val)
{
    case VAL:
    {
        // This **will** work
        int newVal = 42;
    }
    break;

    case ANOTHER_VAL:
      ...
    break;
}
Run Code Online (Sandbox Code Playgroud)

注意括号.


Jee*_*tel 29

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

Case statements are only 'labels'
Run Code Online (Sandbox Code Playgroud)

在C中,根据规范,

§6.8.1标签声明:

labeled-statement:
    identifier : statement
    case constant-expression : statement
    default : statement
Run Code Online (Sandbox Code Playgroud)

在C中,没有任何条款允许"标记声明".它不是语言的一部分.

所以

case 1: int x=10;
        printf(" x is %d",x);
break;
Run Code Online (Sandbox Code Playgroud)

将无法编译,请参阅http://codepad.org/YiyLQTYw.海湾合作委员会发出错误:

label can only be a part of statement and declaration is not a statement
Run Code Online (Sandbox Code Playgroud)

甚至

  case 1: int x;
          x=10;
            printf(" x is %d",x);
    break;
Run Code Online (Sandbox Code Playgroud)

也没有编译,请参阅http://codepad.org/BXnRD3bu.在这里我也得到了同样的错误.


在C++中,根据规范,

允许使用带标签的声明,但不允许标记为-initialization.

http://codepad.org/ZmQ0IyDG.


这种情况的解决方案是两个

  1. 使用{}使用新范围

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

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在switch()之前声明变量,并在case语句中使用不同的值初始化它,如果它满足您的要求

    main()
    {
        int x;   // Declare before
        switch(a)
        {
        case 1: x=10;
            break;
    
        case 2: x=20;
            break;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

有关switch语句的更多内容

切勿在交换机中写入任何不属于任何标签的语句,因为它们永远不会执行:

switch(a)
{
    printf("This will never print"); // This will never executed

    case 1:
        printf(" 1");
        break;

    default:
        break;
}
Run Code Online (Sandbox Code Playgroud)

http://codepad.org/PA1quYX3.

  • 您正确地描述了C问题.但是,在C++标记的初始化中不允许的断言完全不正确.在C++中标记初始化没有任何问题.C++不允许的是*将变量`a`的初始化跳转到变量`a`的范围内.因此,从C的角度来看,问题是`case VAL:`标签,你正确地描述了它.但是从C++的角度来看,问题在于`case ANOTHER_VAL:`label. (2认同)

emk*_*emk 20

你不能这样做,因为case标签实际上只是包含块的入口点.

Duff的设备最清楚地说明了这一点.以下是维基百科的一些代码:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

注意case标签如何完全忽略块边界.是的,这是邪恶的.但这就是你的代码示例不起作用的原因.跳转到case标签与使用相同goto,因此不允许使用构造函数跳过局部变量.

正如其他几张海报所表明的那样,你需要自己设置一个块:

switch (...) {
    case FOO: {
        MyObject x(...);
        ...
        break; 
    }
    ...
 }
Run Code Online (Sandbox Code Playgroud)

  • @Chris:我同意你的意见,R夸大了影响; 我只看到你的评论并且知道一个简单而且不够. (3认同)

MrZ*_*bra 16

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

case 1:
    int x; // Works
    int y = 0; // Error, initialization is skipped by case
    break;
case 2:
    ...
Run Code Online (Sandbox Code Playgroud)

如前所述,一个很好的方法是使用大括号为您的案例创建一个范围.


Jer*_*emy 12

我最喜欢的邪恶切换技巧是使用if(0)跳过不需要的案例标签.

switch(val)
{
case 0:
// Do something
if (0) {
case 1:
// Do something else
}
case 2:
// Do something in all cases
}
Run Code Online (Sandbox Code Playgroud)

但非常邪恶.


Dan*_*eld 10

试试这个:

switch (val)
{
    case VAL:
    {
        int newVal = 42;
    }
    break;
}
Run Code Online (Sandbox Code Playgroud)


Seb*_*ose 7

如果启动新块,可以在switch语句中声明变量:

switch (thing)
{ 
  case A:
  {
    int i = 0;  // Completely legal
  }
  break;
}
Run Code Online (Sandbox Code Playgroud)

原因是在堆栈上分配(和回收)空间以存储局部变量.


sli*_*lim 6

考虑:

switch(val)
{
case VAL:
   int newVal = 42;
default:
   int newVal = 23;
}
Run Code Online (Sandbox Code Playgroud)

在没有break语句的情况下,有时newVal会被声明两次,并且你不知道它是否会在运行时之前发生.我的猜测是限制是因为这种混乱.newVal的范围是什么?公约将规定它将是整个开关块(在支架之间).

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

switch(val) {
    int x;
    case VAL:
        x=1;
}
Run Code Online (Sandbox Code Playgroud)

工作良好.在开关块内声明变量很好.在案件警卫之后宣布不是.

  • @ Mr.32:实际上你的例子显示没有执行printf,但是在这种情况下,int x不是一个语句而是一个声明,声明了x,每次函数环境堆叠时都保留它的空间,见:http://codepad.org/4E9Zuz1e (3认同)