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





beispiel mehrere (20)


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.

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?




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

Beachten Sie die Klammern.




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.




Versuche dies:

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



Der gesamte Abschnitt des Schalters ist ein einzelner Deklarationskontext. Sie können eine Variable in einer solchen Case-Anweisung nicht deklarieren. Versuchen Sie es stattdessen:

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



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 newVal Umgehung seiner Initialisierung in den Gültigkeitsbereich der Variablen newVal springt. 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. Label- case VAL: im ursprünglichen Code ist an die Deklaration der Variablen 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;
    }
    

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

  • In C kann das Problem ohne das {} leicht gelöst werden. Fügen Sie einfach eine leere Anweisung nach dem case VAL: label 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, 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 {} 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.




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.




Ich glaube, das Problem ist, dass die Anweisung übersprungen wurde, und Sie versucht haben, die var anderswo zu verwenden, würde es nicht erklärt werden.




Ich wollte nur den Slim - point betonen. Ein Switch-Konstrukt erzeugt einen vollständigen First-Class-Citizen-Bereich. So ist es möglich, eine Variable in einer switch-Anweisung vor der ersten Case-Bezeichnung zu deklarieren (und zu initialisieren), ohne ein zusätzliches Klammerpaar:

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



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

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



newVal existiert im gesamten Umfang des Schalters, wird aber nur initialisiert, wenn der VAL-Schenkel berührt wird. Wenn Sie einen Block um den Code in VAL erstellen, sollte es OK sein.




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 eine Korrektur 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), ist die Deklaration nicht der Grund für den Fehler, trotz der Logik. Initialisierung ist das Problem. Wenn die Variable in den verschiedenen Zeilen initialisiert und deklariert ist, wird sie kompiliert.




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

Conversly, in C ++, ist eine Deklaration eine Anweisung, daher gilt Folgendes: C ++, ungültiges C

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



Case Anweisungen sind nur Labels . 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 der switch Anweisung. 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 Bereich zu definieren, der für diese Fallanweisung spezifisch ist, und Ihre Variable darin zu definieren.

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



Ich habe diese Antwort ursprünglich für diese Frage geschrieben . Als ich es jedoch beendet habe, habe ich festgestellt, dass die Antwort geschlossen wurde. Also habe ich es hier gepostet, vielleicht wird jemand, der Referenzen zum Standard mag, es hilfreich finden.

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 case Label deklarieren?

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

N3337 6.1 / 1

beschriftete Aussage:

...

  • attribute-specifier-seqopt case constant-expression : statement

...

Und in C++ Deklarationsanweisung auch als Anweisung betrachtet (im Gegensatz zu C ):

N3337 6/1:

Aussage :

...

Deklarationsanweisung

...

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

Weil: N3337 6.7 / 3

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

von einem Punkt aus, an dem eine Variable mit automatischer Speicherdauer nicht in einem Bereich liegt, in dem sie sich befindet, ist schlecht geformt, es sei denn, die Variable hat Skalartyp , 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 (8.5) deklariert.

Da k vom Skalartyp ist und nicht am Deklarationspunkt initialisiert wird, ist die Deklaration möglich. Dies ist semantisch äquivalent:

goto label;

int x;

label:
cout << x << endl;

Dies wäre jedoch nicht möglich, wenn x zum Zeitpunkt der Deklaration initialisiert wurde:

 goto label;

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

    label:
    cout << x << endl;



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 besteht darin, Speicherplatz auf dem Stapel für das Speichern der lokalen Variablen zuzuweisen (und wiederzugewinnen).




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

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

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

Prost, Ralph




Ein switch ist nicht dasselbe wie eine Folge von if/else if Blöcken. Ich bin überrascht, dass keine andere Antwort es deutlich erklärt.

Betrachten Sie diese switch Anweisung:

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

Es mag überraschend sein, aber der Compiler sieht es nicht als einfaches if/else if . Es wird den folgenden Code erzeugen:

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 case Anweisungen werden in Labels konvertiert und dann mit goto aufgerufen. Die Klammern erzeugen einen neuen Gültigkeitsbereich und es ist leicht zu sehen, warum Sie nicht zwei Variablen mit demselben Namen innerhalb eines switch Blocks deklarieren können.

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




Es scheint, dass anonyme Objekte in einer Switch-Case-Anweisung deklariert oder erstellt werden können , weil sie nicht referenziert werden können und daher nicht zum nächsten Fall durchgehen können. Beachten Sie, dass dieses Beispiel in GCC 4.5.3 und Visual Studio 2008 kompiliert wird (dies könnte ein Kompatibilitätsproblem sein, so dass die Experten das Gewicht berücksichtigen).

#include <cstdlib>

struct Foo{};

int main()
{
    int i = 42;

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

    default:
        break;
    }
    return EXIT_SUCCESS;
}



Dies liegt nicht an einem anderen Code, sondern an einer Zwischenspeicherung: Das RAM ist langsamer als die CPU-Register und ein Cache-Speicher befindet sich in der CPU, um zu vermeiden, dass das RAM jedes Mal geschrieben wird, wenn sich eine Variable ändert. Da der Arbeitsspeicher jedoch nicht groß ist, bildet er nur einen Bruchteil davon ab.

Der erste Code modifiziert entfernte Speicheradressen, indem er sie in jeder Schleife abwechselt, so dass der Cache fortlaufend ungültig werden muss.

Der zweite Code wechselt nicht: Er fließt nur zweimal an benachbarte Adressen. Dadurch wird der gesamte Job im Cache abgeschlossen und erst nach dem Start der zweiten Schleife ungültig.





c++ switch-statement