Question Pourquoi les variables ne peuvent-elles pas être déclarées dans une instruction switch?


Je me suis toujours demandé ceci - pourquoi ne pouvez-vous pas déclarer des variables après une étiquette de cas dans une déclaration de commutateur? En C ++, vous pouvez déclarer des variables à peu près n'importe où (et les déclarer proches de la première utilisation est évidemment une bonne chose) mais ce qui suit ne fonctionnera toujours pas:

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

Ce qui précède me donne l'erreur suivante (MSC):

l'initialisation de 'newVal' est ignorée par l'étiquette 'case'

Cela semble être une limitation dans d'autres langues aussi. Pourquoi est-ce un tel problème?


788
2017-09-18 13:11


origine


Réponses:


Case les déclarations sont seulement Étiquettes. Cela signifie que le compilateur interprétera cela comme un saut directement sur l'étiquette. En C ++, le problème ici est celui de la portée. Vos accolades définissent la portée comme tout à l'intérieur du switch déclaration. Cela signifie qu'il vous reste une portée dans laquelle un saut sera effectué plus loin dans le code en ignorant l'initialisation. La bonne façon de gérer cela est de définir une portée spécifique à cette case déclaration et définissez votre variable en son sein.

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

967
2017-09-18 13:17



Cette question est marquée comme [C] et [C ++] en même temps. Le code original est en effet invalide en C et C ++, mais pour des raisons complètement différentes. Je crois que ce détail important a été manqué (ou obfusqué) par les réponses existantes.

  • En C ++, ce code est invalide car le case ANOTHER_VAL: étiquette saute dans la portée de la variable newVal contournant son initialisation. Les sauts qui contournent l'initialisation des objets locaux sont illégaux en C ++. Ce côté du problème est correctement traité par la plupart des réponses.

  • Cependant, en langage C, contourner l'initialisation des variables n'est pas une erreur. Sauter dans la portée d'une variable au cours de son initialisation est légal en C. Cela signifie simplement que la variable n'est pas initialisée. Le code d'origine ne compile pas en C pour une raison complètement différente. Étiquette case VAL: dans le code d'origine est attaché à la déclaration de variable newVal. En langage C, les déclarations ne sont pas des déclarations. Ils ne peuvent pas être étiquetés. Et c'est ce qui provoque l'erreur lorsque ce code est interprété comme du code C.

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

Ajouter un extra {} Le bloc corrige à la fois les problèmes C ++ et C, même si ces problèmes sont très différents. Du côté C ++, cela restreint la portée de newVal, en s'assurant que case ANOTHER_VAL: ne saute plus dans cette portée, ce qui élimine le problème C ++. Du côté C, ce supplément {} introduit une déclaration composée, rendant ainsi le case VAL: étiquette à appliquer à une déclaration, ce qui élimine le problème C.

  • Dans le cas C, le problème peut être résolu facilement sans {}. Ajoutez simplement une déclaration vide après le case VAL: étiquette et le code deviendra valide

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

    Notez que même s'il est maintenant valide du point de vue C, il reste invalide du point de vue C ++.

  • Symétriquement, dans le cas C ++, le problème peut être résolu facilement sans {}. Supprimez simplement l'initialiseur de la déclaration de variable et le code deviendra valide

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

    Notez que même s'il est maintenant valide du point de vue C ++, il reste invalide du point de vue C.


236
2017-11-07 08:12



D'accord. Juste pour clarifier ceci strictement n'a rien à voir avec la déclaration. Il ne concerne que "sauter par-dessus l'initialisation" (ISO C ++ '03 6.7 / 3)

Un grand nombre de messages ici ont mentionné que sauter par-dessus la déclaration peut entraîner la variable "ne pas être déclaré". Ce n'est pas vrai. Un objet POD peut être déclaré sans initialiseur mais il aura une valeur indéterminée. Par exemple:

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

Lorsque l'objet est un non-POD ou un agrégat, le compilateur ajoute implicitement un initialiseur, et il n'est donc pas possible de sauter par-dessus une telle déclaration:

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

Cette limitation n'est pas limitée à l'instruction switch. C'est aussi une erreur d'utiliser 'goto' pour sauter par dessus une initialisation:

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

Un peu de trivial est que c'est une différence entre C ++ et C. En C, ce n'est pas une erreur de sauter par-dessus l'initialisation.

Comme d'autres l'ont mentionné, la solution consiste à ajouter un bloc imbriqué afin que la durée de vie de la variable soit limitée à l'étiquette du cas individuel.


125
2017-09-18 13:54



L'ensemble de l'instruction switch est dans la même portée. Pour contourner cela, faites ceci:

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

    case ANOTHER_VAL:
      ...
    break;
}

Remarque les supports.


35
2017-09-18 13:13



Après avoir lu toutes les réponses et d'autres recherches, j'ai eu quelques petites choses.

Case statements are only 'labels'

En C, selon la spécification,

§6.8.1 Mentions étiquetées:

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

En C il n'y a aucune clause qui permet une "déclaration étiquetée". Ce n'est juste pas une partie de la langue.

Alors

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

Ce ne compilera pasvoir http://codepad.org/YiyLQTYw. GCC donne une erreur:

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

Même

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

c'est également ne pas compilervoir http://codepad.org/BXnRD3bu. Ici, je reçois également la même erreur.


En C ++, selon la spécification,

label-declaration est autorisé mais marqué -initialization n'est pas autorisé.

Voir http://codepad.org/ZmQ0IyDG.


La solution à cette condition est deux

  1. Utilisez la nouvelle portée en utilisant {}

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
    
  2. Ou utilisez une instruction factice avec l'étiquette

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
    
  3. Déclarez la variable before switch () et initialisez-la avec des valeurs différentes dans l'instruction case si elle répond à vos besoins

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

Quelques autres choses avec l'instruction switch

N'écrivez jamais d'instructions dans le commutateur qui ne font partie d'aucune étiquette, car elles ne seront jamais exécutées:

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

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

    default:
        break;
}

Voir http://codepad.org/PA1quYX3.


26
2017-12-18 06:33



Tu ne peux pas faire ça, parce que case Les étiquettes ne sont en fait que des points d'entrée dans le bloc conteneur.

Ceci est le plus clairement illustré par L'appareil de Duff. Voici un code de 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);
    }
}

Remarquez comment le case les étiquettes ignorent totalement les limites du bloc. Oui, c'est mal. Mais c'est pourquoi votre exemple de code ne fonctionne pas. Sauter à un case l'étiquette est la même que l'utilisation goto, vous n'êtes donc pas autorisé à sauter par-dessus une variable locale avec un constructeur.

Comme plusieurs autres affiches l'ont indiqué, vous devez mettre un bloc de votre choix:

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

21
2017-09-18 13:15



La plupart des réponses à ce jour sont erronées sur un point: vous pouvez déclarer des variables après l'instruction de cas, mais vous ne peut pas les initialiser:

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

Comme mentionné précédemment, un bon moyen de contourner cela est d'utiliser des accolades pour créer une portée pour votre cas.


16
2017-09-18 14:00



Mon truc de switch maléfique préféré est d'utiliser un if (0) pour passer une étiquette de case indésirable.

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

Mais très mal.


12
2017-09-18 17:02



Essaye ça:

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

10
2017-09-18 13:14



Vous pouvez déclarer des variables dans une instruction switch si vous commencez un nouveau bloc:

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

La raison en est l'attribution (et la récupération) d'espace sur la pile pour le stockage de la ou des variables locales.


7
2017-09-18 13:15