c++ - statement - switch case php




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

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

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

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

'newVal'的初始化由'case'标签跳过

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


C ++ Standard具有:可以将其转换为块,但不能以绕过具有初始化的声明的方式进行。 从具有自动存储持续时间的局部变量不在范围内的点跳转到其范围内的点的程序不合格,除非变量具有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;
}

newVal存在于交换机的整个范围内,但只有在命中VAL肢体时才会初始化。 如果你在VAL中的代码周围创建一个块,它应该可以。


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

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

原因在于分配(和回收)堆栈上的空间以存储本地变量。


一个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 )。


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

到目前为止,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();
}

好。 只是为了澄清这与声明严格无关。 它只涉及“跳过初始化”(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中,跳过初始化不是错误。

正如其他人所提到的,解决方案是添加一个嵌套块,以便变量的生存期限于个别情况标签。


如果你的代码显示“int newVal = 42”,那么你会合理地期望newVal永远不会被初始化。 但是如果你跳过这个声明(这是你正在做的),那么这就是发生了什么 - newVal在范围内,但尚未分配。

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

如果你考虑同样的例子,但可以用“const int newVal = 42;”来澄清事情


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

有问题的原始代码:

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

其实有两个问题:

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

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

N3337 6.1 / 1

标记的语句:

...

  • attribute-specifier-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;

我只是想强调苗条point 。 交换机构造创建了一个完整的,头等公民范围。 因此,可以在第一个case标签前的switch语句中声明(并初始化)一个变量, 而不需要额外的括号对:

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

我相信手头的问题是,该声明被跳过了,并且您试图在其他地方使用该变量,它不会被声明。


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

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

    case ANOTHER_VAL:
      ...
    break;
}

请注意括号。


有趣的是,这很好:

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

case 1:  
    break;
}

...但这不是:

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

case 1:  
    break;
}

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


看起来,匿名对象可以在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;
}

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

  • 在C ++中,这个代码是无效的,因为case ANOTHER_VAL: label跳转到变量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: label消除了C问题。

  • 在C情况下,如果没有{} ,问题很容易解决。 在case VAL: label和代码将变为有效之后,只需添加一个空语句

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


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

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







switch-statement