visual - usar case c++




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

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

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 chaves para criar um escopo para o seu caso.

Eu sempre quis saber isso - por que você não pode declarar variáveis ​​após um rótulo de caso em uma instrução switch? Em C ++ você pode declarar variáveis ​​praticamente em qualquer lugar (e declará-las próximas ao primeiro uso é obviamente uma coisa boa), mas o seguinte ainda não funciona:

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

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

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

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


A seção inteira do switch é um contexto de declaração única. Você não pode declarar uma variável em uma declaração de caso como essa. Tente isso:

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

C ++ Standard tem: É possível transferir para um bloco, mas não de uma maneira que ignora declarações com inicialização. Um programa que salta de um ponto em que uma variável local com duração de armazenamento automático não está no escopo para um ponto em que está no escopo é malformada a menos que a variável tenha 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 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;
}

Considerar:

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

Na ausência de instruções de quebra, às vezes o newVal é declarado duas vezes e você não sabe se funciona até o tempo de execução. Meu palpite é que a limitação é por causa desse tipo de confusão. Qual seria o escopo do newVal? Convenção ditaria que seria todo o bloco de comutação (entre as chaves).

Eu não sou nenhum programador C ++, mas em C:

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

Funciona bem. Declarar uma variável dentro de um bloco de comutação é bom. Declarando depois que um protetor de caso não é.


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

Muitos dos posts aqui mencionaram que saltar sobre a declaração pode resultar na variável "não sendo declarada". Isso não é verdade. Um objeto POD pode ser declarado sem um 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 é um não-POD ou agregado, o compilador implicitamente adiciona um inicializador e, portanto, não é possível saltar sobre 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 saltar sobre uma inicialização:

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

Um pouco de trivialidade é que esta é uma diferença entre C ++ e C. Em C, não é um erro saltar sobre 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 de caso individual.


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

  • Em C ++, este código é inválido porque o rótulo case ANOTHER_VAL: 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 ++. Esse lado do problema é 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 sobre sua inicialização é legal em C. Significa simplesmente que a variável é deixada não inicializada. O código original não compila em C por um motivo completamente diferente. Label case VAL: no código original é anexado à declaração de variável newVal . Em declarações de linguagem C não são declarações. Eles não podem ser rotulados. E isso é o que causa o erro quando este 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;
    }

Adicionar um bloco {} extra corrige problemas C ++ e C, mesmo que esses problemas sejam muito diferentes. No lado do C ++, ele restringe o escopo do newVal , certificando-se de que o case ANOTHER_VAL: não salte mais nesse escopo, o que elimina o problema do C ++. No lado C, esse extra {} introduz uma instrução composta, tornando assim o case VAL: label para ser aplicado a uma instrução, o que elimina a questão C.

  • No caso C, o problema pode ser facilmente resolvido sem o {} . Basta adicionar uma instrução vazia após o case VAL: label 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;
    }

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

  • Simetricamente, no caso C ++, o problema pode ser facilmente resolvido sem o {} . Basta remover o inicializador da declaração de 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 seja agora válido do ponto de vista do C ++, ele permanece inválido do ponto de vista C.


Eu escrevi esta resposta originalmente para esta pergunta . No entanto, quando terminei, descobri que a resposta foi encerrada. Então eu postei aqui, talvez alguém que goste de referências ao padrão achará ú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 duas perguntas:

1. Por que eu posso declarar uma variável após o rótulo do case ?

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

N3337 6.1 / 1

declaração rotulada:

...

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

...

E na declaração de declaração C++ também é considerada 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 de variáveis ​​e depois usá-la?

Porque: N3337 6.7 / 3

É possível transferir para um bloco, mas não de uma maneira que ignora declarações com inicialização . Um programa que salta (A transferência da condição de uma instrução switch para uma etiqueta de caso é considerada um salto nesse sentido.)

de um ponto em que uma variável com duração de armazenamento automático não está no escopo para um ponto em que está no escopo malformada a menos que a variável tenha um tipo escalar , um tipo de classe com um construtor padrão trivial e um destruidor trivial, uma versão qualificada cv de um desses tipos, ou uma matriz de um dos tipos anteriores e é declarada sem um inicializador (8.5).

Como k é do tipo escalar e não é inicializado no ponto de declaração, o salto é possível. Isto é semanticamente equivalente:

goto label;

int x;

label:
cout << x << endl;

No entanto, isso não seria possível se x fosse inicializado no ponto de declaração:

 goto label;

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

    label:
    cout << x << endl;

Eu só queria enfatizar o point . Uma construção de 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 de caso, 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;
}

Novas variáveis ​​podem ser decalares 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 dos aparelhos ...

Felicidades, Ralph


O meu truque de mudança maligna favorito é usar if (0) para pular uma etiqueta de case indesejada.

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

Mas muito mal.


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

Se isso é o que você realmente queria que acontecesse, então a linguagem precisa explicitar dizendo "int newVal; newVal = 42;". Caso contrário, você pode limitar o escopo de newVal ao único caso, o que é mais provável o que você queria.

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


Tente isto:

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

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

Considere esta declaração de switch :

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

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

As declarações case são convertidas em labels 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 bloco de switch .

Pode parecer estranho, mas é necessário apoiar a queda (ou seja, não usar o break para permitir que a execução continue no próximo case ).


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

Isso é mais claramente ilustrado pelo dispositivo de Duff . Aqui está algum 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 os rótulos das case ignoram totalmente os limites do bloco. Sim, isso é mal. Mas é por isso que o seu exemplo de código não funciona. Saltar para um rótulo de case é 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 um bloco próprio:

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

newVal existe em todo o escopo do switch, mas é inicializado apenas se o limiar VAL for atingido. Se você criar um bloco em torno do código em VAL, ele deverá ser OK.


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

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




switch-statement