Pourquoi pas des variables déclarées dans une instruction switch?

je me suis toujours demandé - pourquoi ne pouvez-vous pas déclarer des variables après une étiquette de cas dans une déclaration de changement? En C++, vous pouvez déclarer des variables à peu près n'importe où (et les déclarer près de la première utilisation est évidemment une bonne chose) mais la suivante 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 sautée par l'étiquette' case '

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

801
demandé sur Antti Haapala 2008-09-18 17:11:55

23 réponses

Case les déclarations sont seulement les étiquettes . Cela signifie que le compilateur interprétera ceci comme un saut direct vers l'étiquette. En C++, le problème est celui de la portée. Vos crochets bouclés définissent la portée comme tout ce qui se trouve à l'intérieur de la déclaration switch . Cela signifie qu'il vous reste un scope où un saut sera effectué plus loin dans le code en sautant l'initialisation. La bonne façon de gérer cela est de définir une portée spécifique à celle case déclaration et de définir votre variable à l'intérieur d'elle.

switch (val)
{   
case VAL:  
{
  // This will work
  int newVal = 42;  
  break;
}
case ANOTHER_VAL:  
...
break;
}
979
répondu TJ Seabrooks 2016-10-27 18:12:39

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é négligé (ou obscurci) par les réponses existantes.

  • dans C++ ce code est invalide parce que l'étiquette case ANOTHER_VAL: saute dans le champ d'application de la variable newVal sans passer par son initialisation. Sauts qui contournent l'initialisation des les objets sont illégaux en C++. Cet aspect de la question est correctement abordé dans 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-dessus de son initialisation est légal en C. Cela signifie simplement que la variable n'est pas initialisée. Le code original ne se compile pas en C pour une raison complètement différente. L'étiquette case VAL: du code original est jointe au déclaration de la variable newVal . Dans la langue 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 bloc supplémentaire {} corrige à la fois les problèmes C++ et C, même si ces problèmes sont très différents. Du côté c++, il restreint la portée de newVal , en s'assurant que case ANOTHER_VAL: ne saute plus dans ce scope, qui élimine le problème C++. Du côté C, le supplément {} introduit une déclaration composée, ce qui fait que l'étiquette case VAL: s'applique à une déclaration, ce qui élimine la question C.

  • dans le cas C le problème peut être facilement résolu sans le {} . Il suffit d'ajouter une déclaration vide après l'étiquette case VAL: 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 si elle 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 facilement résolu sans le {} . Il suffit de supprimer l'initialiseur de la déclaration des variables 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 si elle est maintenant valide du point de vue C++, elle reste invalide du point de vue C.

243
répondu AnT 2017-12-07 01:43:00

Ok. Juste pour clarifier ce strictement a rien à voir avec la déclaration. Elle se rapporte uniquement à "sauter au-dessus de l'initialisation" (ISO C++ '03 6.7/3)

beaucoup de posts ici ont mentionné que le saut sur la déclaration peut entraîner la variable "non 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;
}

où l'objet est un non-POD ou aggregate le compilateur ajoute implicitement un initialiseur, et il n'est donc pas possible de sauter au-dessus d'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'indication de l'interrupteur. C'est aussi une erreur d'utiliser 'goto' pour sauter sur une initialisation:

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

un petit détail 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.

As d'autres ont mentionné, la solution est d'ajouter un bloc imbriqué, de sorte que la durée de vie de la variable est limitée au cas particulier de l'étiquette.

125
répondu Richard Corden 2008-09-18 13:54:04

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

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

    case ANOTHER_VAL:
      ...
    break;
}

Note les crochets.

35
répondu Mark Ingram 2015-04-26 10:21:36

après avoir lu toutes les réponses et plus de recherche, j'obtiens quelques choses.

Case statements are only 'labels'

En C, selon la spécification,

§6.8.1 Déclarations Étiquetées:

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

en C il n'y a pas de clause qui permette une"déclaration étiquetée". C'est tout simplement pas partie de la langue.

Donc

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

Ce ne sera pas compiler , voir http://codepad.org/YiyLQTYw . GCC donne une erreur:

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

Pair

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

c'est également ne pas compiler , voir http://codepad.org/BXnRD3bu . Ici, je suis également obtenir la même erreur.


en C++, selon la spécification,

étiqueté-déclaration est autorisé, mais marqué -l'initialisation n'est pas autorisé.

voir http://codepad.org/ZmQ0IyDG .


la Solution à une telle condition est deux

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

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

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
    
  3. déclarez la variable avant switch () et initialisez-la avec des valeurs différentes dans l'énoncé de cas si elle répond à votre exigence

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

quelques autres choses avec la déclaration de commutateur

N'écrivez jamais d'instructions dans le commutateur qui ne font pas partie d'une é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
répondu Jeegar Patel 2016-07-12 12:51:39

vous ne pouvez pas faire cela, parce que les étiquettes case sont en fait juste des points d'entrée dans le bloc contenant.

cela est très clairement illustré par dispositif de Duff . Voici du code de Wikipédia:

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 les étiquettes case ignorent totalement les limites des blocs. Oui, c'est mal. Mais c'est pourquoi votre exemple de code ne fonctionne pas. Passer à une étiquette case est la même chose qu'utiliser goto , donc vous n'êtes pas autorisé à sauter sur une variable locale avec un constructeur.

comme plusieurs autres affiches ont indiqué, vous devez mettre dans un bloc de votre propre:

switch (...) {
    case FOO: {
        MyObject x(...);
        ...
        break; 
    }
    ...
 }
21
répondu emk 2010-09-05 20:45:12

la plupart des réponses jusqu'à présent sont erronées dans un sens: vous can déclarez des variables après la déclaration de cas, mais vous can't initialisez-les:

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

comme mentionné précédemment, une bonne façon de contourner cela est d'utiliser des bretelles pour créer une portée pour votre cas.

16
répondu MrZebra 2008-09-18 14:00:31

mon truc préféré de mauvais commutateur est d'utiliser un si(0) pour sauter sur une étiquette de cas indésirables.

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
répondu Jeremy 2008-09-18 17:02:47

essayez ceci:

switch (val)
{
    case VAL:
    {
        int newVal = 42;
    }
    break;
}
10
répondu Dan Shield 2008-09-18 13:14:33

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

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

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

7
répondu Seb Rose 2009-10-18 06:12:18

prendre en considération:

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

en l'absence de break statements, newVal est parfois déclaré deux fois, et vous ne savez pas si c'est le cas jusqu'à l'exécution. Je pense que la limitation est due à ce genre de confusion. Quelle serait la portée de newVal? Convention voudrait qu'il serait l'ensemble du bloc de commutateurs (entre les accolades).

Je ne suis pas programmeur C++, mais en C:

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

Works fin. Déclarer une variable à l'intérieur d'un bloc d'interrupteur est très bien. Déclarer après un gardien de cas n'est pas.

6
répondu slim 2008-09-18 13:22:09

la section entière de l'interrupteur est un contexte de déclaration unique. Vous ne pouvez pas déclarer une variable dans une déclaration de cas comme cela. Essayez plutôt ceci:

switch (val)  
{  
case VAL:
{
  // This will work
  int newVal = 42;
  break;
}
case ANOTHER_VAL:  
  ...
  break;
}
4
répondu 2008-09-18 13:16:24

si votre code dit" int newVal=42 " alors vous pouvez raisonnablement vous attendre à ce que newVal ne soit jamais uninitialisé. Mais si vous passez au-dessus de cette Déclaration (ce qui est ce que vous faites) alors c'est exactement ce qui se passe - newVal est dans la portée mais n'a pas été assigné.

si c'est ce que vous avez vraiment voulu dire, alors le langage doit le rendre explicite en disant"int newVal; newVal = 42;". Sinon, vous pouvez limiter la portée de newVal à un seul cas, ce qui est plus probablement ce que vous voulais.

il peut clarifier les choses si vous considérez le même exemple mais avec "const int newVal = 42; "

3
répondu 2008-09-18 13:21:36

je voulais juste souligner slim 's point . Un interrupteur crée un champ d'action citoyen de première classe. Il est donc possible de déclarer (et d'initialiser) une variable dans une instruction de commutation avant la première étiquette de cas, sans une paire de crochets supplémentaires:

switch (val) {  
  /* This *will* work, even in C89 */
  int newVal = 42;  
case VAL:
  newVal = 1984; 
  break;
case ANOTHER_VAL:  
  newVal = 2001;
  break;
}
3
répondu 2 revsVictorH 2017-05-23 11:55:02

Jusqu'à présent, les réponses ont été pour C++.

pour C++, vous ne pouvez pas sauter sur une initialisation. Vous pouvez dans C. Cependant, dans C, une déclaration n'est pas un énoncé, et les étiquettes de cas doivent être suivies par des énoncés.

donc, valide (mais moche) C, invalide 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();
}

Inversement, en C++, une déclaration est une déclaration, de sorte que le suivant est valide en C++, non valide C

switch (something)
{
  case 1:
    do_something();
    break;
  case 2:
    int i = 12;
    do_something_else();
}
3
répondu Peter 2009-09-02 15:08:23

intéressant que ce soit bien:

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

case 1:  
    break;
}

... mais ce n'est pas:

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

case 1:  
    break;
}

je comprends qu'une correction est assez simple, mais je ne comprends pas encore pourquoi le premier exemple ne dérange pas le compilateur. Comme il a été mentionné plus tôt (2 ans plus tôt hehe), déclaration n'est pas ce qui cause l'erreur, même en dépit de la logique. L'Initialisation est le problème. Si la variable est initialisée et déclarée sur la les lignes, il compile.

3
répondu Dan 2011-03-23 02:20:02

j'ai écrit cette réponse initialement pour cette question . Cependant, quand je l'ai terminé, j'ai trouvé que la réponse a été fermée. Donc je l'ai posté ici, peut-être quelqu'un qui aime les références à la norme seront utiles.

code Original en question:

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

il y a en fait 2 questions:

1. Pourquoi puis-je déclarer une variable après l'étiquette case ?

c'est parce que dans C++ l'étiquette doit être en forme:

N3337 6.1 / 1

étiqueté-déclaration:

...

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

...

et dans C++ la déclaration est également considérée comme la déclaration (par opposition à C ):

N3337 6/1:

déclaration :

...

déclaration-déclaration

...

2. Pourquoi je peux sauter sur la variable déclaration et ensuite l'utiliser?

parce que: N3337 6.7 / 3

Il est possible de transférer dans un bloc mais pas d'une manière qui contourne les déclarations d'initialisation . Un programme qui saute (Le transfert de la la condition de instruction switch pour un cas étiquette est considéré comme un saut à cet égard.)

d'un point où une variable avec une durée de stockage automatique n'est pas dans le champ d'application à un point où elle est dans le champ d'application est mal formé à moins que la variable a le type scalaire , le type de classe avec un défaut trivial constructeur et d'un trivial destructeur, un cv qualifiés version de l'un de ces types, ou un tableau de l'un des type précédent et est déclaré sans initialiseur (8.5).

depuis k est de type scalaire , et n'est pas initialisé au point de déclaration sautant au-dessus de sa déclaration est possible. C'est sémantiquement équivalent:

goto label;

int x;

label:
cout << x << endl;

cependant, cela ne serait pas possible si x était initialisé au point de déclaration:

 goto label;

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

    label:
    cout << x << endl;
3
répondu PcAF 2017-05-23 12:18:30

les nouvelles variables ne peuvent être décalaminées qu'au niveau du bloc. Vous devez écrire quelque chose comme ceci:

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

bien sûr, newVal n'a de portée que dans les accolades...

Cheers, Ralph

1
répondu 2008-09-18 13:24:17

Un switch bloc n'est pas le même comme une succession de if/else if blocs. je suis surpris qu'aucune autre réponse ne l'explique clairement.

switch déclaration :

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

cela peut surprendre, mais le compilateur ne le verra pas comme un simple if/else if . Il produira le code suivant:

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

les déclarations case sont converties en labels et ensuite appelé avec goto . Les crochets créent une nouvelle portée et il est facile de voir maintenant pourquoi vous ne pouvez pas déclarer deux variables avec le même nom dans un bloc switch .

cela peut sembler bizarre, mais il est nécessaire de soutenir fallthrough (c'est-à-dire, ne pas utiliser break pour laisser l'exécution continue au prochain case ).

1
répondu Dalmas 2015-01-01 18:17:08

newVal existe dans toute la portée de l'interrupteur mais n'est initialisé que si le bras VAL est touché. Si vous créez un bloc autour du code dans VAL, ça devrait aller.

0
répondu marijne 2008-09-18 13:15:25

C++ Standard a: Il est possible de transférer en un bloc, mais pas d'une manière qui contourne les déclarations à l'initialisation. Un programme qui passe d'un point où une variable locale avec une durée de stockage automatique n'est pas dans scope à un point où elle est dans scope est mal formé à moins que la variable ait le type de POD (3.9) et soit déclarée sans initialiseur (8.5).

le code pour illustrer cette règle:

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

le code pour montrer le effet initialisateur:

#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
répondu Jingguo Yao 2012-07-10 06:48:19

je crois que le problème ici est que la déclaration a été sautée, et vous avez essayé d'utiliser le var ailleurs, il ne serait pas déclaré.

-1
répondu William Keller 2008-09-18 13:15:16

Il semble que les objets anonymes peut , déclarée ou créés dans un commutateur cas de déclaration pour la raison qu'ils ne peuvent pas être référencées et en tant que telle ne peut pas tomber à travers le cas suivant. Considérez cet exemple de compilations sur GCC 4.5.3 et Visual Studio 2008 (pourrait être un problème de conformité tho' so experts please weigh in)

#include <cstdlib>

struct Foo{};

int main()
{
    int i = 42;

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

    default:
        break;
    }
    return EXIT_SUCCESS;
}
-1
répondu Olumide 2013-02-08 10:55:29