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


945

Ich habe mich immer gefragt: Warum können Sie Variablen nach einer Fallbezeichnung in einer switch-Anweisung nicht deklarieren? In C ++ können Sie Variablen praktisch überall deklarieren (und es ist natürlich eine gute Sache, sie kurz vor der ersten Verwendung zu deklarieren), aber Folgendes funktioniert immer noch nicht:

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 vom 'case'-Label übersprungen

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


10
Für eine Erklärung auf der C BNF - Grammatik basiert, siehe stackoverflow.com/questions/1180550/weird-switch-error-in-obj-c/...
Johne

Hier ist eine wirklich gute Lektüre über switch-Anweisungen und Labels (ABC :) im Allgemeinen.
Etherealone

4
Ich würde sagen, warum Variablen nicht in einer switch-Anweisung initialisiert werden können, anstatt deklariert zu werden. Da ich nur die Variable deklariere, gebe ich nur eine Warnung in MSVC.
ZoomIn

Antworten:


1143

CaseAnweisungen sind nur Bezeichnungen . Dies bedeutet, dass der Compiler dies als einen Sprung direkt zum Label interpretiert. In C ++ liegt das Problem hier im Bereich. Ihre geschweiften Klammern definieren den Bereich als alles in der switchAnweisung. 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, besteht darin, einen für diese caseAnweisung spezifischen Bereich zu definieren und Ihre Variable darin zu definieren:

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

94
Beziehen Sie sich auf die Eröffnung eines neuen Bereichs - bevorzugen Sie die Lesbarkeit und Konsistenz des Codes. Früher haben Sie vielleicht automatisch einen "zusätzlichen" Stack-Frame erhalten, aber jetzt sollte dies bei keinem anständigen Optimierungs-Compiler der Fall sein.
Tall Jeff

10
Ich stimme Jeff zu - es ist allzu einfach, beim Lesen einer switch-Anweisung den Umfang zu "übernehmen", da die meisten Leute einen Einrückungsstil verwenden. Mein eigener Stil ist es, immer einen neuen Bereich für jeden Fall / Standard zu öffnen, wenn er mehr als eine Zeile lang ist.
Gebote

39
workmad3 - Können Sie mir überhaupt einen C ++ - Compiler finden, der einen neuen Stapelrahmen generiert, wenn Sie keine neuen Variablen deklarieren? Sie haben mich kurz beunruhigt, aber keines von G ++ 3.1, Visual C ++ 7 oder Intel C ++ 8 generiert Code für neue Bereiche, in denen Sie keine Variablen deklarieren.
Chris Jefferson

10
@ workmad3 durch Eingabe eines neuen geschweiften Klammernblocks verursacht keinen neuen Stapelrahmen stackoverflow.com/questions/2759371/…
MTVS

3
@TallJef Ich weiß nicht, auf welche "alten Zeiten" Sie sich beziehen. Ich bin in 40 Jahren noch nie auf einen Compiler gestoßen, bei dem der gesamte Stapelspeicher für eine Methode bei der Eingabe der Methode nicht zugewiesen wurde.
Marquis von Lorne

333

Diese Frage ist ursprünglich als [C] und [C ++] zur gleichen Zeit markiert. Der ursprüngliche Code ist zwar sowohl in C als auch in C ++ ungültig, jedoch aus völlig anderen, nicht verwandten Gründen.

  • In C ++ ist dieser Code ungültig, da das case ANOTHER_VAL:Label unter newValUmgehung seiner Initialisierung in den Bereich der Variablen springt . Sprünge, die die Initialisierung automatischer Objekte umgehen, sind in C ++ unzulässig. Diese Seite des Problems wird von den meisten Antworten korrekt angesprochen.

  • In der C-Sprache ist das Umgehen der Variableninitialisierung jedoch kein Fehler. Das Springen in den Bereich einer Variablen über ihre Initialisierung ist in C legal. Dies bedeutet einfach, dass die Variable nicht initialisiert bleibt. Der ursprüngliche Code wird aus einem ganz anderen Grund nicht in C kompiliert. Die Beschriftung case VAL:im Originalcode ist an die Deklaration der Variablen angehängt newVal. In der Sprache C sind Deklarationen keine Aussagen. Sie können nicht beschriftet werden. Und genau 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;
    }

Das Hinzufügen eines zusätzlichen {}Blocks behebt sowohl C ++ - als auch C-Probleme, obwohl diese Probleme sehr unterschiedlich sind. Auf der C ++ - Seite wird der Umfang von eingeschränkt newVal, und es wird sichergestellt, dass case ANOTHER_VAL:nicht mehr in diesen Bereich gesprungen wird, wodurch das C ++ - Problem beseitigt wird. Auf der C-Seite führt dieses Extra {}eine zusammengesetzte Anweisung ein, wodurch das case VAL:Label auf eine Anweisung angewendet wird, wodurch das C-Problem beseitigt wird.

  • In C Fall kann das Problem leicht ohne die gelöst werden {}. Fügen Sie einfach eine leere Anweisung nach dem case VAL:Etikett hinzu 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 es aus C ++ -Sicht ungültig bleibt, obwohl es jetzt aus C-Sicht gültig ist.

  • Symmetrisch kann in C ++ das Problem ohne das 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 es, obwohl es jetzt aus C ++ - Sicht gültig ist, aus C-Sicht ungültig bleibt.


4
@AnT: Ich verstehe, warum derjenige, der C ++ behebt, für C nicht anwendbar ist; Ich kann jedoch nicht verstehen, wie das C ++ - Problem des Überspringens der Initialisierung behoben wird. Würde es nicht immer noch die Deklaration und Zuweisung überspringen, newValwann es springt ANOTHER_VAL?
Legends2k

13
@ legends2k: Ja, es wird immer noch übersprungen. Wenn ich jedoch sage "es behebt das Problem", meine ich, dass es den C ++ - Compilerfehler behebt . In C ++ ist es illegal, eine Skalardeklaration mit dem Initialisierer zu überspringen , aber es ist vollkommen in Ordnung, eine Skalardeklaration ohne Initialisierer zu überspringen . Am case ANOTHER_VAL:Punkt ist die Variable newValsichtbar, jedoch mit unbestimmtem Wert.
Am

3
Faszinierend. Ich fand diese Frage nach dem Lesen §A9.3: Compound Statementvon K & R C (zweite Ausgabe). Der Eintrag erwähnte die technische Definition einer zusammengesetzten Aussage, die ist {declaration-list[opt] statement-list[opt]}. Verwirrt, weil ich gedacht hatte, eine Erklärung sei eine Aussage, habe ich sie nachgeschlagen und sofort diese Frage gefunden, ein Beispiel, bei dem diese Ungleichheit offensichtlich wird und tatsächlich ein Programm bricht . Ich glaube, eine andere Lösung (für C) wäre, eine andere Aussage (möglicherweise eine Null-Aussage?) Vor die Deklaration zu setzen, damit die beschriftete Aussage erfüllt ist.
Braden Best

Hoppla, ich habe gerade bemerkt, dass die von mir vorgeschlagene Null-Statement-Lösung bereits in Ihrer Antwort enthalten ist. Egal Dann.
Braden Best

3
Es ist erwähnenswert, dass das Hinzufügen einer leeren Anweisung nur ab C99 funktioniert. In C89 müssen Variablen am Anfang ihres umschließenden Blocks deklariert werden.
Arthur Tacca

136

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

In vielen Beiträgen wurde 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. Zum Beispiel:

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 kein POD oder Aggregat ist, fügt der Compiler implizit einen Initialisierer hinzu, sodass es nicht möglich ist, ü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 bisschen Trivia ist, dass dies ein Unterschied zwischen C ++ und C ist. In C ist es kein Fehler, über die Initialisierung zu springen.

Wie bereits erwähnt, besteht die Lösung darin, einen verschachtelten Block hinzuzufügen, sodass die Lebensdauer der Variablen auf die Einzelfallbezeichnung beschränkt ist.


2
"Fehler beim Überspringen der Initialisierung" ??? Nicht bei meinem GCC. Bei Verwendung von j unter dem Etikett wird möglicherweise die Warnung "j kann einheitlich verwendet werden" ausgegeben, es liegt jedoch kein Fehler vor. Im Falle eines Wechsels liegt jedoch ein Fehler vor (ein schwerer Fehler, keine schwache Warnung).
Mecki

9
@Mecki: Es ist in C ++ illegal. ISO C ++ '03 - 6.7 / 3: "... Ein Programm, das von einem Punkt, an dem sich eine lokale Variable mit automatischer Speicherdauer nicht im Gültigkeitsbereich befindet, zu einem Punkt springt, an dem sie sich im Gültigkeitsbereich befindet, ist fehlerhaft, es sei denn, die Variable hat den POD-Typ (3.9) und wird ohne Initialisierer (8.5) deklariert. "
Richard Corden

1
Ja, aber es ist in C nicht illegal (zumindest sagt gcc, dass es nicht illegal ist). j wird nicht initialisiert (hat eine Zufallszahl), aber der Compiler kompiliert sie. Im Falle der switch-Anweisung wird der Compiler sie jedoch nicht einmal kompilieren, und ich sehe keinen Unterschied zwischen einem goto / label-Fall und einem switch-Fall.
Mecki

8
@Mecki: Im Allgemeinen spiegelt ein einzelnes Compilerverhalten nicht unbedingt wider, was die Sprache tatsächlich zulässt. Ich habe sowohl C'90 als auch C'99 überprüft und beide Standards enthalten ein Beispiel mit einem Sprung über die Initialisierung in einer switch-Anweisung.
Richard Corden

38

Die gesamte switch-Anweisung befindet sich im selben Bereich. Um dies zu umgehen, gehen Sie folgendermaßen vor:

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

    case ANOTHER_VAL:
      ...
    break;
}

Beachten Sie die Klammern.


30

Nachdem ich alle Antworten und weitere Nachforschungen gelesen habe, bekomme ich ein paar Dinge.

Case statements are only 'labels'

In C gemäß der Spezifikation,

§6.8.1 Beschriftete Aussagen:

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

In C gibt es keine Klausel, die eine "gekennzeichnete Deklaration" zulässt. Es ist einfach nicht Teil der Sprache.

Damit

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

Dies wird nicht kompiliert , siehe 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;

Dies wird auch nicht kompiliert , siehe http://codepad.org/BXnRD3bu . Hier bekomme ich auch den gleichen Fehler.


In C ++ gemäß der Spezifikation,

Die Labeled-Deklaration ist zulässig, die Labeled-Initialisierung jedoch nicht.

Siehe http://codepad.org/ZmQ0IyDG .


Die Lösung für einen solchen Zustand ist zwei

  1. Verwenden Sie entweder einen 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-Anweisung

Schreiben Sie niemals Anweisungen in den Schalter, die nicht Teil eines Labels sind, da diese niemals ausgeführt werden:

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

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

    default:
        break;
}

Siehe http://codepad.org/PA1quYX3 .


2
Sie haben das C-Problem korrekt beschrieben. Die Behauptung, dass in C ++ eine markierte Initialisierung nicht zulässig ist, ist jedoch völlig falsch. An der beschrifteten Initialisierung in C ++ ist nichts auszusetzen. Was C ++ nicht zulässt, ist das Überspringen der Initialisierung einer Variablen ain den Bereich der Variablen a. Aus C-Sicht liegt das Problem also beim case VAL:Etikett, und Sie haben es richtig beschrieben. Aus C ++ - Sicht liegt das Problem jedoch bei der case ANOTHER_VAL:Beschriftung.
Am

In C ++ sind Deklarationen im Gegensatz zu C eine Teilmenge von Anweisungen.
Keith Thompson

20

Sie können dies nicht tun, da caseBeschriftungen eigentlich nur Einstiegspunkte in den enthaltenen Block sind.

Dies wird am deutlichsten durch Duffs Gerät veranschaulicht . Hier ist ein Code aus 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, dass die caseBeschriftungen die Blockgrenzen vollständig ignorieren. Ja, das ist böse. Aus diesem Grund funktioniert Ihr Codebeispiel nicht. Das Springen zu einem caseLabel entspricht dem Verwenden von goto, sodass Sie mit einem Konstruktor nicht über eine lokale Variable springen dürfen.

Wie mehrere andere Poster angegeben haben, müssen Sie einen eigenen Block einfügen:

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

1
Die Geräteimplementierung dieses Duff weist einen Fehler auf, der ihn extrem langsam macht: count ist vom Typ int, daher muss% eine echte Divisions- / Modulo-Operation ausführen. Machen Sie die Zählung ohne Vorzeichen (oder verwenden Sie noch besser size_t für Zählungen / Indizes) und das Problem verschwindet.
R .. GitHub STOP HELPING ICE

1
@R ..: Was?! In einem Zweierkomplementsystem wirkt sich die Vorzeichen nicht auf Modulos mit Zweierpotenzen aus (es ist nur ein UND in den unteren Bits) und wirkt sich nicht auf die Division durch Zweierpotenzen aus, solange Ihre Prozessorarchitektur eine arithmetische Rechtsverschiebungsoperation hat ( SARin x86 im Vergleich SHRzu vorzeichenlosen Schichten).
Chris Jester-Young

@ Chris: Ich glaube, er meint, wenn der Compiler negative Werte zulassen muss, bei denen "nur ein UND an den unteren Bits" nicht gilt; Zum Beispiel ergibt -1% 8 -1 auf dem Komplementsystem dieser beiden unter Verwendung von g ++ (das Vorzeichen in diesem Fall ist die gemäß 5.6 / 4 definierte Implementierung).

3
@ Chris: Ich stimme Ihnen zu, dass R die Auswirkungen übertreibt; Ich habe nur Ihren Kommentar gesehen und wusste, dass ein einfaches UND nicht ausreicht.

1
Erwähnenswert ist auch, dass der ursprüngliche Wikipedia-Code zum Senden von Daten an eine speicherabgebildete Ausgabe dient, was hier seltsam aussieht, da er nicht erwähnt wird und jedes Byte an denselben Speicherort "to" kopiert wird. Könnte dies umgehen, indem Sie entweder postfix ++ zum to hinzufügen oder den Anwendungsfall für speicherabgebildete E / A erwähnen. Völlig peripher zur ursprünglichen Frage :-).
Peter

16

Die meisten der bisherigen Antworten sind in einer Hinsicht falsch: Sie können Variablen nach der case-Anweisung deklarieren, aber nicht initialisieren:

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

Wie bereits erwähnt, besteht eine gute Möglichkeit darin, Klammern zu verwenden, um einen Bereich für Ihren Fall zu erstellen.


1
Mr. 32 Sie haben falsch verstanden, was Ihr Fehler ist: Ja, das wird nicht kompiliert, aber nicht, weil Sie eine Variable in einem Switch deklarieren. Der Fehler ist, weil Sie versuchen, eine Variable nach einer Anweisung zu deklarieren, die in C illegal ist.
MrZebra

1
Jetzt ein Tag, der in c90 und neuerer Version von c
Jeegar Patel

12

Mein bevorzugter böser Trick ist es, mit einem if (0) ein unerwünschtes Falletikett 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.


Sehr schön. Beispiel dafür, warum: Fall 0 und Fall 1 könnten beispielsweise eine Variable anders initialisieren, die dann in Fall 2 verwendet wird.
hlovdal

1
Wenn Sie möchten, dass sowohl Fall 0 als auch Fall 1 durch Fall 2 fallen (ohne dass Fall 0 durch Fall 1 fällt). Ich weiß nicht, ob es wirklich nützlich ist, aber es funktioniert auf jeden Fall.
Petruza

1
Sie können einfach gotoohne Verschleierung des Codes zum gewünschten Etikett springen
SomeWittyUsername

10

Versuche dies:

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

7

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

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

Der Grund liegt darin, Speicherplatz auf dem Stapel für die Speicherung der lokalen Variablen zuzuweisen (und zurückzugewinnen).


1
Die Variable kann deklariert, aber nicht initialisiert werden. Außerdem bin ich mir ziemlich sicher, dass sich das Problem sowieso nicht auf den Stapel und die lokalen Variablen bezieht.
Richard Corden

6

Erwägen:

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

Wenn keine break-Anweisungen vorhanden sind, wird newVal manchmal zweimal deklariert, und Sie wissen erst zur Laufzeit, ob dies der Fall ist. Ich vermute, dass die Einschränkung auf diese Art von Verwirrung zurückzuführen ist. Was wäre der Umfang von newVal? Die Konvention würde vorschreiben, dass es sich um den gesamten Schaltblock (zwischen den Klammern) handelt.

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

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

Funktioniert gut. Das Deklarieren einer Variablen innerhalb eines Schalterblocks ist in Ordnung. Nach einem Fallwächter zu deklarieren ist nicht.


3
@ Mr.32: Eigentlich zeigt Ihr Beispiel, dass ein printf nicht ausgeführt wird, aber in diesem Fall ist das int x keine Anweisung, sondern eine Deklaration, das x wird deklariert, der Platz dafür wird jedes Mal reserviert, wenn die Funktionsumgebung gestapelt wird. siehe: codepad.org/4E9Zuz1e
Petruza

Ich hatte erwartet, dies beim Lesen des Titels der Frage zu finden, da es bei der Frage nicht darum geht, Variablen in "case:" - Labels zu deklarieren, sondern in switch-Anweisungen. Und nur Sie (und VictorH, die Ihre Antwort hervorheben) haben tatsächlich über Variablen in switch-Anweisungen gesprochen.
beendet

4

Der gesamte Abschnitt des Switches ist ein einzelner Deklarationskontext. In einer solchen case-Anweisung können Sie keine Variable deklarieren. Versuchen Sie stattdessen Folgendes:

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

Die Variable kann deklariert, aber nicht initialisiert werden.
Richard Corden

@ Richard Corden Ich bin zuversichtlich, dass die Initialisierung funktionieren wird. Behaupten Sie immer noch, dass es nicht initialisiert werden kann?
chux

3

Wenn Ihr Code "int newVal = 42" sagt, würden Sie vernünftigerweise erwarten, dass newVal niemals nicht initialisiert wird. Aber wenn Sie über diese Aussage hinweg sind (was Sie tun), dann passiert genau das - newVal ist im Geltungsbereich, wurde aber nicht zugewiesen.

Wenn es das ist, was Sie wirklich wollten, muss die Sprache es explizit machen, indem Sie "int newVal; newVal = 42;" sagen. Andernfalls können Sie den Umfang von newVal auf den Einzelfall beschränken, was wahrscheinlicher ist, als Sie wollten.

Es kann Dinge verdeutlichen, wenn Sie dasselbe Beispiel betrachten, aber mit "const int newVal = 42;"


3

Ich wollte nur betonen , schlank ‚s Punkt . Ein Switch-Konstrukt schafft einen ganzen, erstklassigen Bürgerbereich. Es ist also möglich, eine Variable in einer switch-Anweisung vor der ersten Fallbezeichnung ohne ein zusätzliches Klammerpaar zu deklarieren (und zu initialisieren) :

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

-1 hier int newVal = 42; wird niemals hingerichtet werden. siehe diese codepad.org/PA1quYX3
Jeegar Patel

4
Die Deklaration int newVal wird ausgeführt, nicht jedoch die = 42Zuordnung.
Petruza

3

Bisher waren die Antworten für C ++.

Für C ++ können Sie nicht über eine Initialisierung springen. Sie können in C. In C ist eine Deklaration jedoch keine Anweisung, und auf Fallbezeichnungen müssen Anweisungen folgen.

Also, gültiges (aber hässliches) C, ungültiges 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();
}

Umgekehrt ist in C ++ eine Deklaration eine Anweisung, daher ist das folgende C ++ gültig, ungültiges C.

switch (something)
{
  case 1:
    do_something();
    break;
  case 2:
    int i = 12;
    do_something_else();
}

1
Das zweite Beispiel ist NICHT gültig C ++ (Test mit vc2010 und gcc 4.6.1 C ++ erlaubt nicht, den Initialisierungsteil zu überspringen. Die gcc-Fehlermeldung lautet:
Kreuzinitialisierung

3

Interessant, dass das in Ordnung ist:

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

case 1:  
    break;
}

... aber das ist nicht:

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

case 1:  
    break;
}

Ich verstehe, dass ein Fix einfach genug ist, aber ich verstehe noch nicht, warum das erste Beispiel den Compiler nicht stört. Wie bereits erwähnt (2 Jahre zuvor, hehe), verursacht die Deklaration trotz der Logik nicht den Fehler. Initialisierung ist das Problem. Wenn die Variable in den verschiedenen Zeilen initialisiert und deklariert wird, wird sie kompiliert.


1
Erstens ist auf gcc 4.2 nicht in Ordnung: "Fehler: erwarteter Ausdruck vor 'int'". Wie Peter und Mr.32 sagen, funktionieren "Fall 0 :; int j; ..." und "Fall 0 :; int j = 7; ..." beide. Das Problem in C ist nur, dass "case <Label>: Deklaration" keine gültige C-Syntax ist.
Dubiousjim

3

Ich habe diese Antwort ursprünglich für diese Frage geschrieben . Als ich damit fertig war, stellte ich fest, dass die Antwort geschlossen wurde. Also habe ich es hier gepostet, vielleicht findet es jemand hilfreich, der Verweise auf Standard mag.

Ursprünglicher Code in Frage:

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

Es gibt tatsächlich 2 Fragen:

1. Warum kann ich eine Variable nach dem caseLabel deklarieren ?

Es ist, weil in C ++ Label in der Form sein muss:

N3337 6.1 / 1

Labeled-Statement:

...

  • Attribut-Spezifizierer-seqopt case constant-expression :statement

...

Und in der C++ Erklärung gilt die Aussage auch als Aussage (im Gegensatz zu C):

N3337 6/1:

Aussage :

...

Deklarationsanweisung

...

2. Warum kann ich über die Variablendeklaration springen und sie dann verwenden?

Weil: N3337 6.7 / 3

Es ist möglich, in einen Block zu übertragen, jedoch nicht so, dass Deklarationen mit der Initialisierung umgangen werden . Ein Programm, das springt (Die Übertragung von der Bedingung einer switch-Anweisung auf eine Fallbezeichnung wird in dieser Hinsicht als Sprung angesehen .)

Von einem Punkt, an dem sich eine Variable mit automatischer Speicherdauer nicht im Gültigkeitsbereich befindet, bis zu einem Punkt, an dem sie sich im Gültigkeitsbereich befindet, ist sie fehlerhaft, es sei denn, die Variable hat einen Skalartyp , einen Klassentyp mit einem trivialen Standardkonstruktor und einen trivialen Destruktor, eine cv-qualifizierte Version eines dieser Typen oder eines Arrays eines der vorhergehenden Typen und wird ohne Initialisierer deklariert (8.5).

Da kes sich um einen skalaren Typ handelt und zum Zeitpunkt der Deklaration nicht initialisiert wird, ist ein Überspringen der Deklaration möglich. Dies ist semantisch äquivalent:

goto label;

int x;

label:
cout << x << endl;

Dies wäre jedoch nicht möglich, wenn xes zum Zeitpunkt der Deklaration initialisiert würde :

 goto label;

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

    label:
    cout << x << endl;

1

Neue Variablen können nur im Blockbereich dekaliert werden. Sie müssen so etwas schreiben:

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

Natürlich hat newVal nur innerhalb der Klammern Spielraum ...

Prost, Ralph


1

Ein switchBlock ist nicht dasselbe wie eine Folge von if/else ifBlöcken. Ich bin überrascht, dass keine andere Antwort dies klar erklärt.

Betrachten Sie diese switchAussage:

switch (value) {
    case 1:
        int a = 10;
        break;
    case 2:
        int a = 20;
        break;
}

Es mag überraschen, aber der Compiler wird es nicht als einfach ansehen if/else if. Es wird der folgende Code erzeugt:

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

Die caseAnweisungen werden in Labels konvertiert und dann mit aufgerufen goto. Die Klammern erstellen einen neuen Bereich und es ist jetzt leicht zu erkennen, warum Sie nicht zwei Variablen mit demselben Namen innerhalb von a deklarieren könnenswitch Blocks .

Es mag seltsam aussehen, aber es ist notwendig, Fallthrough zu unterstützen ( dh nicht zu verwenden break, um die Ausführung zum nächsten fortzusetzen case).


0

Ich glaube, das Problem ist, dass die Anweisung übersprungen wurde und Sie versucht haben, die Variable an anderer Stelle zu verwenden. Sie würde nicht deklariert.


0

newVal existiert im gesamten Bereich des Schalters, wird jedoch nur initialisiert, wenn das VAL-Glied getroffen wird. Wenn Sie einen Block um den Code in VAL erstellen, sollte dieser in Ordnung sein.


0

C ++ Standard hat: Es ist möglich, in einen Block zu übertragen, jedoch nicht so, dass Deklarationen bei der Initialisierung umgangen werden. Ein Programm, das von einem Punkt, an dem sich eine lokale Variable mit automatischer Speicherdauer nicht im Gültigkeitsbereich befindet, zu einem Punkt springt, an dem sie sich im Gültigkeitsbereich befindet, ist fehlerhaft, es sei denn, die Variable hat den POD-Typ (3.9) und wird ohne Initialisierer (8.5) deklariert.

Der Code zur Veranschaulichung dieser Regel:

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

Der Code zum Anzeigen des Initialisierungseffekts:

#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

Es scheint, dass anonyme Objekte in einer switch case-Anweisung deklariert oder erstellt werden können , weil sie nicht referenziert werden können und als solche nicht zum nächsten Fall durchfallen können. Betrachten Sie dieses Beispiel, das unter GCC 4.5.3 und Visual Studio 2008 kompiliert wurde (möglicherweise ein Compliance-Problem, obwohl Experten bitte abwägen).

#include <cstdlib>

struct Foo{};

int main()
{
    int i = 42;

    switch( i )
    {
    case 42:
        Foo();  // Apparently valid
        break;

    default:
        break;
    }
    return EXIT_SUCCESS;
}

Wenn Sie es ablehnen wollen, erklären Sie bitte warum. Ich bin gespannt, warum das Erstellen eines anonymen Objekts eine Ausnahme darstellt.
Olumide

1
kein DV, aber: Die ganze Frage betrifft die Deklaration / den Umfang der benannten Variablen. Ein temporäres Objekt ("anonymes Objekt" ist kein Begriff) ist weder eine benannte Variable noch eine Deklaration oder unterliegt dem Geltungsbereich (es sei denn, es ist an eine constReferenz mit einem eigenen Geltungsbereich gebunden ). Es ist ein Ausdruck, der in seiner Aussage lebt und stirbt (wo immer das auch sein mag). Daher ist es völlig irrelevant.
underscore_d

Foo();ist keine Erklärung; Die Frage betrifft Erklärungen.
MM
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.