提问



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


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


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



  case标签跳过newVal的初始化



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

最佳参考


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


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

其它参考1


这个问题同时被标记为[[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


好。只是为了澄清这一点与声明无关。它只涉及跳过初始化(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


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


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

    case ANOTHER_VAL:
      ...
    break;
}


注意括号。

其它参考4


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


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发出错误:[78]


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。在这里我也得到了同样的错误。[79]





在C ++中,根据规范


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


见http://codepad.org/ZmQ0IyDG。[80]





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



  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. [81]

其它参考5


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


Duff的设备最清楚地说明了这一点。这里有来自维基百科的一些代码:[82]


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


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


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


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

其它参考7


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


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


但非常邪恶。

其它参考8


尝试这个:


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

其它参考9


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


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


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

其它参考10


考虑:


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


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


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

其它参考12


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


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


如果你考虑相同的例子但是使用const int newVal=42;它可能会澄清一些事情。

其它参考13


我只是想强调 slim 的观点。一个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


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

其它参考15


有趣的是这很好:


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


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


有问题的原始代码:


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-expression:statement

  •   

  
  ...



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


只能在块范围内对新变量进行decalared。你需要写这样的东西:


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


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


干杯,拉尔夫

其它参考18


A switchif/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


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

其它参考20


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


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

其它参考22


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