Frage Warum können Variablen in einer switch-Anweisung nicht deklariert werden?


Ich habe mich immer gefragt, warum Sie Variablen nicht nach einer Case-Bezeichnung in einer switch-Anweisung deklarieren können. In C ++ können Sie Variablen ziemlich überall deklarieren (und es ist offensichtlich eine gute Sache, sie nahe der Erstbenutzung zu deklarieren), aber Folgendes wird immer noch nicht funktionieren:

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

Das obige gibt mir den folgenden Fehler (MSC):

Die Initialisierung von 'newVal' wird über die 'case'-Bezeichnung übersprungen

Dies scheint auch in anderen Sprachen eine Einschränkung zu sein. Warum ist das ein Problem?


788
2017-09-18 13:11


Ursprung


Antworten:


Case Aussagen sind nur Etiketten. Dies bedeutet, dass der Compiler dies als Sprung direkt zum Label interpretiert. In C ++ liegt das Problem hier im Bereich. Ihre geschweiften Klammern definieren den Bereich als alles innerhalb des switch Erklärung. Dies bedeutet, dass Sie einen Bereich haben, in dem ein Sprung weiter in den Code ausgeführt wird, wobei die Initialisierung übersprungen wird. Der richtige Weg, dies zu handhaben, ist, einen dafür spezifischen Bereich zu definieren case Aussage und definieren Sie Ihre Variable darin.

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

967
2017-09-18 13:17



Diese Frage wird gleichzeitig als [C] und [C ++] markiert. Der ursprüngliche Code ist tatsächlich sowohl in C als auch in C ++ ungültig, aber aus völlig anderen Gründen. Ich glaube, dieses wichtige Detail wurde durch die vorhandenen Antworten verfehlt (oder verschleiert).

  • In C ++ ist dieser Code ungültig, weil der case ANOTHER_VAL: Label springt in den Gültigkeitsbereich der Variablen newVal Umgehung seiner Initialisierung. Sprünge, die die Initialisierung von lokalen Objekten umgehen, sind in C ++ illegal. Diese Seite des Problems wird von den meisten Antworten korrekt behandelt.

  • In C-Sprache ist das Umgehen der Variableninitialisierung jedoch kein Fehler. Das Springen in den Gültigkeitsbereich einer Variablen über ihre Initialisierung ist in C erlaubt. Das bedeutet einfach, dass die Variable nicht initialisiert ist. Der ursprüngliche Code kompiliert nicht in C aus einem ganz anderen Grund. Etikette case VAL: im ursprünglichen Code ist an die Deklaration der Variablen angehängt newVal. In C sind Sprachdeklarationen keine Aussagen. Sie können nicht etikettiert werden. Und das verursacht den Fehler, wenn dieser Code als C-Code interpretiert wird.

    switch (val)  
    {  
    case VAL:             /* <- C error is here */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:     /* <- C++ error is here */
      ...
      break;
    }
    

Hinzufügen eines Extra {} Block behebt sowohl C ++ - als auch C-Probleme, obwohl diese Probleme sehr unterschiedlich sind. Auf der C ++ Seite beschränkt es den Umfang von newValdarauf achten, dass case ANOTHER_VAL: springt nicht mehr in diesen Bereich, wodurch das C ++ - Problem beseitigt wird. Auf der C-Seite das extra {} führt eine zusammengesetzte Aussage ein und macht so die case VAL: Label, das auf eine Anweisung angewendet wird, wodurch das C-Problem behoben wird.

  • In C - Fällen kann das Problem leicht gelöst werden ohne {}. Fügen Sie einfach eine leere Anweisung nach dem hinzu case VAL: Label und der Code wird gültig

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

    Beachten Sie, dass, obwohl es jetzt aus Sicht von C gültig ist, aus Sicht von C ++ ungültig bleibt.

  • Symmetrisch, in C ++ Fall kann das Problem ohne das Problem leicht gelöst werden {}. Entfernen Sie einfach den Initialisierer aus der Variablendeklaration und der Code wird gültig

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

    Beachten Sie, dass, obwohl es jetzt aus Sicht von C ++ gültig ist, es aus Sicht von C ungültig bleibt.


236
2017-11-07 08:12



OK. Nur um dies klarzustellen, hat das nichts mit der Erklärung zu tun. Es bezieht sich nur auf das "Überspringen der Initialisierung" (ISO C ++ '03 6.7 / 3)

Viele der Beiträge hier haben erwähnt, dass das Überspringen der Deklaration dazu führen kann, dass die Variable "nicht deklariert" wird. Das ist nicht wahr. Ein POD-Objekt kann ohne Initialisierer deklariert werden, hat jedoch einen unbestimmten Wert. Beispielsweise:

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

Wenn das Objekt ein Nicht-POD oder Aggregat ist, fügt der Compiler implizit einen Initialisierer hinzu, und daher ist es nicht möglich, über eine solche Deklaration zu springen:

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

Diese Einschränkung ist nicht auf die switch-Anweisung beschränkt. Es ist auch ein Fehler, 'goto' zu verwenden, um über eine Initialisierung zu springen:

goto LABEL;    // Error jumping over initialization
int j = 0; 
LABEL:
  ;

Ein wenig Wissen ist, dass dies ein Unterschied zwischen C ++ und C ist. In C ist es kein Fehler, über die Initialisierung zu springen.

Wie andere bereits erwähnt haben, besteht die Lösung darin, einen verschachtelten Block hinzuzufügen, so dass die Lebensdauer der Variablen auf die individuelle Fallbezeichnung beschränkt ist.


125
2017-09-18 13:54



Die gesamte switch-Anweisung ist im selben Umfang. Um es zu umgehen, tun Sie Folgendes:

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

    case ANOTHER_VAL:
      ...
    break;
}

Hinweis die Klammern.


35
2017-09-18 13:13



Nach dem Lesen aller Antworten und einigen weiteren Recherchen bekomme ich ein paar Dinge.

Case statements are only 'labels'

In C, gemäß der Spezifikation,

§6.8.1 Beschriftete Anweisungen:

labeled-statement:
    identifier : statement
    case constant-expression : statement
    default : statement

In C gibt es keine Klausel, die eine "etikettierte Deklaration" erlaubt. Es ist einfach nicht Teil der Sprache.

Damit

case 1: int x=10;
        printf(" x is %d",x);
break;

Dies wird nicht kompiliert, sehen http://codepad.org/YiyLQTYw. GCC gibt einen Fehler aus:

label can only be a part of statement and declaration is not a statement

Sogar

  case 1: int x;
          x=10;
            printf(" x is %d",x);
    break;

das ist auch nicht kompilieren, sehen http://codepad.org/BXnRD3bu. Hier bekomme ich auch den gleichen Fehler.


In C ++, gemäß der Spezifikation,

Labeled-Deklaration ist erlaubt, aber gekennzeichnet -Initialisierung ist nicht erlaubt.

Sehen http://codepad.org/ZmQ0IyDG.


Lösung für eine solche Bedingung ist zwei

  1. Verwenden Sie entweder den neuen Bereich mit {}

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
    
  2. Oder verwenden Sie eine Dummy-Anweisung mit Label

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
    
  3. Deklarieren Sie die Variable vor switch () und initialisieren Sie sie mit unterschiedlichen Werten in der case-Anweisung, wenn sie Ihre Anforderung erfüllt

    main()
    {
        int x;   // Declare before
        switch(a)
        {
        case 1: x=10;
            break;
    
        case 2: x=20;
            break;
        }
    }
    

Noch ein paar Dinge mit switch statement

Schreiben Sie niemals irgendwelche Anweisungen in den Schalter, die nicht Teil eines Etiketts sind, da sie niemals ausgeführt werden:

switch(a)
{
    printf("This will never print"); // This will never executed

    case 1:
        printf(" 1");
        break;

    default:
        break;
}

Sehen http://codepad.org/PA1quYX3.


26
2017-12-18 06:33



Du kannst das nicht tun, weil case Etiketten sind eigentlich nur Eintrittspunkte in den umschließenden Block.

Dies wird am deutlichsten veranschaulicht durch Duffs Gerät. Hier ist ein Code von 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);
    }
}

Beachten Sie, wie die case Etiketten ignorieren die Blockgrenzen vollständig. Ja, das ist böse. Aus diesem Grund funktioniert das Codebeispiel nicht. Springen zu einem case Label ist das gleiche wie die Verwendung goto, so dass es nicht erlaubt ist, mit einem Konstruktor über eine lokale Variable zu springen.

Wie einige andere Plakate angedeutet haben, müssen Sie einen eigenen Block setzen:

switch (...) {
    case FOO: {
        MyObject x(...);
        ...
        break; 
    }
    ...
 }

21
2017-09-18 13:15



Die meisten Antworten sind bisher in einer Hinsicht falsch: Sie kann deklariere Variablen nach der case-Anweisung, aber du kippen initialisiere sie:

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

Wie bereits erwähnt, ist ein guter Weg um Klammern zu verwenden, um einen Bereich für Ihren Fall zu erstellen.


16
2017-09-18 14:00



Mein liebster böser Switch-Trick besteht darin, ein if (0) zu verwenden, um ein unerwünschtes Case-Label zu überspringen.

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

Aber sehr böse.


12
2017-09-18 17:02



Versuche dies:

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

10
2017-09-18 13:14



Sie können Variablen innerhalb einer switch-Anweisung deklarieren ob Sie starten einen neuen Block:

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

Der Grund besteht darin, Speicherplatz auf dem Stapel für das Speichern der lokalen Variablen zuzuweisen (und wiederzugewinnen).


7
2017-09-18 13:15



Erwägen:

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

In Abwesenheit von Break-Anweisungen wird manchmal newVal zweimal deklariert, und Sie wissen nicht, ob es bis zur Laufzeit funktioniert. Meine Vermutung ist, dass die Beschränkung auf diese Art von Verwirrung zurückzuführen ist. Was wäre der Umfang von newVal? Konvention würde diktieren, dass es der gesamte Schalterblock (zwischen den Klammern) wäre.

Ich bin kein C ++ - Programmierer, aber in C:

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

Funktioniert gut. Das Deklarieren einer Variablen in einem Switch-Block ist in Ordnung. Deklarieren nach einem Fallschutz ist nicht.


6
2017-09-18 13:22