Pregunta ¿Por qué no se pueden declarar las variables en una instrucción switch?


Siempre me he preguntado esto: ¿por qué no puedes declarar variables después de una etiqueta de caso en una instrucción switch? En C ++ puedes declarar variables prácticamente en cualquier lugar (y declararlas cercanas al primer uso es obviamente una buena cosa) pero las siguientes aún no funcionarán:

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

Lo anterior me da el siguiente error (MSC):

la inicialización de 'newVal' se omite por la etiqueta 'case'

Esto parece ser una limitación en otros idiomas también. ¿Por qué es esto un problema?


788
2017-09-18 13:11


origen


Respuestas:


Case las declaraciones son solo etiquetas. Esto significa que el compilador interpretará esto como un salto directamente a la etiqueta. En C ++, el problema aquí es uno de alcance. Sus llaves definen el alcance como todo dentro del switch declaración. Esto significa que se queda con un alcance en el que se realizará un salto en el código omitiendo la inicialización. La forma correcta de manejar esto es definir un alcance específico para ese case declaración y define tu variable dentro de ella.

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

967
2017-09-18 13:17



Esta pregunta está etiquetada como [C] y [C ++] al mismo tiempo. El código original no es válido tanto en C como en C ++, pero por razones completamente diferentes no relacionadas. Creo que este detalle importante fue pasado por alto (u ofuscado) por las respuestas existentes.

  • En C ++, este código no es válido porque el case ANOTHER_VAL: la etiqueta salta dentro del alcance de la variable newVal eludiendo su inicialización. Los saltos que omiten la inicialización de objetos locales son ilegales en C ++. Este lado del problema es abordado correctamente por la mayoría de las respuestas.

  • Sin embargo, en lenguaje C eludir la inicialización de la variable no es un error. Saltar al ámbito de una variable sobre su inicialización es legal en C. Simplemente significa que la variable se deja sin inicializar. El código original no se compila en C por una razón completamente diferente. Etiqueta case VAL: en el código original se adjunta a la declaración de variable newVal. En lenguaje C, las declaraciones no son declaraciones. No pueden ser etiquetados. Y esto es lo que causa el error cuando este código se interpreta como código C.

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

Agregar un extra {} block corrige los problemas C ++ y C, aunque estos problemas son muy diferentes. En el lado de C ++, restringe el alcance de newVal, asegurándose de que case ANOTHER_VAL: ya no salta a ese ámbito, lo que elimina el problema de C ++. En el lado C ese extra {} introduce una declaración compuesta, lo que hace que el case VAL: etiqueta para aplicar a una declaración, lo que elimina el problema C.

  • En el caso C, el problema se puede resolver fácilmente sin el {}. Solo agrega una declaración vacía después de case VAL: etiqueta y el código será válido

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

    Tenga en cuenta que, aunque ahora es válido desde el punto de vista de C, sigue siendo inválido desde el punto de vista de C ++.

  • Simétricamente, en el caso de C ++, el problema se puede resolver fácilmente sin el {}. Solo elimine el inicializador de la declaración de variables y el código será válido

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

    Tenga en cuenta que, aunque ahora es válido desde el punto de vista de C ++, sigue siendo inválido desde el punto de vista C.


236
2017-11-07 08:12



De acuerdo. Solo aclarar esto estrictamente no tiene nada que ver con la declaración. Se refiere solo a "saltar sobre la inicialización" (ISO C ++ '03 6.7 / 3)

Muchos de los mensajes aquí mencionados han mencionado que saltar la declaración puede dar como resultado que la variable "no se declare". Esto no es verdad. Un objeto POD puede declararse sin un inicializador, pero tendrá un valor indeterminado. Por ejemplo:

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

Cuando el objeto no es un POD o un agregado, el compilador agrega implícitamente un inicializador, por lo que no es posible pasar por encima de dicha declaración:

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

Esta limitación no está limitada a la declaración de cambio. También es un error usar 'goto' para saltar sobre una inicialización:

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

Un poco de trivia es que esta es una diferencia entre C ++ y C. En C, no es un error saltar sobre la inicialización.

Como otros han mencionado, la solución es agregar un bloque anidado de modo que la duración de la variable se limite a la etiqueta de caso individual.


125
2017-09-18 13:54



Toda la instrucción switch está en el mismo alcance. Para evitarlo, haz esto:

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

    case ANOTHER_VAL:
      ...
    break;
}

Nota los corchetes.


35
2017-09-18 13:13



Después de leer todas las respuestas y más investigación, obtengo algunas cosas.

Case statements are only 'labels'

En C, de acuerdo con la especificación,

§6.8.1 Declaraciones etiquetadas:

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

En C no hay ninguna cláusula que permita una "declaración etiquetada". Simplemente no es parte del lenguaje.

Asi que

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

Esta no compilará, ver http://codepad.org/YiyLQTYw. GCC está dando un error:

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

Incluso

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

esto es también no compilando, ver http://codepad.org/BXnRD3bu. Aquí también estoy obteniendo el mismo error.


En C ++, de acuerdo con la especificación,

la declaración etiquetada está permitida, pero está etiquetada, la inicialización no está permitida.

Ver http://codepad.org/ZmQ0IyDG.


La solución a tal condición es dos

  1. Utilice un ámbito nuevo usando {}

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
    
  2. O use una declaración ficticia con la etiqueta

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
    
  3. Declare la variable antes de cambiar () e inicialícela con valores diferentes en la declaración de caso si cumple con su requisito

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

Algunas cosas más con la declaración de cambio

Nunca escriba ninguna declaración en el interruptor que no forme parte de ninguna etiqueta, porque nunca se ejecutará:

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

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

    default:
        break;
}

Ver http://codepad.org/PA1quYX3.


26
2017-12-18 06:33



No puedes hacer esto, porque case las etiquetas son en realidad solo puntos de entrada en el bloque contenedor.

Esto se ilustra más claramente por Dispositivo de Duff. Aquí hay un código 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);
    }
}

Observe cómo el case las etiquetas ignoran por completo los límites del bloque. Sí, esto es malo. Pero esta es la razón por la que su ejemplo de código no funciona. Saltando a un case etiqueta es lo mismo que usar goto, por lo que no puede saltar una variable local con un constructor.

Como varios otros carteles han indicado, debes colocar un bloque propio:

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

21
2017-09-18 13:15



La mayoría de las respuestas hasta ahora son erróneas en un aspecto: poder declare las variables después de la declaración del caso, pero hipocresía Inicialízalos:

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

Como se mencionó anteriormente, una buena manera de evitar esto es utilizar llaves para crear un alcance para su caso.


16
2017-09-18 14:00



Mi truco de interruptor malvado favorito es usar un if (0) para omitir una etiqueta de caso no deseado.

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

Pero muy malvado


12
2017-09-18 17:02



Prueba esto:

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

10
2017-09-18 13:14



Puede declarar variables dentro de una instrucción switch Si empiezas un nuevo bloque:

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

El motivo es hacer asignando (y reclamando) espacio en la pila para el almacenamiento de la (s) variable (s) local (es).


7
2017-09-18 13:15