代码之家  ›  专栏  ›  技术社区  ›  Rob

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

  •  845
  • Rob  · 技术社区  · 16 年前

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

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

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

    “case”标签跳过“newval”的初始化

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

    23 回复  |  直到 16 年前
        1
  •  1027
  •   roottraveller    8 年前

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

    switch (val)
    {   
    case VAL:  
    {
      // This will work
      int newVal = 42;  
      break;
    }
    case ANOTHER_VAL:  
    ...
    break;
    }
    
        2
  •  272
  •   AnT stands with Russia    6 年前

    这个问题 最初被标记为[C]和[C++]同时。原始代码在C和C++中都是无效的,但是完全不同的无关原因。我相信这个重要的细节被现有的答案遗漏了(或混淆了)。

    • 在C++中,此代码无效,因为 case ANOTHER_VAL: 标签跳入变量范围 newVal 跳过初始化。旁路初始化本地对象的跳跃在C++中是非法的。这方面的问题是正确解决大多数答案。

    • 但是,在C语言中,绕过变量初始化并不是一个错误。在C语言中,跳过变量的初始化范围是合法的。这只意味着变量没有初始化。由于完全不同的原因,原始代码不能在C中编译。标签 case VAL: 在原始代码中附加到变量声明 纽瓦尔 . 在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++方面,它限制了 纽瓦尔 ,确保 另一种情况: 不再跳入该范围,这消除了C++问题。在C端,额外的 {} 引入复合语句,从而使 瓦迩案: 用于语句的标签,它消除了C问题。

    • 在C的情况下,如果没有 {} . 只需在 瓦迩案: 标签和代码将生效

      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的观点无效。

        3
  •  130
  •   Richard Corden    16 年前

    好啊。仅仅是澄清这一点与声明没有任何关系。它只涉及“跳过初始化”(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标签。

        4
  •  35
  •   Peter Mortensen user1284631    9 年前

    整个switch语句在同一范围内。要绕过它,请执行以下操作:

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

    注释 括号。

        5
  •  27
  •   polfosol à° _à°    8 年前

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

    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()之前声明变量,如果满足您的要求,则在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 .

        6
  •  20
  •   emk    14 年前

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

    这一点最清楚地体现在 Duff's device . 以下是维基百科的一些代码:

    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);
        }
    }
    

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

    如其他几张海报所示,您需要自己放一块:

    switch (...) {
        case FOO: {
            MyObject x(...);
            ...
            break; 
        }
        ...
     }
    
        7
  •  16
  •   Zebra North    16 年前

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

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

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

        8
  •  12
  •   Jeremy    16 年前

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

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

    但非常邪恶。

        9
  •  10
  •   Dan Shield    16 年前

    试试这个:

    switch (val)
    {
        case VAL:
        {
            int newVal = 42;
        }
        break;
    }
    
        10
  •  7
  •   Ether    15 年前

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

    switch (thing)
    { 
      case A:
      {
        int i = 0;  // Completely legal
      }
      break;
    }
    

    原因在于在堆栈上分配(和回收)空间来存储局部变量。

        11
  •  6
  •   slim    16 年前

    考虑:

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

    在缺少break语句的情况下,有时newval会被声明两次,直到运行时才知道它是否声明。我想限制是因为这种混乱。纽瓦尔的范围是什么?约定要求它是整个交换块(在大括号之间)。

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

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

    工作良好。在开关块中声明变量是可以的。在案件警卫之后声明不是。

        12
  •  4
  •   Andrew Eidsness    16 年前

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

    switch (val)  
    {  
    case VAL:
    {
      // This will work
      int newVal = 42;
      break;
    }
    case ANOTHER_VAL:  
      ...
      break;
    }
    
        13
  •  3
  •   Mike F    16 年前

    如果您的代码说“int newval=42”,那么您可以合理地期望newval永远不会未初始化。但是,如果您继续阅读这个语句(这就是您正在做的),那么这就是所发生的事情——newval在作用域中,但尚未被分配。

    如果这是您真正想要发生的事情,那么语言需要通过说“int newval;newval=42;”来明确地表达出来。否则,您可以将newval的范围限制为单个案例,这更可能是您想要的。

    如果您考虑相同的例子,但“const int newval=42;,它可能会澄清一些问题。

        14
  •  3
  •   community wiki 2 revs VictorH    7 年前

    我只是想强调一下 苗条的 point . switch构造创建了一个完整的、一流的公民范围。因此,可以在switch语句中的第一个case标签之前声明(并初始化)变量, 没有 附加支架对:

    switch (val) {  
      /* This *will* work, even in C89 */
      int newVal = 42;  
    case VAL:
      newVal = 1984; 
      break;
    case ANOTHER_VAL:  
      newVal = 2001;
      break;
    }
    
        15
  •  3
  •   Peter    15 年前

    到目前为止,答案都是针对C++的。

    对于C++,不能跳过初始化。您可以在C语言中使用。但是,在C语言中,声明不是语句,并且case标签后面必须跟着语句。

    所以,有效的(但丑陋的)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();
    }
    
        16
  •  3
  •   thkala jaxb    13 年前

    有趣的是这很好:

    switch (i)  
    {  
    case 0:  
        int j;  
        j = 7;  
        break;  
    
    case 1:  
        break;
    }
    

    …但事实并非如此:

    switch (i)  
    {  
    case 0:  
        int j = 7;  
        break;  
    
    case 1:  
        break;
    }
    

    我知道修复是足够简单的,但是我还不理解为什么第一个示例不会影响编译器。如前所述(2年前和合) 宣言 并不是导致错误的原因,即使是在逻辑上。初始化是问题所在。如果变量已初始化并在不同的行上声明,则它将编译。

        17
  •  3
  •   Community George Stocker    7 年前

    我发自内心地写这个答案是为了 this question . 但是当我完成它时,我发现答案已经关闭了。所以我把它贴在这里,也许喜欢引用标准的人会发现它很有用。

    有问题的原始代码:

    int i;
    i = 2;
    switch(i)
    {
        case 1: 
            int k;
            break;
        case 2:
            k = 1;
            cout<<k<<endl;
            break;
    }
    

    实际上有两个问题:

    1。为什么我可以在后面声明一个变量 case 标签?

    这是因为C++中的标签必须是:

    N337 61/1

    带标签的语句:

    • 属性说明符seqopt 案例 constant-expression : statement

    而在 C++ 声明声明 也被认为是 陈述 (而不是 C ):

    N33 37 6/1:

    陈述 :

    声明声明

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

    因为: N33 37 6 7/3

    可以转换成一个块, 但不是以一种通过初始化绕过声明的方式 . 一 跳跃的程序 (The 从转移 条件 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;
    
        18
  •  1
  •   Ralph Hempel    16 年前

    新变量只能在块范围内去标记。你需要这样写:

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

    当然,newval只在大括号内有作用域…

    干杯,拉尔夫

        19
  •  1
  •   Dalmas    10 年前

    switch 不是连续的 if/else if 阻碍。 我很惊讶没有其他答案能解释清楚。

    考虑这个 转换 声明:

    switch (value) {
        case 1:
            int a = 10;
            break;
        case 2:
            int a = 20;
            break;
    }
    

    这可能令人惊讶,但编译器不会将其视为简单的 如果/如果 . 它将生成以下代码:

    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 . 括号创建了一个新的作用域,现在很容易理解为什么不能在 转换 块。

    这可能看起来很奇怪,但需要支持 坠落 (也就是说,不使用 break 让执行继续到下一个 案例 )

        20
  •  0
  •   marijne    16 年前

    newval存在于开关的整个范围内,但仅在命中val分支时初始化。如果在VAL中的代码周围创建一个块,它应该是正常的。

        21
  •  0
  •   Jingguo Yao    12 年前

    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 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;
    }
    
        22
  •  -1
  •   William Keller    16 年前

    我认为目前的问题是,该语句被跳过,而您试图在其他地方使用var,它将不会被声明。

        23
  •  -1
  •   Olumide    11 年前

    似乎匿名对象 可以 在switch case语句中声明或创建,原因是它们不能被引用,因此不能进入下一个case。考虑到这个例子编译在GCC4.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;
    }