Domanda

Mi sono sempre chiesto questo - perché non si può dichiarare le variabili dopo un'etichetta di caso in un'istruzione switch?In C++, è possibile dichiarare variabili praticamente ovunque (e dichiarare loro di chiudere al primo utilizzo è ovviamente una buona cosa), ma il seguito ancora non funziona:

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

Sopra mi da il seguente errore (MSC):

inizializzazione di 'newVal' è saltato dal 'caso' etichetta

Questa sembra essere una limitazione anche in altre lingue.Perché è questo un problema?

È stato utile?

Soluzione

Case dichiarazioni sono solo etichette.Questo significa che il compilatore interpreta questo come un salto direttamente l'etichetta.In C++, il problema qui è uno di ambito.La tua parentesi graffe definire l'ambito all'interno tutto il switch istruzione.Questo significa che si sono lasciati con un campo di applicazione in cui un salto verrà eseguito ulteriormente nel codice di saltare la fase di inizializzazione.Il modo corretto di gestire questa è la definizione di un ambito specifico che case istruzione e definire la variabile all'interno di esso.

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

Altri suggerimenti

Questa domanda è originariamente contrassegnati con il tag [C] e [C++] allo stesso tempo.Il codice originale è davvero valido sia in C e C++, ma per diversi motivi non specificati.Credo che questo importante dettaglio è stato perso (o nascosta) da answers.

  • In C++ questo codice non è valido perché il case ANOTHER_VAL: etichetta salti nel campo di applicazione della variabile newVal ignorando la sua inizializzazione.Salti che ignorano l'inizializzazione di oggetti locali sono illegali in C++.Questo lato del problema è correttamente affrontato dalla maggior parte delle risposte.

  • Tuttavia, in linguaggio C, bypassando inizializzazione di una variabile non è un errore.Saltando nell'ambito di una variabile oltre la sua inizializzazione, è legale in C.Significa semplicemente che la variabile è di sinistra non inizializzata.Il codice originale non compilare in C per un motivo completamente diverso.Etichetta case VAL: il codice originale è allegata alla dichiarazione di variabile newVal.In linguaggio C, le dichiarazioni non sono dichiarazioni.Essi non possono essere etichettati.E questo è ciò che causa l'errore quando questo codice viene interpretato come codice C.

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

L'aggiunta di un extra {} blocco correzioni sia in C++ e C problemi, anche se questi problemi capita di essere molto diverso.Il C++ lato limita l'ambito di applicazione della newVal, assicurandosi che case ANOTHER_VAL: non salta più in quell'ambito, che elimina il C++ problema.Sul lato C che extra {} introduce un'istruzione composta, rendendo il case VAL: etichette da applicare a una dichiarazione, che elimina il C problema.

  • In C caso il problema può essere facilmente risolto senza l' {}.Basta aggiungere un'istruzione vuota dopo l' case VAL: etichetta e il codice sarà valido

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

    Nota che anche se ora è valido dal C punto di vista, rimane valido dal C++ punto di vista.

  • Simmetricamente, in C++ caso il problema può essere facilmente risolto senza l' {}.Basta rimuovere l'inizializzatore da dichiarazione di variabile e il codice sarà valido

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

    Nota che anche se ora è valido dal C++ punto di vista, rimane valido dal C punto di vista.

Ok.Giusto per chiarire questo rigorosamente non ha nulla a che fare con la dichiarazione.Si riferisce solo a "saltare oltre l'inizializzazione" (ISO C++ '03 6.7/3)

Un sacco di post qui hanno detto che saltando la dichiarazione può comportare la variabile "non dichiarato".Questo non è vero.Un CONTENITORE di un oggetto può essere dichiarato senza un inizializzatore, ma avrà un valore indeterminato.Per esempio:

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

In cui l'oggetto è un non-POD o aggregata, il compilatore implicitamente aggiunge un inizializzatore, e quindi non è possibile saltare una tale dichiarazione:

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

Questa limitazione non è limitato all'istruzione switch.È anche un errore usare 'goto' saltare un inizializzazione:

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

Un po ' di curiosità è che questa è una differenza tra C++ e C.In C, non è un errore di saltare la fase di inizializzazione.

Come altri hanno detto, la soluzione è di aggiungere un blocco nidificato in modo che la durata della variabile è limitata al singolo caso etichetta.

Tutta l'istruzione switch è nello stesso ambito.Per ottenere intorno ad esso, fare questo:

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

    case ANOTHER_VAL:
      ...
    break;
}

Nota le parentesi quadre.

Dopo aver letto tutte le risposte e le ulteriori ricerche ho un paio di cose.

Case statements are only 'labels'

In C, secondo la specifica,

§6.8.1 Etichettato Dichiarazioni:

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

In C non c'è alcuna clausola che consente una "etichetta " dichiarazione".Non è solo una parte del linguaggio.

Così

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

Questo non compilare, vedere http://codepad.org/YiyLQTYw.GCC dà un errore:

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

Anche

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

questo è inoltre non si compila, vedere http://codepad.org/BXnRD3bu.Qui sono anche sempre lo stesso errore.


In C++, secondo la specifica,

etichettato dichiarazione è consentito, ma con l'etichetta " inizializzazione non è consentito.

Vedere http://codepad.org/ZmQ0IyDG.


La soluzione a tale condizione è di due

  1. Utilizzare il nuovo campo di applicazione l'utilizzo di {}

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
    
  2. O utilizzare il manichino di istruzione con etichetta

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
    
  3. Dichiarare la variabile prima di passare() e inizializzate con valori diversi in caso di dichiarazione se soddisfa le vostre esigenze

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

Alcune cose di più con istruzione switch

Non scrivere mai le affermazioni dello switch che non sono parte di qualsiasi etichetta, perché non sarà mai eseguito:

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

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

    default:
        break;
}

Vedere http://codepad.org/PA1quYX3.

Non si può fare questo, perché case le etichette sono solo punti di ingresso nel blocco che contiene.

Questo è più chiaramente illustrato da Duff dispositivo.Ecco alcuni codici da 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);
    }
}

Si noti come il case etichette di ignorare totalmente il blocco dei confini.Sì, questo è male.Ma questo è il motivo per cui il tuo esempio di codice non funziona.Il passaggio a una case l'etichetta è la stessa cosa che usare goto, in modo che non si possono saltare una variabile locale con un costruttore.

Come molti altri manifesti hanno indicato, è necessario mettere in un blocco di tua scelta:

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

La maggior parte delle risposte finora sono sbagliato in un punto di vista:si può dichiarare le variabili dopo l'istruzione case, ma si non può inizializzare loro:

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

Come accennato in precedenza, un bel modo per aggirare questo è quello di utilizzare le parentesi graffe per creare un campo di applicazione per il vostro caso.

Il mio preferito male interruttore trucco è quello di utilizzare un if(0) saltare un indesiderato caso di etichetta.

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

Ma molto male.

Prova questo:

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

È possibile dichiarare le variabili all'interno di un'istruzione switch se si avvia un nuovo blocco:

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

La ragione ha a che fare con l'allocazione (e recupero) spazio sullo stack per la conservazione della variabile locale(s).

Prendere in considerazione:

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

In assenza di interruzione di dichiarazioni, a volte newVal viene dichiarata due volte, e non so se lo fa fino all'esecuzione.La mia ipotesi è che la limitazione è a causa di questo tipo di confusione.Che cosa sarebbe l'ambito di newVal essere?Convenzione, vorrebbe che sarebbe tutta un interruttore di blocco (tra le parentesi graffe).

Io non sono un programmatore C++, ma in C:

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

Funziona bene.Dichiarazione di una variabile all'interno di un interruttore di blocco è bene.Dichiarando, dopo un caso di guardia non.

L'intera sezione del commutatore è una dichiarazione unica contestuale.Non è possibile dichiarare una variabile in un caso simile affermazione, che.Invece, provare questo:

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

Se il codice dice "int newVal=42" quindi si potrebbe ragionevolmente aspettare che newVal non è mai non inizializzato.Ma se si va oltre questa dichiarazione (che è quello che stai facendo), allora che è esattamente ciò che accade - newVal è in questo ambito, ma non ha ancora ricevuto.

Se questo è ciò che è realmente accaduto allora la lingua richiede per rendere esplicito dicendo "int newVal;newVal = 42;".Altrimenti è possibile limitare l'ambito di newVal per il singolo caso, che è più probabile che ciò che si voleva.

Potrebbe chiarire le cose, se si considera lo stesso esempio ma con "const int newVal = 42;"

Volevo solo sottolineare slim's punto.Un costrutto switch crea un tutto, di prima classe-ambito cittadino.Quindi, la possibilità di dichiarare (e inizializzare) una variabile in un'istruzione switch prima del primo caso l'etichetta, senza un ulteriore staffa di coppia:

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

Finora le risposte sono state per C++.

Per il C++, non è possibile saltare una inizializzazione.È possibile in C.Tuttavia, in C, di una dichiarazione, non è una dichiarazione, e in caso le etichette devono essere seguite da istruzioni.

Così, valido (ma di brutto) C, C++non valido

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++, una dichiarazione è una dichiarazione, in modo che la seguente è valida, C++, C non valido

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

Interessante il fatto che questo è bene:

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

case 1:  
    break;
}

...ma questo non è:

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

case 1:  
    break;
}

Credo che una correzione è abbastanza semplice, ma io non capire ancora perché il primo esempio non si è preoccupato di compilatore.Come è stato accennato in precedenza (2 anni prima, hehe), dichiarazione non è ciò che causa l'errore, anche a dispetto della logica.L'inizializzazione è il problema.Se la variabile è inizializzata e dichiarato sulle diverse linee, compila.

Ho scritto questa risposta originariamente per questa domanda.Tuttavia, quando ho finito e ho trovato che la risposta è stata chiusa.Così ho postato qui, magari qualcuno che ama i riferimenti a standard sarà utile.

Originale Codice in questione:

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

In realtà ci sono 2 domande:

1.Perché è possibile dichiarare una variabile dopo case etichetta?

È perché in C++ etichetta deve essere nella forma:

N3337 6.1/1

etichettato-istruzione:

...

  • attributo identificatore-seqopt case constant-expression : statement

...

E in C++ dichiarazione è anche considerato come istruzione (invece di C):

N3337 6/1:

istruzione:

...

dichiarazione-dichiarazione

...

2.Perché posso saltare dichiarazione di variabile e quindi utilizzarlo?

Perché:N3337 6.7/3

È possibile trasferire in un blocco, ma non in un modo che aggira dichiarazioni di inizializzazione.Un programma che salti (Il trasferimento da la condizione di istruzione switch per un'etichetta di caso è considerato un salto a questo riguardo.)

da un punto in cui una variabile con durata di archiviazione automatica non è portata a un punto in cui è in questo ambito è mal formato a meno che la variabile è di tipo scalare, tipo di classe con un banale default costruttore e distruttore trivial, un curriculum qualificato versione di uno di questi tipi, o una matrice di uno dei tipi precedenti e viene dichiarato senza un inizializzatore (8.5).

Dal k è di tipo scalare, e non è inizializzato al punto di dichiarazione di saltare la dichiarazione è possibile.Questo è semanticamente equivalente:

goto label;

int x;

label:
cout << x << endl;

Tuttavia questo non sarebbe stato possibile, se x è stato inizializzato a punto della dichiarazione:

 goto label;

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

    label:
    cout << x << endl;

Variabili possono essere decalared solo in ambito blocco.Avete bisogno di scrivere qualcosa di simile a questo:

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

Naturalmente, newVal solo ha portata all'interno di parentesi graffe...

Ciao Ralph

Un switch blocco non è la stessa come una successione di if/else if i blocchi. Mi sorprende che nessun altra risposta che spiega chiaramente.

Prendere in considerazione questo switch dichiarazione :

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

Può essere sorprendente, ma il compilatore non la vedo come una semplice if/else if.Produrrà il seguente codice :

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

Il case le dichiarazioni sono convertiti in etichette e poi chiama con goto.Le staffe di creare un nuovo ambito, ed è facile vedere ora perché non è possibile dichiarare due variabili con lo stesso nome all'interno di una switch il blocco.

Può sembrare strano, ma è necessario il supporto fallthrough (che è, non si utilizza break per consentire l'esecuzione di continuare per il prossimo case).

newVal esiste in tutto l'ambito dell'interruttore, ma è solo inizializzato se la VAL arto colpito.Se si crea un blocco di tutto il codice in VAL dovrebbe essere OK.

C++ Standard è:È possibile trasferire in un blocco, ma non in un modo che aggira le dichiarazioni con l'inizializzazione.Un programma che salti da un punto in cui una variabile locale con durata di archiviazione automatica non è portata a un punto in cui è in questo ambito è mal formato, a meno che la variabile tipo di POD (3.9) e viene dichiarato senza un inizializzatore (8.5).

Il codice per illustrare questa regola:

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

Il codice per visualizzare l'inizializzatore effetto:

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

Credo che il problema in questione è che la dichiarazione è stata ignorata, e si è tentato di utilizzare il var altrove, non sarebbe dichiarato.

Sembra che gli oggetti anonimi può essere dichiarato o creato in un interruttore caso di dichiarazione per la ragione che essi non può essere fatto riferimento e, come tale, non può cadere il caso.Si consideri questo esempio viene compilato su GCC 4.5.3 e Visual Studio 2008 (potrebbe essere un problema di conformità tho' così esperti, si prega di pesa)

#include <cstdlib>

struct Foo{};

int main()
{
    int i = 42;

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

    default:
        break;
    }
    return EXIT_SUCCESS;
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top