1049

私はいつもこれを疑問に思っていました - switch ステートメントの case ラベルの後に変数を宣言できないのはなぜですか? C++ では、ほとんどどこでも変数を宣言できます (そして、変数を最初に使用する直前に宣言することは明らかに良いことです) が、以下はまだ機能しません:

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

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

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

これは、他の言語でも制限のようです。なぜこれがこのような問題なのですか?

4

23 に答える 23

1268

Caseステートメントは単なるラベルです。これは、コンパイラがこれをラベルへの直接ジャンプとして解釈することを意味します。C++ では、ここでの問題はスコープの 1 つです。中かっこは、スコープをswitchステートメント内のすべてとして定義します。これは、初期化をスキップしてさらにコードにジャンプするスコープが残っていることを意味します。

これを処理する正しい方法は、そのステートメントに固有のスコープを定義し、そのcase中で変数を定義することです。

switch (val)
{   
case VAL:  
{
  // This will work
  int newVal = 42;  
  break;
}
case ANOTHER_VAL:  
...
break;
}
于 2008-09-18T13:17:14.533 に答える
401

この質問は、もともとのタグが同時に付けられていました。元のコードは C と C++ の両方で確かに無効ですが、まったく別の無関係な理由があります。

  • C++ では、ラベルがその初期化をバイパスしてcase ANOTHER_VAL:variable のスコープにジャンプするため、このコードは無効です。newValC++ では、自動オブジェクトの初期化をバイパスするジャンプは不正です。問題のこちら側は、ほとんどの回答で正しく対処されています。

  • ただし、C 言語では、変数の初期化をバイパスしてもエラーにはなりません。初期化後に変数のスコープにジャンプすることは、C では合法です。これは単に、変数が初期化されないままであることを意味します。元のコードは、まったく別の理由で C でコンパイルされません。case VAL:variable の宣言には、元のコードでのラベルが付けられます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++ 側では、 のスコープを制限し、 がそのスコープにジャンプしないようにして、C++ の問題を解消しますnewValcase ANOTHER_VAL:C 側では、エクストラ{}が複合ステートメントを導入するため、case VAL:ラベルをステートメントに適用するようになり、C の問題が解消されます。

  • C の場合、問題は .xml なしで簡単に解決できます{}case VAL:ラベルの後に空のステートメントを追加するだけで、コードが有効になります

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

    C の観点からは有効になりましたが、C++ の観点からは無効のままであることに注意してください。

  • 対称的に、C++ の場合、問題は .xml なしで簡単に解決できます{}。変数宣言から初期化子を削除するだけで、コードが有効になります

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

    C++ の観点からは有効ですが、C の観点からは無効のままであることに注意してください。

于 2013-11-07T08:12:16.270 に答える
138

Ok。これを厳密に明確にするために、宣言とは何の関係もありません。「初期化を飛び越える」ことにのみ関連します(ISO C++ '03 6.7/3)

ここの投稿の多くは、宣言を飛び越えると変数が「宣言されていない」という結果になる可能性があると述べています。本当じゃない。POD オブジェクトは初期化子なしで宣言できますが、値は不定になります。例えば:

switch (i)
{
   case 0:
     int j; // 'j' has indeterminate value
     j = 0; // 'j' set (not 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 では、初期化を飛び越してもエラーにはなりません。

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

于 2008-09-18T13:54:04.447 に答える
41

switch ステートメント全体が同じスコープ内にあります。これを回避するには、次のようにします。

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

    case ANOTHER_VAL:
      ...
    break;
}

括弧に注意してください。

于 2008-09-18T13:13:46.523 に答える
20

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; 
    }
    ...
 }
于 2008-09-18T13:15:23.587 に答える
16

これまでの回答のほとんどは、1 つの点で間違っています。caseステートメントの後に変数を宣言することはできますが、それらを初期化することはできません。

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

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

于 2008-09-18T14:00:31.840 に答える
12

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

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

しかし、非常に邪悪です。

于 2008-09-18T17:02:47.783 に答える
10

これを試して:

switch (val)
{
    case VAL:
    {
        int newVal = 42;
    }
    break;
}
于 2008-09-18T13:14:33.073 に答える
7

新しいブロックを開始する場合、switch ステートメント内で変数を宣言できます。

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

その理由は、ローカル変数を格納するためのスタック上の領域の割り当て (および再利用) に関係しています。

于 2008-09-18T13:15:17.660 に答える
6

検討:

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

正常に動作します。switch ブロック内で変数を宣言しても問題ありません。ケースガードの後の宣言はそうではありません。

于 2008-09-18T13:22:09.483 に答える
4

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

switch (val)  
{  
case VAL:
{
  // This will work
  int newVal = 42;
  break;
}
case ANOTHER_VAL:  
  ...
  break;
}
于 2008-09-18T13:16:24.427 に答える
3

コードに「int newVal=42」と表示されている場合は、newVal が初期化されていないことが当然期待できます。しかし、このステートメント (あなたが行っていること) に取り掛かると、まさにそれが起こります - newVal はスコープ内にありますが、割り当てられていません。

それが本当に意図したことである場合、言語は「int newVal; newVal = 42;」と言って明示的にする必要があります。それ以外の場合は、 newVal のスコープを単一のケースに制限できます。これは、必要な可能性が高いです。

同じ例を検討すると、「const int newVal = 42;」を使用して明確になる場合があります。

于 2008-09-18T13:21:36.537 に答える
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();
}
于 2009-09-02T15:08:23.113 に答える
3

私はスリムポイントを強調したかっただけです。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;
}
于 2008-10-17T14:31:26.943 に答える
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)、ロジックにもかかわらず、宣言はエラーの原因ではありません。初期化が問題です。変数が初期化され、別の行で宣言されている場合、コンパイルされます。

于 2010-09-06T21:19:43.920 に答える
2

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ステートメントはラベルに変換され、 で呼び出されますgotoswitch角かっこは新しいスコープを作成し、ブロック内で同じ名前の 2 つの変数を宣言できない理由は簡単にわかります。

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

于 2015-01-01T18:17:08.520 に答える
1

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

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

もちろん、newVal の有効範囲は波括弧内のみです...

乾杯、ラルフ

于 2008-09-18T13:24:17.183 に答える
0

匿名オブジェクトは、参照できないため、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;
}
于 2013-02-08T10:55:29.563 に答える
0

当面の問題は、ステートメントがスキップされ、他の場所で var を使用しようとした場合、宣言されないことだと思います。

于 2008-09-18T13:15:16.800 に答える
0

newVal はスイッチのスコープ全体に存在しますが、VAL リムがヒットした場合にのみ初期化されます。VAL 内のコードの周りにブロックを作成すれば問題ありません。

于 2008-09-18T13:15:25.647 に答える
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;
}
于 2012-07-10T06:48:19.453 に答える