it-swarm-eu.dev

Perché le variabili non possono essere dichiarate in un'istruzione switch?

Mi sono sempre chiesto questo: perché non puoi dichiarare le variabili dopo un'etichetta di caso in un'istruzione switch? In C++ puoi dichiarare le variabili praticamente ovunque (e dichiararle vicine al primo utilizzo è ovviamente una buona cosa) ma quanto segue non funzionerà ancora:

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

Quanto sopra mi dà il seguente errore (MSC):

l'inizializzazione di 'newVal' viene saltata dall'etichetta 'case'

Questo sembra essere un limite anche in altre lingue. Perché questo è un problema?

874
Rob

Le dichiarazioni Case sono solo etichette . Ciò significa che il compilatore interpreterà questo come un salto direttamente sull'etichetta. In C++, il problema qui è uno di scopo. Le parentesi graffe definiscono l'ambito come ogni cosa all'interno dell'istruzione switch. Ciò significa che ti rimane un ambito in cui un salto verrà eseguito ulteriormente nel codice saltando l'inizializzazione. Il modo corretto per gestirli è definire un ambito specifico per quell'istruzione case e definire la variabile al suo interno.

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

Questa domanda è è stato inizialmente etichettato come [C] e [C++] allo stesso tempo. Il codice originale è in effetti non valido sia in C che in C++, ma per ragioni non correlate completamente diverse. Credo che questo importante dettaglio sia stato mancato (o occultato) dalle risposte esistenti.

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

  • Tuttavia, in linguaggio C bypassare l'inizializzazione della variabile non è un errore. Saltare nello scope di una variabile rispetto alla sua inizializzazione è legale in C. Significa semplicemente che la variabile non è inizializzata. Il codice originale non viene compilato in C per un motivo completamente diverso. L'etichetta case VAL: nel codice originale è allegata alla dichiarazione della variabile newVal. In linguaggio C le dichiarazioni non sono dichiarazioni. 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 blocco {} aggiuntivo risolve entrambi i problemi C++ e C, anche se questi problemi sono molto diversi. Sul lato C++ limita l'ambito di newVal, assicurandosi che case ANOTHER_VAL: non salti più in quell'ambito, il che elimina il problema del C++. Sul lato C quel {} extra introduce una dichiarazione composta, rendendo così l'etichetta case VAL: da applicare a un'istruzione, che elimina il problema C.

  • Nel caso C il problema può essere facilmente risolto senza {}. Basta aggiungere una dichiarazione vuota dopo l'etichetta case VAL: e il codice diventerà valido

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

    Si noti che anche se ora è valido dal punto di vista C, rimane invalido dal punto di vista C++.

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

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

    Si noti che, anche se ora è valido dal punto di vista C++, rimane invalido dal punto di vista C.

297
AnT

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

Molti post qui hanno menzionato il fatto che saltare la dichiarazione può portare alla variabile "non dichiarata". Questo non è vero. Un oggetto POD 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;
}

Laddove l'oggetto è un non POD o aggregato, il compilatore aggiunge implicitamente un inizializzatore e quindi non è possibile saltare sopra 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 è limitata all'istruzione switch. È anche un errore usare 'goto' per 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 saltare l'inizializzazione.

Come altri hanno già detto, la soluzione è aggiungere un blocco nidificato in modo che la durata della variabile sia limitata alla singola etichetta del caso.

131
Richard Corden

L'intera istruzione switch è nello stesso ambito. Per aggirarlo, fai questo:

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

    case ANOTHER_VAL:
      ...
    break;
}

Nota le parentesi.

35
Mark Ingram

Dopo aver letto tutte le risposte e qualche altra ricerca, ottengo alcune cose.

Case statements are only 'labels'

In C, secondo le specifiche,

§6.8.1 Dichiarazioni etichettate:

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

In C non esiste alcuna clausola che consenta una "dichiarazione etichettata". Non fa parte della lingua.

Così

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

Questo non verrà compilato , vedi http://codepad.org/YiyLQTYw . GCC sta dando 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 è anche non compilando , vedi http://codepad.org/BXnRD3bu . Anche qui sto ottenendo lo stesso errore.


In C++, secondo le specifiche,

la dichiarazione con etichetta è consentita ma l'etichettatura -inizializzazione non è consentita.

Vedi http://codepad.org/ZmQ0IyDG .


La soluzione a tale condizione è due

  1. Utilizza il nuovo ambito usando {}

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
    
  2. Oppure usa una dichiarazione fittizia con etichetta

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
    
  3. Dichiarare la variabile prima di switch () e inizializzarla con valori diversi nell'istruzione case se soddisfa i requisiti

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

Altre cose con l'istruzione switch

Non scrivere mai dichiarazioni nell'interruttore che non fanno parte di alcuna etichetta, perché non verranno mai eseguite:

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

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

    default:
        break;
}

Vedi http://codepad.org/PA1quYX3 .

28
Jeegar Patel

Non puoi farlo, perché le etichette case sono in realtà solo punti di ingresso nel blocco contenitore.

Questo è più chiaramente illustrato da dispositivo di Duff . Ecco un codice 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 le etichette case ignorino totalmente i limiti dei blocchi. Sì, questo è il male. Ma questo è il motivo per cui il tuo esempio di codice non funziona. Saltare su un'etichetta case equivale a usare goto, quindi non è possibile saltare su una variabile locale con un costruttore.

Come molti altri poster hanno indicato, devi inserire un blocco tutto tuo:

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

La maggior parte delle risposte finora sono sbagliate da un punto di vista: tu puoi dichiarare le variabili dopo l'istruzione case, ma tu can ' t inizializzali:

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

Come accennato in precedenza, un buon modo per aggirare questo è usare le parentesi graffe per creare uno scope per il tuo caso.

16
MrZebra

Il mio trucco di interruttore malvagio preferito consiste nell'usare un if (0) per saltare un'etichetta del caso indesiderata.

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

Ma molto male.

12
Jeremy

Prova questo:

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

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

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

Il motivo è da attribuire allo spazio di allocazione (e bonifica) nello stack per l'archiviazione delle variabili locali.

7
Seb Rose

Tenere conto:

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

In assenza di istruzioni break, a volte newVal viene dichiarato due volte e non si sa se lo fa fino al runtime. La mia ipotesi è che la limitazione sia dovuta a questo tipo di confusione. Quale sarebbe lo scopo di newVal? La convenzione imporrebbe che sarebbe l'intero blocco dell'interruttore (tra parentesi graffe).

Non sono un programmatore C++, ma in C:

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

Funziona bene. Dichiarare una variabile all'interno di un blocco di switch va bene. Dichiarare dopo una guardia caso non lo è.

6
slim

L'intera sezione dello switch è un singolo contesto di dichiarazione. Non è possibile dichiarare una variabile in un'istruzione case come quella. Prova questo:

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

Se il tuo codice dice "int newVal = 42" allora ti aspetteresti ragionevolmente che newVal non fosse mai inizializzato. Ma se passi a questa affermazione (che è ciò che stai facendo), allora è esattamente quello che succede: newVal è in-scope ma non è stato assegnato.

Se questo è ciò che intendevi realmente accadere, allora il linguaggio richiede di renderlo esplicito dicendo "int newVal; newVal = 42;". Altrimenti puoi limitare l'ambito di newVal al singolo caso, che è più probabile quello che volevi.

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

3
Mike F

Volevo solo sottolineare slim point . Un costrutto dell'interruttore crea un intero, ambito cittadino di prima classe. Quindi è possibile dichiarare (e inizializzare) una variabile in un'istruzione switch prima della prima etichetta del caso, senza una coppia di parentesi aggiuntiva:

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

Interessante che questo va bene:

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

case 1:  
    break;
}

... ma questo non lo è:

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

case 1:  
    break;
}

Ho capito che una correzione è abbastanza semplice, ma non capisco ancora perché il primo esempio non infastidisca il compilatore. Come accennato in precedenza (2 anni prima hehe), dichiarazione non è ciò che causa l'errore, nonostante la logica. L'inizializzazione è il problema. Se la variabile è inizializzata e dichiarata sulle diverse linee, viene compilata.

3
Dan

Ho scritto questa risposta orginalmente per questa domanda . Tuttavia, quando ho finito, ho trovato che la risposta è stata chiusa. Quindi l'ho pubblicato qui, forse qualcuno che adora i riferimenti allo standard lo troverà utile.

Codice originale 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é posso dichiarare una variabile dopo l'etichetta case?

È perché l'etichetta C++ deve essere in forma:

N3337 6.1/1

etichettato-dichiarazione:

...

  • attribute-specifier-seqopt caseconstant-expression: statement

...

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

N3337 6/1:

dichiarazione :

...

Dichiarazione-economico

...

2. Perché posso saltare la dichiarazione delle variabili e poi usarla?

Perché: N3337 6.7/3

È possibile trasferire in un blocco, ma non in un modo che ignora le dichiarazioni con l'inizializzazione . Un programma che salta (Il trasferimento di da la condizione di un'istruzione di commutazione a un'etichetta di caso è considerato un salto a questo riguardo. )

da un punto in cui una variabile con durata dell'archiviazione automatica non è nell'ambito di un punto in cui è nell'ambito di ambito è mal formata a meno che la variabile non abbia scalare type , tipo di classe con un costruttore banale di default e un distruttore banale, una versione cv-qualificata di uno di questi tipi, o una matrice di uno dei tipi precedenti e dichiarata senza un inizializzatore (8.5).

Poiché k è di tipo scalare , e non è inizializzato nel punto di dichiarazione è possibile saltare sopra la sua dichiarazione. Questo è semanticamente equivalente:

goto label;

int x;

label:
cout << x << endl;

Tuttavia ciò non sarebbe possibile, se x è stato inizializzato nel punto di dichiarazione:

 goto label;

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

    label:
    cout << x << endl;
3
PcAF

Finora le risposte sono state per C++.

Per C++, non puoi saltare un'inizializzazione. È possibile in C. Tuttavia, in C, una dichiarazione non è una dichiarazione e le etichette dei casi devono essere seguite da dichiarazioni.

Quindi, valido (ma brutto) C, C++ invalido

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

Conversare, in C++, una dichiarazione è un'istruzione, quindi il seguente è C++ valido, C non valido

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

Un blocco switch non è lo stesso di una successione di blocchi if/else if. Sono sorpreso che nessun'altra risposta lo spieghi chiaramente.

Considera questa dichiarazione switchname__:

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

Può essere sorprendente, ma il compilatore non lo vedrà come un 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

Le istruzioni casevengono convertite in etichette e quindi chiamate con gotoname__. Le parentesi creano un nuovo ambito ed è facile vedere ora perché non è possibile dichiarare due variabili con lo stesso nome all'interno di un blocco switchname__.

Può sembrare strano, ma è necessario supportare fallthrough (cioè, non usando breakper lasciare che l'esecuzione continui al successivo casename__).

1
Dalmas

newVal esiste nell'intero ambito dello switch ma è inizializzato solo se viene colpito l'arto VAL. Se crei un blocco attorno al codice in VAL, dovrebbe essere OK.

0
marijne

Lo standard C++ ha: È possibile trasferire in un blocco, ma non in un modo che ignora le dichiarazioni con l'inizializzazione. Un programma che salta da un punto in cui una variabile locale con durata dell'archiviazione automatica non è nell'ambito di un punto in cui è nell'ambito dell'ambito è mal formata a meno che la variabile non abbia il tipo POD (3.9) e sia dichiarata 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 mostrare l'effetto di inizializzazione:

#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
Jingguo Yao