关于C++:为什么不能在switch语句中声明变量?

关于C++:为什么不能在switch语句中声明变量?

Why can't variables be declared in a switch statement?

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

1
2
3
4
5
6
7
8
9
10
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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
switch (val)
{  
case VAL:  
{
  // This will work
  int newVal = 42;  
  break;
}
case ANOTHER_VAL:  
...
break;
}

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

  • 在C++中,这个代码是无效的,因为EDCOX1×0的标签跳过变量EDCOX1 OR 1的范围,绕过它的初始化。旁路初始化本地对象的跳跃在C++中是非法的。这方面的问题是正确解决大多数答案。

  • 但是,在C语言中,绕过变量初始化并不是一个错误。在C语言中,通过初始化跳入变量的作用域是合法的。这只意味着变量没有初始化。由于完全不同的原因,原始代码不能在C中编译。原代码中的标签case VAL:附在变量newVal的声明中。在C语言中,声明不是语句。它们不能被标记。这就是当这个代码被解释为C代码时产生错误的原因。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    switch (val)  
    {  
    case VAL:             /* <- C error is here */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:     /* <- C++ error is here */
      ...
      break;
    }

添加一个额外的EDCOX1×4块可以修复C++和C的问题,尽管这些问题发生了很大的不同。在C++方面,它限制了EDCOX1×1的范围,确保EDCOX1 OR 0不再跳入该范围,从而消除C++问题。在C端,额外的{}引入一个复合语句,从而使case VAL:标签应用于一个语句,从而消除了C问题。

  • 在C的情况下,没有{},问题很容易解决。只需在case VAL:标签后添加一个空语句,代码就会生效。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    switch (val)  
    {  
    case VAL:;            /* Now it works in C! */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:  
      ...
      break;
    }

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

  • 对称地,在C++的情况下,在不使用EDCOX1〔4〕的情况下,可以很容易地解决问题。只需从变量声明中移除初始值设定项,代码就会生效。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    switch (val)  
    {  
    case VAL:
      int newVal;
      newVal = 42;  
      break;
    case ANOTHER_VAL:     /* Now it works in C++! */
      ...
      break;
    }

    请注意,即使它现在从C++的观点来看是有效的,但它仍然从C的观点无效。


好啊。仅仅是澄清这一点与声明没有任何关系。它只涉及"跳过初始化"(ISO C++ 03’6.7/3)。

这里有很多帖子提到,跳过声明可能导致变量"未声明"。这不是真的。可以在没有初始值设定项的情况下声明pod对象,但它将具有不确定的值。例如:

1
2
3
4
5
6
7
8
9
10
11
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或聚合,编译器将隐式添加初始值设定项,因此无法跳过此类声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
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"跳过初始化也是一个错误:

1
2
3
4
goto LABEL;    // Error jumping over initialization
int j = 0;
LABEL:
  ;

一个琐事是C和C.在C中的区别,跳过初始化不是一个错误。

正如其他人提到的,解决方案是添加一个嵌套的块,这样变量的生存期就只限于单个case标签。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
switch (val)
{
    case VAL:
    {
        // This **will** work
        int newVal = 42;
    }
    break;

    case ANOTHER_VAL:
      ...
    break;
}

注意括号。


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

1
Case statements are only 'labels'

在C中,根据规范,

§6.8.1标签声明:

1
2
3
4
labeled-statement:
    identifier : statement
    case constant-expression : statement
    default : statement

在C语言中,没有任何子句允许使用"带标签的声明"。它不只是语言的一部分。

所以

1
2
3
case 1: int x=10;
        printf(" x is %d",x);
break;

这不会编译,请参阅http://codepad.org/yiylqtyw。GCC给出了一个错误:

1
label can only be a part of statement and declaration is not a statement

偶数

1
2
3
4
  case 1: int x;
          x=10;
            printf(" x is %d",x);
    break;

这也不是编译,请参阅http://codepad.org/bxnrd3bu。这里我也得到同样的错误。

在C++中,根据规范,

允许标记声明,但不允许标记-初始化。

见http://codepad.org/zmq0iydg。

这种情况的解决办法是两个

  • 使用使用新范围

    1
    2
    3
    4
    5
    6
    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
  • 或者使用带有标签的伪语句

    1
    2
    3
    4
    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
  • 在switch()之前声明变量,如果满足您的要求,则在case语句中用不同的值初始化它。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    main()
    {
        int x;   // Declare before
        switch(a)
        {
        case 1: x=10;
            break;

        case 2: x=20;
            break;
        }
    }
  • switch语句还有其他一些内容

    不要在开关中写入任何不属于任何标签的语句,因为它们永远不会执行:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    switch(a)
    {
        printf("This will never print"); // This will never executed

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

        default:
            break;
    }

    请参阅http://codepad.org/pa1kuyx3。


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

    达夫的装置最清楚地说明了这一点。以下是维基百科的一些代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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相同,因此不允许使用构造函数跳过局部变量。

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

    1
    2
    3
    4
    5
    6
    7
    8
    switch (...) {
        case FOO: {
            MyObject x(...);
            ...
            break;
        }
        ...
     }

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

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

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    switch(val)
    {
    case 0:
    // Do something
    if (0) {
    case 1:
    // Do something else
    }
    case 2:
    // Do something in all cases
    }

    但非常邪恶。


    试试这个:

    1
    2
    3
    4
    5
    6
    7
    8
    switch (val)
    {
        case VAL:
        {
            int newVal = 42;
        }
        break;
    }


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

    1
    2
    3
    4
    5
    6
    7
    8
    switch (thing)
    {
      case A:
      {
        int i = 0;  // Completely legal
      }
      break;
    }

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


    考虑:

    1
    2
    3
    4
    5
    6
    7
    switch(val)
    {
    case VAL:
       int newVal = 42;
    default:
       int newVal = 23;
    }

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

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

    1
    2
    3
    4
    5
    switch(val) {
        int x;
        case VAL:
            x=1;
    }

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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;,它可能会澄清一些问题。


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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    switch (something)
    {
      case 1:
        do_something();
        break;
      case 2:
        int i = 12;
        do_something_else();
    }


    有趣的是这很好:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    switch (i)  
    {  
    case 0:  
        int j;  
        j = 7;  
        break;  

    case 1:  
        break;
    }

    …但事实并非如此:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    switch (i)  
    {  
    case 0:  
        int j = 7;  
        break;  

    case 1:  
        break;
    }

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


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

    有问题的原始代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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

    labeled-statement:

    ...

    • attribute-specifier-seqopt case constant-expression : statement

    ...

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

    N33 37 6/1:

    statement:

    ...

    declaration-statement

    ...

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

    因为:N33 37 6 7/3

    It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A
    program that jumps
    (The transfer from the condition of a switch statement to a case label is considered a jump in this respect.)

    from a point where a variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has scalar type, class type with a trivial default
    constructor and a trivial destructor, a cv-qualified version of one of these types, or an array of one of the
    preceding types and is declared without an initializer (8.5).

    由于k是标量类型,并且在声明点未初始化,因此可以跳过它的声明。这在语义上是等价的:

    1
    2
    3
    4
    5
    6
    goto label;

    int x;

    label:
    cout << x << endl;

    但是,如果在声明点初始化x,这是不可能的:

    1
    2
    3
    4
    5
    6
     goto label;

        int x = 58; //error, jumping over declaration with initialization

        label:
        cout << x << endl;

    我只是想强调斯利姆的观点。switch构造创建了一个完整的、一流的公民范围。因此,可以在switch语句中的第一个case标签之前声明(并初始化)一个变量,而不需要额外的括号对:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    switch (val) {  
      /* This *will* work, even in C89 */
      int newVal = 42;  
    case VAL:
      newVal = 1984;
      break;
    case ANOTHER_VAL:  
      newVal = 2001;
      break;
    }

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

    1
    2
    3
    4
    5
    6
    case VAL:  
      // This will work
      {
      int newVal = 42;  
      }
      break;

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

    干杯,拉尔夫


    switch区块与if/else if区块的序列不同。我很惊讶没有其他答案能解释清楚。

    考虑一下这一switch声明:

    1
    2
    3
    4
    5
    6
    7
    8
    switch (value) {
        case 1:
            int a = 10;
            break;
        case 2:
            int a = 20;
            break;
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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调用。括号创建了一个新的作用域,现在很容易理解为什么不能在一个switch块中声明两个同名的变量。

    这可能看起来很奇怪,但有必要支持Fallthrough(也就是说,不使用break让执行继续到下一个case)。


    C++标准有:可以转换到块中,但不能以通过初始化绕过声明的方式进行。一种程序,从具有自动存储时间的局部变量不在作用域内的点跳到它在作用域内的点是不正确的,除非该变量具有pod类型(3.9),并且声明时没有初始值设定项(8.5)。

    用于说明此规则的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    #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;
    }

    显示初始值设定项效果的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    #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;
    }

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


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


    匿名对象似乎可以在switch case语句中声明或创建,原因是它们不能被引用,因此不能进入下一个案例。考虑到这个例子编译在GCC4.5.3和Visual Studio 2008上(可能是一个合规性问题,所以专家们请参与进来)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include <cstdlib>

    struct Foo{};

    int main()
    {
        int i = 42;

        switch( i )
        {
        case 42:
            Foo();  // Apparently valid
            break;

        default:
            break;
        }
        return EXIT_SUCCESS;
    }


    推荐阅读