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

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

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

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

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


当前回答

这个问题的答案是我写的。然而,当我完成它,我发现答案已经关闭。所以我把它贴在这里,也许喜欢参考标准的人会发现它很有用。

问题的原始代码:

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

标记语句: … 属性说明符-seqopt case常量表达式:语句 …

在c++中声明语句也被认为是语句(与C相反):

N3337 6/1:

声明: ... 说明语句 ...

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

因为: N3337 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是标量类型,并且在声明时没有初始化,跳过它的声明是可能的。这在语义上是等价的:

goto label;

int x;

label:
cout << x << endl;

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

 goto label;

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

    label:
    cout << x << endl;

其他回答

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

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

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

我相信手头的问题是,这是声明被跳过,你试图在其他地方使用var,它不会被声明。

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;
}

一个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调用。括号创建了一个新的作用域,现在很容易看出为什么不能在一个开关块中声明两个具有相同名称的变量。

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

试试这个:

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