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

时间:2008-09-18 13:11:55

标签: c++ switch-statement

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

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

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

  标签

跳过'newVal'的初始化

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

23 个答案:

答案 0 :(得分:1061)

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

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

答案 1 :(得分:295)

此问题 最初同时被标记为[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;
    }
    

添加额外的{}块可以解决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;
    }
    

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

  • 对称地,在C ++案例中,可以在没有{}的情况下轻松解决问题。只需从变量声明中删除初始化程序,代码就会生效

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

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

答案 2 :(得分:131)

确定。只是为了澄清这一点,严格与宣言无关。它仅涉及“跳过初始化”(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中,跳过初始化并不是错误。

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

答案 3 :(得分:35)

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

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

    case ANOTHER_VAL:
      ...
    break;
}

注意括号。

答案 4 :(得分:28)

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

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 ++中,根据规范,

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

请参阅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;
        }
    }
    

  4. 使用switch语句进行更多操作

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

    switch(a)
    {
        printf("This will never print"); // This will never executed
    
        case 1:
            printf(" 1");
            break;
    
        default:
            break;
    }
    

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

答案 5 :(得分:20)

您不能这样做,因为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);
    }
}

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

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

switch (...) {
    case FOO: {
        MyObject x(...);
        ...
        break; 
    }
    ...
 }

答案 6 :(得分:16)

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

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

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

答案 7 :(得分:12)

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

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

但非常邪恶。

答案 8 :(得分:10)

试试这个:

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

答案 9 :(得分:7)

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

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

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

答案 10 :(得分:6)

考虑:

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

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

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

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

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

答案 11 :(得分:4)

交换机的整个部分是单个声明上下文。你不能在case语句中声明一个变量。试试这个:

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

答案 12 :(得分:3)

如果你的代码说“int newVal = 42”那么你会合理地期望newVal永远不会被初始化。但是如果你转到这个陈述(这就是你正在做的事情)那么这正是发生的事情 - newVal是范围内但尚未分配。

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

如果你考虑相同的例子但是使用“const int newVal = 42;”

,它可能会澄清一些事情。

答案 13 :(得分:3)

我只是想强调苗条point。 switch结构创建了一个完整的,一流的公民范围。因此,可以在第一个案例标签之前在switch语句中声明(并初始化)一个变量,不带一个额外的括号对:

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

答案 14 :(得分:3)

到目前为止,答案都是针对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();
}

答案 15 :(得分: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年前hehe),即使有逻辑,声明也不是导致错误的原因。初始化是个问题。如果变量在不同的行上初始化并声明,则编译。

答案 16 :(得分:3)

我为this question撰写了这个答案。但是当我完成它时,我发现答案已经关闭。所以我在这里发布,也许有人喜欢参考标准会发现它有用。

有问题的原始代码:

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

实际上有两个问题:

<强> 1。为什么我可以在case标签后声明变量?

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

N3337 6.1 / 1

  

标记的语句:

     

...

     
      
  • attribute-specifier-seqopt case constant-expressionstatement
  •   
     

...

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

N3337 6/1:

  

语句

     

...

     

声明陈述

     

...

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

由于: N3337 6.7 / 3

  

可以转移到块,但不能绕过初始化声明。一个   跳跃的程序   (在这方面,转换语句转移到案例标签的条件被视为跳转。)

     

从具有自动存储持续时间的变量不在范围内的点到其在范围内的点是不正确的 ,除非该变量具有标量类型,具有普通默认值的类类型   构造函数和一个普通的析构函数,这些类型之一的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;

答案 17 :(得分:1)

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

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

当然,newVal只有大括号内的范围......

干杯,拉尔夫

答案 18 :(得分:1)

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 (即,不使用break让执行继续到下一个case)。

答案 19 :(得分:0)

newVal存在于开关的整个范围内,但仅在VAL肢体被击中时才初始化。如果你在VAL中的代码周围创建一个块,它应该没问题。

答案 20 :(得分: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;
}

答案 21 :(得分:-1)

似乎可以在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;
}

答案 22 :(得分:-1)

我认为手头的问题是语句被跳过了,你试图在其他地方使用var,它不会被声明。