c++ - lenguaje - tipos de variables en java ejemplos
¿Por qué no se pueden declarar las variables en una sentencia de cambio? (23)
Siempre me he preguntado esto: ¿por qué no puede declarar variables después de una etiqueta de caso en una declaración de cambio? En C ++, puede declarar las variables prácticamente en cualquier lugar (y declararlas cerca del primer uso es obviamente algo bueno) pero lo siguiente aún no funciona:
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 en la etiqueta ''case''
Esto parece ser una limitación en otros idiomas también. ¿Por qué es este un problema?
Considerar:
switch(val)
{
case VAL:
int newVal = 42;
default:
int newVal = 23;
}
En ausencia de declaraciones de ruptura, a veces newVal se declara dos veces, y no se sabe si lo hace hasta el tiempo de ejecución. Mi conjetura es que la limitación se debe a este tipo de confusión. ¿Cuál sería el alcance de newVal? La convención dictaría que sería la totalidad del bloque de conmutación (entre las llaves).
No soy programador de C ++, pero en C:
switch(val) {
int x;
case VAL:
x=1;
}
Funciona bien. Declarar una variable dentro de un bloque de interruptores está bien. Declarar después de un caso de guardia no es.
Creo que el problema en cuestión es que se saltó la declaración, y usted intentó utilizar la var en otro lugar, no se declararía.
De acuerdo. Solo para aclarar esto estrictamente no tiene nada que ver con la declaración. Se relaciona solo con "saltar sobre la inicialización" (ISO C ++ ''03 6.7 / 3)
Muchas de las publicaciones aquí han mencionado que saltar sobre la declaración puede resultar en que la variable "no se declare". Esto no es verdad. Un objeto POD se puede declarar 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 está agregado, el compilador agrega implícitamente un inicializador, por lo que no es posible saltar sobre esa 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 se limita a la instrucción de cambio. También es un error usar ''goto'' para saltar 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 han mencionado otros, la solución es agregar un bloque anidado para que la vida útil de la variable se limite a la etiqueta del caso individual.
Después de leer todas las respuestas y algunas investigaciones más, consigo algunas cosas.
Case statements are only ''labels''
En C, según 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;
Esto no se compilará , consulte 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 tampoco está compilando , vea http://codepad.org/BXnRD3bu . Aquí también estoy recibiendo el mismo error.
En C ++, según la especificación,
se permite la declaración-etiquetada pero no se permite la inicialización-etiquetada.
Consulte http://codepad.org/ZmQ0IyDG .
La solución a tal condición es dos
O usa un nuevo alcance usando {}
case 1: { int x=10; printf(" x is %d", x); } break;
O use una declaración falsa con etiqueta
case 1: ; int x=10; printf(" x is %d",x); break;
Declare la variable antes de switch () e inicialícela con diferentes valores 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 instrucción switch
Nunca escriba en el conmutador ninguna declaración que no forme parte de ninguna etiqueta, ya que nunca se ejecutarán:
switch(a)
{
printf("This will never print"); // This will never executed
case 1:
printf(" 1");
break;
default:
break;
}
El estándar C ++ tiene: Es posible transferir a un bloque, pero no de una manera que evite las declaraciones con la inicialización. Un programa que salta de un punto donde una variable local con duración de almacenamiento automático no está dentro del alcance a un punto donde está dentro del alcance, está mal formado a menos que la variable tenga un tipo de POD (3.9) y se declare sin un inicializador (8.5).
El código para ilustrar esta regla:
#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;
}
El código para mostrar el efecto inicializador:
#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;
}
Escribí esta respuesta originalmente para esta pregunta . Sin embargo cuando lo terminé encontré que la respuesta ha sido cerrada. Así que lo publiqué aquí, tal vez a alguien a quien le gusten las referencias a los estándares les resulte útil.
Código original en cuestión:
int i;
i = 2;
switch(i)
{
case 1:
int k;
break;
case 2:
k = 1;
cout<<k<<endl;
break;
}
En realidad hay 2 preguntas:
1. ¿Por qué puedo declarar una variable después de la etiqueta del case
?
Es porque en C ++ la etiqueta tiene que estar en forma:
N3337 6.1 / 1
declaración-etiquetada:
...
- constato-especificador de atributo-seqopt
constant-expression
:statement
...
Y en C++
declaración también se considera como una declaración (a diferencia de C
):
N3337 6/1:
declaración :
...
Declaración
...
2. ¿Por qué puedo saltar sobre la declaración de variables y luego usarla?
Porque: N3337 6.7 / 3
Es posible transferir a un bloque, pero no de una manera que evite las declaraciones con la inicialización . Un programa que salta (la transferencia de la condición de una declaración de cambio a una etiqueta de caso se considera un salto a este respecto).
desde un punto en el que una variable con duración de almacenamiento automático no está dentro del alcance hasta un punto en el que está dentro del alcance está mal formada, a menos que la variable tenga un tipo escalar , un tipo de clase con un constructor predeterminado trivial y un destructor trivial, una versión calificada de cv de uno de estos tipos, o una matriz de uno de los tipos anteriores y se declara sin un inicializador (8.5).
Como k
es de tipo escalar , y no se inicializa en el punto de declaración, es posible saltar sobre su declaración. Esto es semánticamente equivalente:
goto label;
int x;
label:
cout << x << endl;
Sin embargo, eso no sería posible si x
se inicializara en el punto de declaración:
goto label;
int x = 58; //error, jumping over declaration with initialization
label:
cout << x << endl;
Esta pregunta se etiquetó originalmente como [C] y [C ++] al mismo tiempo. El código original es de hecho inválido tanto en C como en C ++, pero por razones no relacionadas completamente diferentes. Creo que este detalle importante fue omitido (o confuso) por las respuestas existentes.
En C ++ este código no es válido porque el
case ANOTHER_VAL:
label salta al ámbito de la variablenewVal
pasar por la inicialización. Los saltos que omiten la inicialización de objetos locales son ilegales en C ++. Esta parte del problema se aborda correctamente en la mayoría de las respuestas.Sin embargo, en el lenguaje C, omitir la inicialización de la variable no es un error. Saltar al alcance 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 variablenewVal
. 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 bloque adicional {}
soluciona los problemas tanto de C ++ como de C, aunque estos problemas son muy diferentes. En el lado de C ++, restringe el alcance de newVal
, asegurándose de que el case ANOTHER_VAL:
ya no salte a ese alcance, lo que elimina el problema de C ++. En el lado C, extra {}
introduce una declaración compuesta, haciendo que la etiqueta case VAL:
aplique a una declaración, lo que elimina el problema C.
En el caso C, el problema puede resolverse fácilmente sin el
{}
. Solo agregue una declaración vacía después de la etiquetacase VAL:
delcase VAL:
y el código será válidoswitch (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 C, sigue siendo inválido desde el punto de vista C ++.
Simétricamente, en el caso de C ++, el problema se puede resolver fácilmente sin
{}
. Simplemente quite el inicializador de la declaración de variable y el código será válidoswitch (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 de C.
Hasta ahora las respuestas han sido para C ++.
Para C ++, no puedes saltar una inicialización. Puede hacerlo en C. Sin embargo, en C, una declaración no es una declaración, y las etiquetas de los casos deben ir seguidas de declaraciones.
Entonces, válido (pero feo) C, inválido 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();
}
Por el contrario, en C ++, una declaración es una declaración, por lo que la siguiente es C ++ válida, C inválida
switch (something)
{
case 1:
do_something();
break;
case 2:
int i = 12;
do_something_else();
}
Interesante que esto está bien:
switch (i)
{
case 0:
int j;
j = 7;
break;
case 1:
break;
}
... pero esto no es
switch (i)
{
case 0:
int j = 7;
break;
case 1:
break;
}
Entiendo que una solución es bastante simple, pero aún no entiendo por qué el primer ejemplo no molesta al compilador. Como se mencionó anteriormente (2 años antes, jeje), la declaración no es lo que causa el error, incluso a pesar de la lógica. La inicialización es el problema. Si la variable se inicializa y se declara en las diferentes líneas, se compila.
La mayoría de las respuestas hasta ahora son erróneas en un aspecto: puede declarar variables después de la declaración del caso, pero no puede inicializarlas:
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 usar llaves para crear un alcance para su caso.
La sección completa del conmutador es un contexto de declaración única. No se puede declarar una variable en una declaración de caso como esa. Intenta esto en su lugar:
switch (val)
{
case VAL:
{
// This will work
int newVal = 42;
break;
}
case ANOTHER_VAL:
...
break;
}
Las nuevas variables pueden ser desclasificadas solo en el ámbito del bloque. Necesitas escribir algo como esto:
case VAL:
// This will work
{
int newVal = 42;
}
break;
Por supuesto, newVal solo tiene alcance dentro de las llaves ...
Saludos, Ralph
Mi truco favorito para cambiar el mal es usar un if (0) para omitir una etiqueta de caso no deseada.
switch(val)
{
case 0:
// Do something
if (0) {
case 1:
// Do something else
}
case 2:
// Do something in all cases
}
Pero muy malvado.
No puede hacer esto, porque las etiquetas de case
son en realidad puntos de entrada en el bloque contenedor.
Esto se ilustra más claramente con el 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 las etiquetas de case
ignoran totalmente los límites de bloque. Sí, esto es malo. Pero esta es la razón por la que su ejemplo de código no funciona. Saltar a una etiqueta de case
es lo mismo que usar goto
, por lo que no se le permite saltar sobre una variable local con un constructor.
Como varios otros carteles han indicado, debe colocar un bloque propio:
switch (...) {
case FOO: {
MyObject x(...);
...
break;
}
...
}
Parece que los objetos anónimos se pueden declarar o crear en una declaración de caso de cambio por la razón de que no se puede hacer referencia y, como tal, no puede pasar al siguiente caso. Considere este ejemplo de compilaciones en GCC 4.5.3 y Visual Studio 2008 (podría ser un problema de cumplimiento aunque los expertos lo consideren)
#include <cstdlib>
struct Foo{};
int main()
{
int i = 42;
switch( i )
{
case 42:
Foo(); // Apparently valid
break;
default:
break;
}
return EXIT_SUCCESS;
}
Prueba esto:
switch (val)
{
case VAL:
{
int newVal = 42;
}
break;
}
Puede declarar variables dentro de una instrucción de cambio si inicia un nuevo bloque:
switch (thing)
{
case A:
{
int i = 0; // Completely legal
}
break;
}
El motivo está relacionado con la asignación (y la reclamación) de espacio en la pila para el almacenamiento de las variables locales.
Si su código dice "int newVal = 42", es razonable esperar que newVal nunca esté sin inicializar. Pero si pasa por esta declaración (que es lo que está haciendo), entonces eso es exactamente lo que sucede: newVal está dentro del alcance pero no ha sido asignado.
Si eso es lo que realmente querías que sucediera, entonces el lenguaje requiere hacerlo explícito diciendo "int newVal; newVal = 42;". De lo contrario, puede limitar el alcance de newVal al caso individual, que es más probable que lo que deseaba.
Puede aclarar las cosas si considera el mismo ejemplo pero con "const int newVal = 42;"
Solo quería enfatizar el point Slim . Una construcción de conmutación crea un ámbito completo de primera clase para el ciudadano. Por lo tanto, es posible declarar (e inicializar) una variable en una declaración de cambio antes de la primera etiqueta de caso, sin un par de paréntesis adicional:
switch (val) {
/* This *will* work, even in C89 */
int newVal = 42;
case VAL:
newVal = 1984;
break;
case ANOTHER_VAL:
newVal = 2001;
break;
}
Toda la instrucción switch está en el mismo ámbito. Para evitarlo, haz esto:
switch (val)
{
case VAL:
{
// This **will** work
int newVal = 42;
}
break;
case ANOTHER_VAL:
...
break;
}
Tenga en cuenta los soportes.
Un bloque de switch
no es lo mismo que una sucesión de if/else if
blocks. Me sorprende que ninguna otra respuesta lo explique claramente.
Considere esta instrucción de switch
:
switch (value) {
case 1:
int a = 10;
break;
case 2:
int a = 20;
break;
}
Puede ser sorprendente, pero el compilador no lo verá como un simple if/else if
. Producirá el siguiente código:
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
Las declaraciones de case
se convierten en etiquetas y luego se llaman con goto
. Los corchetes crean un nuevo alcance y es fácil ver ahora por qué no puede declarar dos variables con el mismo nombre dentro de un bloque de switch
.
Puede parecer extraño, pero es necesario admitir el fracaso (es decir, no usar break
para permitir que la ejecución continúe en el siguiente case
).
newVal existe en todo el alcance del interruptor, pero solo se inicializa si se golpea el miembro VAL. Si creas un bloque alrededor del código en VAL, debería estar bien.
Case
declaraciones de Case
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 corchetes definen el alcance como todo dentro de la instrucción switch
. Esto significa que te quedas con un ámbito en el que se realizará un salto adicional en el código que omite la inicialización. La forma correcta de manejar esto es definir un alcance específico para esa declaración de case
y definir su variable dentro de ella.
switch (val)
{
case VAL:
{
// This will work
int newVal = 42;
break;
}
case ANOTHER_VAL:
...
break;
}