Por que as variáveis ​​não podem ser declaradas em uma instrução switch?

StackOverflow https://stackoverflow.com/questions/92396

  •  01-07-2019
  •  | 
  •  

Pergunta

Sempre me perguntei isso - por que você não pode declarar variáveis ​​após um rótulo case em uma instrução switch?Em C++ você pode declarar variáveis ​​praticamente em qualquer lugar (e declará-las perto do primeiro uso é obviamente uma coisa boa), mas o seguinte ainda não funcionará:

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

O acima me dá o seguinte erro (MSC):

a inicialização de 'newVal' é ignorada pelo rótulo 'case'

Esta parece ser uma limitação também em outros idiomas.Por que isso é um problema tão grande?

Foi útil?

Solução

Case declarações são apenas rótulos.Isso significa que o compilador interpretará isso como um salto direto para o rótulo.Em C++, o problema aqui é de escopo.Suas chaves definem o escopo como tudo dentro do switch declaração.Isso significa que você terá um escopo onde um salto será executado no código, ignorando a inicialização.A maneira correta de lidar com isso é definir um escopo específico para aquele case declaração e defina sua variável dentro dela.

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

Outras dicas

Essa questão é foi originalmente marcado como [C] e [C++] ao mesmo tempo.O código original é realmente inválido em C e C++, mas por razões não relacionadas completamente diferentes.Acredito que este detalhe importante foi perdido (ou ofuscado) pelas respostas existentes.

  • Em C++ este código é inválido porque o case ANOTHER_VAL: rótulo salta para o escopo da variável newVal ignorando sua inicialização.Saltos que ignoram a inicialização de objetos locais são ilegais em C++.Este lado da questão é abordado corretamente pela maioria das respostas.

  • No entanto, na linguagem C, ignorar a inicialização da variável não é um erro.Saltar para o escopo de uma variável durante sua inicialização é legal em C.Significa simplesmente que a variável não foi inicializada.O código original não é compilado em C por um motivo completamente diferente.Rótulo case VAL: no código original está anexado à declaração da variável newVal.Na linguagem C, as declarações não são declarações.Eles não podem ser rotulados.E é isso que causa o erro quando esse código é interpretado como código C.

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

Adicionando um extra {} block corrige problemas de C++ e C, mesmo que esses problemas sejam muito diferentes.No lado C++, restringe o escopo de newVal, certificando-se de que case ANOTHER_VAL: não entra mais nesse escopo, o que elimina o problema do C++.No lado C aquele extra {} introduz uma declaração composta, tornando assim o case VAL: rótulo para aplicar a uma instrução, o que elimina o problema C.

  • No caso C, o problema pode ser facilmente resolvido sem o {}.Basta adicionar uma instrução vazia após o case VAL: rótulo e o código se tornará válido

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

    Observe que embora agora seja válido do ponto de vista C, permanece inválido do ponto de vista C++.

  • Simetricamente, no caso C++ o problema pode ser facilmente resolvido sem o {}.Basta remover o inicializador da declaração da variável e o código se tornará válido

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

    Observe que embora agora seja válido do ponto de vista C++, permanece inválido do ponto de vista C.

OK.Só para esclarecer isso estritamente não tem nada a ver com a declaração.Relaciona-se apenas a "pular a inicialização" (ISO C++ '03 6.7/3)

Muitas postagens aqui mencionaram que pular a declaração pode fazer com que a variável "não seja declarada".Isso não é verdade.Um objeto POD pode ser declarado sem inicializador, mas terá um valor indeterminado.Por exemplo:

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

Onde o objeto não é POD ou é agregado, o compilador adiciona implicitamente um inicializador e, portanto, não é possível pular essa declaração:

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

Essa limitação não está limitada à instrução switch.Também é um erro usar 'goto' para pular uma inicialização:

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

Uma curiosidade é que essa é uma diferença entre C++ e C.Em C, não é um erro pular a inicialização.

Como outros mencionaram, a solução é adicionar um bloco aninhado para que o tempo de vida da variável seja limitado ao rótulo do caso individual.

Toda a instrução switch está no mesmo escopo.Para contornar isso, faça o seguinte:

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

    case ANOTHER_VAL:
      ...
    break;
}

Observação os colchetes.

Depois de ler todas as respostas e mais algumas pesquisas, recebo algumas coisas.

Case statements are only 'labels'

Em C, de acordo com a especificação,

§6.8.1 Declarações rotuladas:

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

Em C não existe nenhuma cláusula que permita uma "declaração rotulada".Simplesmente não faz parte da linguagem.

Então

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

Esse não irá compilar, ver http://codepad.org/YiyLQTYw.GCC está dando um erro:

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

Até

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

isso é também não compila, ver http://codepad.org/BXnRD3bu.Aqui também estou recebendo o mesmo erro.


Em C++, de acordo com a especificação,

A declaração rotulada é permitida, mas a inicialização rotulada não é permitida.

Ver http://codepad.org/ZmQ0IyDG.


A solução para tal condição é dois

  1. Use o novo escopo usando {}

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
    
  2. Ou use uma declaração fictícia com rótulo

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
    
  3. Declare a variável antes de switch() e inicialize-a com valores diferentes na instrução case se ela atender aos seus requisitos

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

Mais algumas coisas com a instrução switch

Nunca escreva nenhuma instrução no switch que não faça parte de nenhum rótulo, pois elas nunca serão executadas:

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

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

    default:
        break;
}

Ver http://codepad.org/PA1quYX3.

Você não pode fazer isso, porque case os rótulos são, na verdade, apenas pontos de entrada no bloco que os contém.

Isto é mais claramente ilustrado por Dispositivo de Duff.Aqui está um código da 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 como o case os rótulos ignoram totalmente os limites do bloco.Sim, isso é mau.Mas é por isso que seu exemplo de código não funciona.Pulando para um case rótulo é o mesmo que usar goto, então você não tem permissão para pular uma variável local com um construtor.

Como vários outros pôsteres indicaram, você precisa colocar seu próprio bloco:

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

A maioria das respostas até agora está errada em um aspecto:você pode declarar variáveis ​​após a instrução case, mas você não pode inicialize-os:

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

Como mencionado anteriormente, uma boa maneira de contornar isso é usar colchetes para criar um escopo para o seu caso.

Meu truque favorito de troca maligna é usar um if(0) para pular um rótulo de caso indesejado.

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

Mas muito malvado.

Experimente isto:

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

Você pode declarar variáveis ​​​​dentro de uma instrução switch se você inicia um novo bloco:

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

O motivo tem a ver com a alocação (e recuperação) de espaço na pilha para armazenamento da(s) variável(is) local(is).

Considerar:

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

Na ausência de instruções break, às vezes newVal é declarado duas vezes e você não sabe se isso acontece até o tempo de execução.Meu palpite é que a limitação se deve a esse tipo de confusão.Qual seria o escopo do newVal?A convenção ditaria que seria todo o bloco de comutação (entre as chaves).

Não sou programador C++, mas em C:

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

Funciona bem.Declarar uma variável dentro de um bloco switch é bom.Declarar após um guarda de caso não é.

A seção inteira do switch é um contexto de declaração única.Você não pode declarar uma variável em uma instrução case como essa.Em vez disso, tente isto:

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

Se o seu código disser "int newVal = 42", você esperaria razoavelmente que newVal nunca fosse inicializado.Mas se você passar por cima desta afirmação (que é o que você está fazendo), então é exatamente isso que acontece - newVal está no escopo, mas não foi atribuído.

Se é isso que você realmente queria que acontecesse, então a linguagem precisa torná-lo explícito dizendo "int newVal;novoVal = 42;".Caso contrário, você poderá limitar o escopo de newVal ao caso único, que é mais provavelmente o que você desejava.

Pode esclarecer as coisas se você considerar o mesmo exemplo, mas com "const int newVal = 42;"

Eu só queria enfatizar magrode apontar.Uma construção switch cria um escopo completo de cidadão de primeira classe.Portanto, é possível declarar (e inicializar) uma variável em uma instrução switch antes do primeiro rótulo case, sem um par de colchetes adicional:

switch (val) {  
  /* This *will* work, even in C89 */
  int newVal = 42;  
case VAL:
  newVal = 1984; 
  break;
case ANOTHER_VAL:  
  newVal = 2001;
  break;
}

Até agora as respostas foram para C++.

Para C++, você não pode pular uma inicialização.Você pode em C.Entretanto, em C, uma declaração não é uma declaração, e os rótulos de caso devem ser seguidos por declarações.

Então, C válido (mas feio), C++ inválido

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 outro lado, em C++, uma declaração é uma instrução, então o seguinte é C++ válido, C inválido

switch (something)
{
  case 1:
    do_something();
    break;
  case 2:
    int i = 12;
    do_something_else();
}

Interessante que isso esteja bem:

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

case 1:  
    break;
}

...mas isso não é:

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

case 1:  
    break;
}

Entendo que uma correção é bastante simples, mas ainda não entendi por que o primeiro exemplo não incomoda o compilador.Como foi mencionado anteriormente (2 anos antes hehe), declaração não é o que causa o erro, mesmo apesar da lógica.A inicialização é o problema.Se a variável for inicializada e declarada em linhas diferentes, ela será compilada.

Eu escrevi esta resposta originalmente para essa questão.No entanto, quando terminei, descobri que a resposta estava fechada.Então postei aqui, talvez alguém que goste de referências a padrões ache útil.

Código original em questão:

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

Na verdade, existem 2 perguntas:

1.Por que posso declarar uma variável depois case rótulo?

É porque em C++ o rótulo tem que estar no formato:

N3337 6.1/1

declaração rotulada:

...

  • especificador de atributo-seqopt case constant-expression : statement

...

E em C++ declaração de declaração também é considerado como declaração (em oposição a C):

N3337 6/1:

declaração:

...

declaração-declaração

...

2.Por que posso pular a declaração da variável e usá-la?

Porque:N3337 6.7/3

É possível transferir para um bloco, mas não de uma forma que ignore as declarações com inicialização.Um programa que salta (o transferir de a condição de um instrução switch para um rótulo case é considerado um salto neste aspecto.)

de um ponto onde uma variável com duração de armazenamento automática não está no escopo até um ponto onde está no escopo está mal formada a menos que a variável tenha tipo escalar, tipo de classe com um construtor padrão trivial e um destruidor trivial, uma versão qualificada por CV de um desses tipos, ou uma matriz de um dos tipos anteriores e é declarada sem um inicializador (8.5).

Desde k é de tipo escalar, e não é inicializado no ponto da declaração, é possível saltar sobre sua declaração.Isso é semanticamente equivalente:

goto label;

int x;

label:
cout << x << endl;

Contudo, isso não seria possível, se x foi inicializado no ponto da declaração:

 goto label;

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

    label:
    cout << x << endl;

Novas variáveis ​​podem ser decalcadas apenas no escopo do bloco.Você precisa escrever algo assim:

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

Claro, newVal só tem escopo dentro do aparelho...

Felicidades, Ralf

A switch bloquear não é o mesmo que uma sucessão de if/else if blocos. Estou surpreso que nenhuma outra resposta explique isso claramente.

Considere isto switch declaração :

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

Pode ser surpreendente, mas o compilador não verá isso como uma simples if/else if.Ele produzirá o seguinte 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

O case instruções são convertidas em rótulos e então chamadas com goto.Os colchetes criam um novo escopo e é fácil ver agora porque você não pode declarar duas variáveis ​​com o mesmo nome dentro de um switch bloquear.

Pode parecer estranho, mas é preciso apoiar Cair em (ou seja, não usar break para deixar a execução continuar para o próximo case).

newVal existe em todo o escopo do switch, mas só é inicializado se o membro VAL for atingido.Se você criar um bloco em torno do código em VAL, tudo ficará bem.

O padrão C++ tem:É possível transferir para um bloco, mas não de uma forma que ignore as declarações com inicialização.Um programa que salta de um ponto onde uma variável local com duração de armazenamento automática não está no escopo para um ponto onde está no escopo é mal formado, a menos que a variável tenha o tipo POD (3.9) e seja declarada sem um inicializador (8.5).

O código para ilustrar esta regra:

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

O código para mostrar o efeito do 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;
}

Acredito que o problema em questão é que a instrução foi ignorada e você tentou usar o var em outro lugar, ela não seria declarada.

Parece que objetos anônimos pode ser declarados ou criados em uma instrução switch case porque não podem ser referenciados e, como tal, não podem passar para o próximo caso.Considere este exemplo compilado no GCC 4.5.3 e no Visual Studio 2008 (pode ser um problema de conformidade, portanto, especialistas, por favor, avaliem)

#include <cstdlib>

struct Foo{};

int main()
{
    int i = 42;

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

    default:
        break;
    }
    return EXIT_SUCCESS;
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top