Utilizzo di {} in un'istruzione case. Perché?


101

Qual è il punto con l'utilizzo di {e }in una casedichiarazione? Normalmente, non importa quante righe ci siano in caseun'istruzione, tutte le righe vengono eseguite. È solo una regola per quanto riguarda i compilatori più vecchi / più recenti o c'è qualcosa dietro?

int a = 0;
switch (a) {
  case 0:{
    std::cout << "line1\n";
    std::cout << "line2\n";
    break;
  }
}

e

int a = 0;
switch (a) {
  case 0:
    std::cout << "line1\n";
    std::cout << "line2\n";
    break;
}

57
Un utilizzo può essere quello di limitare l'ambito delle variabili dichiarate nell'istruzione case.
Abhishek Bansal


1
Anche troppa rientranza. I casi sono solo etichette all'interno del blocco dell'istruzione switch: non introducono annidamenti aggiuntivi, quindi dovrebbero allinearsi con la switchparola chiave e, nel secondo esempio, le istruzioni racchiuse rientrano solo una volta. Nota come hai un imbarazzante de-rientro di quattro spazi dopo il break;.
Kaz

Nota, la risposta accettata è corretta solo parzialmente, come sottolinea il commento di Jack e manca di alcune sottigliezze, che affronterò nella mia risposta.
Shafik Yaghmour

Proprio come FYI: in C (anche C11) piuttosto che C ++, non puoi etichettare una dichiarazione; non sono nella categoria sintattica statement. In C ++, puoi (un componente della categoria sintattica statementè declaration statement).
Jonathan Leffler

Risposte:


195

Il {}denota un nuovo blocco di ambito .

Considera il seguente esempio molto artificioso:

switch (a)
{
    case 42:
        int x = GetSomeValue();
        return a * x;
    case 1337:
        int x = GetSomeOtherValue(); //ERROR
        return a * x;
}

Riceverai un errore del compilatore perché xè già definito nell'ambito.

Separarli nel proprio sotto-ambito eliminerà la necessità di dichiarare xal di fuori dell'istruzione switch.

switch (a)
{
    case 42: {
        int x = GetSomeValue();
        return a * x; 
    }
    case 1337: {
        int x = GetSomeOtherValue(); //OK
        return a * x; 
    }
}

11
In realtà IMO otterrai un errore del compilatore anche se salti la seconda dichiarazione della variabile x.
Abhishek Bansal

1
Sebbene l'uso di questo stile e l'inserimento di grossi blocchi all'interno dell'istruzione switch renderà illeggibile seguire i casi. Preferisco mantenere minuscole le dichiarazioni.
masoud

2
@MatthieuM. So per certo che MS Visual Studio 2010 avrà il comportamento indicato da Abhishek: non compilerà alcuna dichiarazione di variabile all'interno di un caso (a meno che non si utilizzino parentesi graffe per indicare un nuovo ambito all'interno di quel caso). Se questo corrisponde agli standard, non lo so.
KRyan

1
@KRyan: non è così, ma è un'alternativa molto più sicura che difficilmente posso biasimarli per averlo fatto rispettare.
Matthieu M.

6
La sezione 6.7 (3) dello standard (numerazione per la bozza del 2005) specifica che non è possibile saltare un'inizializzazione, quindi non è possibile avere un'inizializzazione in un blocco di casi.
Jack Aidley

23

TL; DR

L'unico modo per dichiarare una variabile con un inizializzatore o un oggetto non banale all'interno di un case è introdurre uno scope di blocco usando {}o un'altra struttura di controllo che abbia il proprio ambito come un ciclo o un'istruzione if .

Dettagli cruenti

Possiamo vedere che i casi sono solo istruzioni etichettate come le etichette usate con un'istruzione goto ( questo è trattato nella bozza di C ++ sezione 6.1 Dichiarazione etichettata ) e possiamo vedere dalla sezione 6.7paragrafo 3 che saltare una dichiarazione non è consentito in molti casi , compresi quelli con un'inizializzazione:

È possibile trasferire in un blocco, ma non in un modo che aggiri le dichiarazioni con inizializzazione. Un programma che salta 87 da un punto in cui una variabile con durata di memorizzazione automatica non è nell'ambito a un punto in cui è nell'ambito è mal formato a meno che la variabile non abbia un tipo scalare, un tipo di classe con un banale costruttore predefinito e un banale distruttore, una versione qualificata da cv di uno di questi tipi o un array di uno dei tipi precedenti e viene dichiarata senza un inizializzatore (8.5).

e fornisce questo esempio:

void f() {
 // ...
 goto lx; // ill-formed: jump into scope of a

 ly:
  X a = 1;
 // ...
 lx:
  goto ly; // OK, jump implies destructor
          // call for a followed by construction
          // again immediately following label ly
}

Nota, ci sono alcune sottigliezze qui, puoi saltare oltre una dichiarazione scalare che non ha un'inizializzazione, ad esempio:

switch( n ) 
{
    int x ;
    //int x  = 10 ; 
    case 0:
      x = 0 ;
      break;
    case 1:
      x = 1 ;
      break;
    default:
      x = 100 ;
      break ;
}

è perfettamente valido ( esempio dal vivo ). Ovviamente se vuoi dichiarare la stessa variabile in ogni caso, ognuno di loro avrà bisogno del proprio ambito, ma funziona allo stesso modo anche al di fuori delle istruzioni switch , quindi non dovrebbe essere una grande sorpresa.

Per quanto riguarda la logica per non consentire l'inizializzazione del salto passato, il report di difetto 467, sebbene copra un problema leggermente diverso, fornisce un caso ragionevole per le variabili automatiche :

le variabili [...] automatiche, se non esplicitamente inizializzate, possono avere valori indeterminati ("garbage"), incluse rappresentazioni trap, [...]

Probabilmente è più interessante osservare il caso in cui estendi un ambito all'interno di uno switch su più casi, gli esempi più famosi di questo è probabilmente il dispositivo di Duff che sarebbe simile a questo:

void send( int *to, const int *from, int  count)
{
        int n = (count + 7) / 8;
        switch(count % 8) 
        {
            case 0: do {    *to = *from++;   // <- Scope start
            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);    // <- Scope end
        }
}

6

È un'abitudine che consente di inserire nelle caseclausole dichiarazioni di variabili con il distruttore risultante (o conflitti di ambito) . Un altro modo di vederlo è che stanno scrivendo per il linguaggio che vorrebbero avere, in cui tutto il controllo del flusso consiste in blocchi e non sequenze di istruzioni.


4

Seleziona questa una restrizione di base del compilatore e inizierai a chiederti cosa sta succedendo:

int c;
c=1;

switch(c)
{
    case 1:
    //{
        int *i = new int;
        *i =1;
        cout<<*i;
        break;
    //}

    default : cout<<"def";
}

Questo ti darà un errore:

error: jump to case label [-fpermissive]
error:   crosses initialization of int* i

Anche se questo non:

int c;
c=1;

switch(c)
{
    case 1:
    {
        int *i = new int;
        *i =1;
        cout<<*i;
        break;
    }

    default : cout<<"def";
}

1

L'uso delle parentesi nell'interruttore denota un nuovo blocco di ambito come detto da Rotem.

Ma può essere anche per chiarezza quando leggi. Per sapere dove si ferma il caso in quanto potresti avere una rottura condizionale.


0

Le ragioni potrebbero essere:

  1. Leggibilità, migliora visivamente ogni caso come sezione con ambito.
  2. Dichiarazione di variabili diverse con lo stesso nome per diversi casi di switch.
  3. Micro ottimizzazioni: possibilità di una variabile allocata di risorse molto costosa che si desidera distruggere non appena si esce dall'ambito del caso, o anche uno scenario più spaghetto dell'utilizzo del comando "GOTO".
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.