web-dev-qa-db-ja.com

Switchステートメントで変数を宣言できないのはなぜですか?

私はいつもこれを疑問に思いました - なぜあなたはswitch文のcaseラベルの後に変数を宣言できないのですか? C++では、ほとんどどこにでも変数を宣言できます(そして、最初の使用に近いところで宣言するのは明らかに良いことです)が、それでもまだうまくいかないでしょう:

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

上記は私に次のエラー(MSC)を与えます:

'newVal'の初期化は 'case'ラベルによってスキップされます

これは他の言語でも制限のようです。これはなぜそんなに問題なのでしょうか。

869
Rob

Caseステートメントは ラベル のみです。これは、コンパイラがこれをラベルへの直接ジャンプとして解釈することを意味します。 C++では、ここでの問題はスコープの1つです。あなたの中括弧は、スコープをswitchステートメント内のすべてのものとして定義します。これは、初期化をスキップしてコード内にさらにジャンプする範囲があることを意味します。これを処理する正しい方法は、そのcaseステートメントに固有のスコープを定義し、その中に変数を定義することです。

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

この質問 です もともとは[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の観点からは無効のままです。

290
AnT

OK。明確にするために、これは厳密には宣言とは関係ありません。それは「初期化を飛び越える」ことだけに関係します(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では、初期化を飛び越えてもエラーではありません。

他の人が述べたように、解決策は変数の寿命が個々のケースラベルに制限されるようにネストされたブロックを追加することです。

131
Richard Corden

全体のswitchステートメントは同じ範囲内にあります。それを回避するには、これを行います。

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

    case ANOTHER_VAL:
      ...
    break;
}

括弧。

35
Mark Ingram

すべての答えといくつかのより多くの研究を読んだ後、私はいくつかのことがわかります。

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 を参照してください。


このような状況に対する解決策は2つです

  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 を参照してください。

28
Jeegar Patel

caseラベルは実際には包含ブロックへのエントリポイントにすぎないため、これはできません。

これは Duffのデバイス によって最も明確に示されています。これがWikipediaのコードです。

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; 
    }
    ...
 }
20
emk

これまでのほとんどの返信は、ある点で間違っています。あなたは、cancaseステートメントの後に変数を宣言しますが、ca n'tを初期化します。

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

前述したように、これを回避する良い方法は、中括弧を使用してケースのスコープを作成することです。

16
MrZebra

私のお気に入りの悪いスイッチトリックは、不要なケースラベルをスキップするためにif(0)を使うことです。

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

しかし、非常に悪です。

12
Jeremy

これを試して:

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

Switch文の中で変数を宣言することができます もし あなたが新しいブロックを始めるなら:

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

その理由は、ローカル変数を格納するためのスタック上のスペースの割り当て(および再利用)を行うためです。

7
Seb Rose

検討してください:

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

Breakステートメントがないと、newValが2回宣言されることがあり、実行時まで実行されるかどうかわかりません。私の推測では、この制限はこの種の混乱が原因です。 newValの範囲は何になりますか?規則は、それがスイッチブロック全体(中括弧の間)であることを要求するでしょう。

私はC++プログラマではありませんが、Cでは:

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

正常に動作します。スイッチブロック内で変数を宣言することは問題ありません。ケースガード後の宣言はしていません。

6
slim

スイッチのセクション全体は単一の宣言コンテキストです。このようなcase文で変数を宣言することはできません。代わりにこれを試してください:

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

あなたのコードが "int newVal = 42"と言っているなら、newValが初期化されていないことを合理的に期待するでしょう。しかし、あなたがこのステートメントを見直すならば(それがあなたがしていることです)、それはまさに起こることです - newValは範囲内ですが、割り当てられていません。

もしそれがあなたが実際に起こることを意図しているのであれば、言語は "int newVal; newVal = 42;"と言ってそれを明示的にすることを要求します。そうでなければ、newValの範囲を単一のケースに限定することができます。

「const int newVal = 42;」という同じ例を検討すると、内容が明確になる場合があります。

3
Mike F

私はslimpoint を強調したかっただけです。 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;
}
3
VictorH

これは結構なことです。

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)、 宣言 は論理にもかかわらず、エラーの原因となるものではありません。初期化が問題です。変数が異なる行で初期化および宣言されている場合はコンパイルされます。

3
Dan

私は この質問 のためにこの答えを書いた。しかし、私がそれを終えたとき、私は答えが閉じられていることを知りました。だから私はここにそれを投稿した、多分標準への参照を好む人はそれが役に立つと思うでしょう。

問題の元のコード:

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

実際には2つの質問があります。

1. caseラベルの後に変数を宣言できるのはなぜですか?

これは、C++ではラベルは次の形式でなければならないためです。

N3337 6.1/1

ラベル付きステートメント

...

  • 属性指定子seqoptcaseconstant-expressionstatement

...

そしてC++宣言文では({Cとは対照的に)と見なされます。

N3337 6/1:

...

宣言文

...

2.変数宣言を飛び越えて使用できるのはなぜですか?

その理由:N3337 6.7/3

ブロックに転送することは可能です ただし初期化を伴う宣言を迂回する方法ではありません 。ジャンプするプログラム( からの転送 の条件 caseラベルへのswitchステートメントはジャンプと見なされます

自動保存期間を持つ変数が有効範​​囲内にない点から有効範囲内にある点までの形式が正しくない場合変数がスカラー型でない場合 自明なデストラクタ、これらの型の1つのcv修飾版、または前述の型の1つの配列で、初期化子なしで宣言されている(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;
3
PcAF

これまでのところ、答えは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();
}
3
Peter

新しい変数は、ブロックスコープでのみ宣言できます。次のようなものを書く必要があります。

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

もちろん、newValは波括弧内にのみスコープを持ちます...

乾杯、ラルフ

1
Ralph Hempel

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ブロック内で同じ名前を持つ2つの変数を宣言できないのは、今では簡単にわかります。

奇妙に見えるかもしれませんが、 fallthrough をサポートする必要があります(つまり、実行を次のbreakに継続させるためにcaseを使用しないでください)。

1
Dalmas

newValはスイッチの有効範囲全体に存在しますが、VALリムがヒットした場合にのみ初期化されます。 VALのコードの周りにブロックを作成すれば、それは問題ないはずです。

0
marijne

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;
}
0
Jingguo Yao