1049

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

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

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

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

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

4

23 回答 23

1268

Case语句只是标签。这意味着编译器会将其解释为直接跳转到标签。在 C++ 中,这里的问题是范围之一。您的大括号将范围定义为switch语句中的所有内容。这意味着您将留下一个范围,在该范围内将进一步执行跳转到跳过初始化的代码。

处理此问题的正确方法是定义特定于该case语句的范围并在其中定义变量:

switch (val)
{   
case VAL:  
{
  // This will work
  int newVal = 42;  
  break;
}
case ANOTHER_VAL:  
...
break;
}
于 2008-09-18T13:17:14.533 回答
401

这个问题最初被同时标记为。原始代码确实在 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;
      }
    

    添加一个额外的{}块可以解决 C++ 和 C 问题,即使这些问题恰好有很大不同。在 C++ 方面,它限制了 的范围newVal,确保case ANOTHER_VAL:不再跳转到该范围,从而消除了 C++ 问题。在 C 方面,extra{}引入了复合语句,从而使case VAL:标签适用于语句,从而消除了 C 问题。

  • 在 C 情况下,没有{}. 只需在标签后添加一个空语句,case VAL:代码就会生效

      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 的角度来看它仍然是无效的。

于 2013-11-07T08:12:16.270 回答
138

行。只是为了澄清这与声明无关。它仅与“跳过初始化”有关(ISO C++ '03 6.7/3)

这里的很多帖子都提到,跳过声明可能会导致变量“未声明”。这不是真的。可以在没有初始化程序的情况下声明 POD 对象,但它将具有不确定的值。例如:

switch (i)
{
   case 0:
     int j; // 'j' has indeterminate value
     j = 0; // 'j' set (not 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 中,跳过初始化不是错误。

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

于 2008-09-18T13:54:04.447 回答
41

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

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

    case ANOTHER_VAL:
      ...
    break;
}

注意括号。

于 2008-09-18T13:13:46.523 回答
33

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

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 中写任何不属于任何标签的语句,因为它们永远不会执行:

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

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

    default:
        break;
}

请参阅http://codepad.org/PA1quYX3

于 2011-12-18T06:33:17.943 回答
20

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

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

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; 
    }
    ...
 }
于 2008-09-18T13:15:23.587 回答
16

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

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

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

于 2008-09-18T14:00:31.840 回答
12

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

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

但是非常邪恶。

于 2008-09-18T17:02:47.783 回答
10

试试这个:

switch (val)
{
    case VAL:
    {
        int newVal = 42;
    }
    break;
}
于 2008-09-18T13:14:33.073 回答
7

如果您开始一个新块,您可以在 switch 语句中声明变量:

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

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

于 2008-09-18T13:15:17.660 回答
6

考虑:

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

在没有 break 语句的情况下,有时 newVal 会被声明两次,直到运行时你才知道它是否如此。我的猜测是限制是因为这种混乱。newVal 的范围是什么?约定将规定它将是整个 switch 块(在大括号之间)。

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

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

工作正常。在 switch 块内声明一个变量是可以的。在案件警卫之后声明不是。

于 2008-09-18T13:22:09.483 回答
4

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

switch (val)  
{  
case VAL:
{
  // This will work
  int newVal = 42;
  break;
}
case ANOTHER_VAL:  
  ...
  break;
}
于 2008-09-18T13:16:24.427 回答
3

如果您的代码显示“int newVal=42”,那么您可以合理地预期 newVal 永远不会未初始化。但是,如果您跳过此语句(这就是您正在做的事情),那么这正是发生的事情 - newVal 在范围内但尚未分配。

如果这就是你真正想要发生的事情,那么语言需要通过说“int newVal; newVal = 42;”来使其明确。否则,您可以将 newVal 的范围限制为单一情况,这更有可能是您想要的。

如果您考虑相同的示例但使用“const int newVal = 42;”,它可能会澄清事情

于 2008-09-18T13:21:36.537 回答
3

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

switch (val) {  
  /* This *will* work, even in C89 */
  int newVal = 42;  
case VAL:
  newVal = 1984; 
  break;
case ANOTHER_VAL:  
  newVal = 2001;
  break;
}
于 2008-10-17T14:31:26.943 回答
3

到目前为止,答案都是针对 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();
}
于 2009-09-02T15:08:23.113 回答
3

有趣的是,这很好:

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

case 1:  
    break;
}

...但这不是:

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

case 1:  
    break;
}

我知道修复很简单,但我还不明白为什么第一个示例不会打扰编译器。正如前面提到的(2 年前呵呵),声明不是导致错误的原因,即使有逻辑。初始化是问题。如果变量被初始化并在不同的行上声明,它就会编译。

于 2010-09-06T21:19:43.920 回答
3

我最初为这个问题写了这个答案。但是,当我完成它时,我发现答案已关闭。所以我把它贴在这里,也许喜欢参考标准的人会觉得它很有帮助。

有问题的原始代码:

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

其实有2个问题:

1、为什么我可以在caselabel后面声明一个变量?

这是因为在 C++ 中,标签必须采用以下形式:

N3337 6.1/1

标记语句:

...

  • 属性说明符 seqopt case constant-expressionstatement

...

并且在C++ 声明中声明也被认为是声明(而不是C):

N3337 6/1:

声明

...

声明-声明

...

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

因为:N3337 6.7/3

可以转移到一个块中,但不能以一种绕过初始化声明的方式。跳转的程序(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;
于 2016-05-21T22:17:37.487 回答
2

一个switch与一系列if/else if块不同。我很惊讶没有其他答案清楚地解释它。

考虑这个switch声明:

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

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

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(即不使用breakto let execution continue to next case)。

于 2015-01-01T18:17:08.520 回答
1

新变量只能在块范围内声明。你需要写这样的东西:

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

当然, newVal 仅在大括号内具有范围......

干杯,拉尔夫

于 2008-09-18T13:24:17.183 回答
0

我相信手头的问题是该语句被跳过,并且您尝试在其他地方使用 var ,它不会被声明。

于 2008-09-18T13:15:16.800 回答
0

newVal 存在于开关的整个范围内,但仅在 VAL 分支被命中时才被初始化。如果您围绕 VAL 中的代码创建一个块,则应该没问题。

于 2008-09-18T13:15:25.647 回答
0

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;
}
于 2012-07-10T06:48:19.453 回答
0

似乎可以在 switch case 语句中声明或创建匿名对象因为它们不能被引用,因此不能落入下一个案例。考虑这个示例在 GCC 4.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;
}
于 2013-02-08T10:55:29.563 回答