it-swarm-eu.dev

Proč nemohou být proměnné deklarovány v příkazu switch?

Vždycky jsem se nad tím zajímal - proč nemůžete deklarovat proměnné po označení případu v příkazu switch? V C++ můžete deklarovat proměnné téměř kdekoli (a jejich deklarace blízko prvního použití je samozřejmě dobrá věc), ale následující stále nebude fungovat:

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

Výše uvedené mi dává následující chybu (MSC):

inicializace 'newVal' je přeskočena štítkem 'case'

Zdá se, že to je omezení i v jiných jazycích. Proč je to takový problém?

874
Rob

Příkazy Case jsou pouze popisky. To znamená, že kompilátor to interpretuje jako skok přímo na štítek. V C++ je zde problém. Vaše složené závorky definují obor jako všechno uvnitř příkazu switch. To znamená, že vám zůstane obor, ve kterém bude skok proveden dále do kódu přeskočujícího inicializaci. Správný způsob, jak to zvládnout, je definovat obor specifický pro tento příkaz case a definovat v něm proměnnou.

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

Tato otázka je byl původně označen jako [C] a [C++] současně. Původní kód je skutečně neplatný jak v C, tak v C++, ale z úplně jiných nesouvisejících důvodů. Věřím, že tento důležitý detail byl minulými odpověďmi vynechán (nebo zmaten).

  • V C++ je tento kód neplatný, protože štítek case ANOTHER_VAL: skočí do rozsahu proměnné newVal obejít jeho inicializaci. Skoky, které inicializují obejít lokální objekty, jsou v C++ nezákonné. Většina odpovědí na tuto stránku problému správně reaguje.

  • Inicializace proměnné v jazyce C však není chybou. Přeskočení do rozsahu proměnné před její inicializací je v C legální. Znamená to jednoduše, že proměnná je ponechána neinicializovaná. Původní kód nelze kompilovat v C z úplně jiného důvodu. Štítek case VAL: v původním kódu je připojen k deklaraci proměnné newVal. Prohlášení v jazyce C nejsou prohlášení. Nelze je označit. A to je to, co způsobuje chybu, když je tento kód interpretován jako kód C.

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

Přidání dalšího bloku {} opraví problémy C++ i C, přestože se tyto problémy velmi liší. Na straně C++ omezuje rozsah newVal, přičemž se ujistí, že case ANOTHER_VAL: již neskočí do tohoto rozsahu, což vylučuje problém C++. Na straně C, který navíc {} zavádí složený příkaz, čímž se štítek case VAL: použije na příkaz, který eliminuje problém C.

  • V případě C lze problém snadno vyřešit bez {}. Po označení case VAL: jednoduše přidejte prázdný příkaz a kód se stane platným

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

    Všimněte si, že i když je nyní platný z pohledu C, zůstává z pohledu C++ neplatný.

  • Symetricky v případě C++ může být problém snadno vyřešen bez {}. Jednoduše odstraňte inicializátor z deklarace proměnné a kód bude platný

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

    Všimněte si, že i když je nyní platný z pohledu C++, zůstává z pohledu C neplatný.

297
AnT

OK. Pouhé vysvětlení tohoto přísně nemá nic společného s prohlášením. Týká se pouze „skákání přes inicializaci“ (ISO C++ '03 6.7/3).

Mnoho příspěvků zde uvedlo, že přeskočení deklarace může vést k tomu, že proměnná „nebude deklarována“. To není pravda. Objekt POD může být deklarován bez inicializátoru, ale bude mít neurčitou hodnotu. Například:

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

Pokud je objektem nepodporujícím nebo agregovaným, kompilátor implicitně přidá inicializátor, a proto není možné přeskočit takové prohlášení:

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

Toto omezení není omezeno na příkaz switch. Je také chybou použít 'goto' pro přeskočení inicializace:

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

Trochu maličkosti je, že se jedná o rozdíl mezi C++ a C. V C není skok přeskočit inicializaci.

Jak již uvedli ostatní, řešením je přidat vnořený blok, takže životnost proměnné je omezena na označení jednotlivých případů.

131
Richard Corden

Celý příkaz přepínače je ve stejném rozsahu. Chcete-li to obejít, postupujte takto:

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

    case ANOTHER_VAL:
      ...
    break;
}

Poznámka závorky.

35
Mark Ingram

Po přečtení všech odpovědí a nějakém dalším výzkumu dostanu pár věcí.

Case statements are only 'labels'

V C, podle specifikace,

§6.8.1 Prohlášení označená štítkem:

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

V C neexistuje žádná klauzule, která by umožňovala „označenou deklaraci“. Není to jen část jazyka.

Tak

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

Toto se nebude kompilovat , viz http://codepad.org/YiyLQTYw . GCC dává chybu:

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

Dokonce

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

toto je také nekompilující , viz http://codepad.org/BXnRD3b . Tady také dostávám stejnou chybu.


V C++ podle specifikace

deklarovaná deklarace je povolena, ale inicializace označená není povolena.

Viz http://codepad.org/ZmQ0IyDG .


Řešení takové podmínky je dvě

  1. Buď použijte nový obor pomocí {}

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
    
  2. Nebo použijte fiktivní prohlášení se štítkem

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
    
  3. Deklarovat proměnnou před switchem () a inicializovat ji s různými hodnotami v případě, pokud splňuje váš požadavek

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

Další věci s příkazem switch

Nikdy nevypisujte do přepínače žádné příkazy, které nejsou součástí žádného štítku, protože nikdy nebudou provedeny:

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

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

    default:
        break;
}

Viz http://codepad.org/PA1quYX .

28
Jeegar Patel

Nemůžete to udělat, protože štítky case jsou ve skutečnosti pouze vstupními body do obsahujícího bloku.

Nejjasněji to ilustruje Duffovo zařízení . Tady je nějaký kód z Wikipedie:

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

Všimněte si, jak štítky case ignorují hranice bloků. Ano, to je zlé. Proto proto váš příklad kódu nefunguje. Skok na štítek case je stejný jako u goto, takže není dovoleno přeskočit lokální proměnnou pomocí konstruktoru.

Jak již naznačilo několik dalších plakátů, musíte vložit vlastní blok:

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

Většina odpovědí doposud se v jednom ohledu mýlí: vy můžete deklarovat proměnné po příkazu case, ale nemůžete inicializovat je:

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

Jak již bylo zmíněno, pěkným způsobem je použití šle k vytvoření prostoru pro váš případ.

16
MrZebra

Můj oblíbený trik se zlým přepínáním je použít if (0) pro přeskočení nechtěného štítku případu.

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

Ale velmi zlé.

12
Jeremy

Zkuste to:

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

Proměnné můžete deklarovat v příkazu switch pokud začnete nový blok:

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

Důvodem je přidělení (a regenerace) prostoru na zásobníku pro uložení místní proměnné (proměnných).

7
Seb Rose

Zvážit:

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

Při absenci break příkazů je někdy newVal deklarován dvakrát, a vy nevíte, jestli to dělá až za běhu. Myslím, že omezení je kvůli tomuto druhu zmatku. Jaký by byl rozsah působnosti newVal? Konvence by diktovala, že by to byl celý spínací blok (mezi závorkami).

Nejsem programátor C++, ale v C:

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

Funguje to dobře. Deklarování proměnné uvnitř přepínacího bloku je v pořádku. Prohlášení po strážci případu není.

6
slim

Celá část přepínače je kontextem jediného prohlášení. V takovém případu nemůžete deklarovat proměnnou. Zkuste to místo toho:

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

Pokud váš kód říká "int newVal = 42", pak byste rozumně očekávali, že newVal není nikdy neinicializován. Pokud však přejdete na toto tvrzení (což je to, co děláte), pak se to přesně stane - newVal je v rozsahu, ale nebyl přidělen.

Pokud je to to, k čemu jste opravdu chtěli dojít, pak jazyk vyžaduje, aby byl výslovný vyslovením „int newVal; newVal = 42;“. Jinak můžete omezit rozsah newVal na jediný případ, což je pravděpodobně to, co jste chtěli.

Může to vyjasnit, pokud uvažujete stejný příklad, ale s „const int newVal = 42;“

3
Mike F

Jen jsem chtěl zdůraznit tenký 's bod . Konstrukce přepínače vytváří celý rozsah, prvotřídní občan. Je tedy možné deklarovat (a inicializovat) proměnnou v příkazu switch před prvním označením případu bez další dvojice závorek:

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

Zajímavé, že je to v pořádku:

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

case 1:  
    break;
}

... ale to není:

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

case 1:  
    break;
}

Mám dojem, že oprava je dostatečně jednoduchá, ale zatím nechápu, proč první příklad kompilátoru neobtěžuje. Jak bylo zmíněno dříve (o 2 roky dříve hehe), prohlášení není to, co způsobuje chybu, a to i přes logiku. Inicializace je problém. Pokud je proměnná inicializována a deklarována na různých řádcích, zkompiluje se.

3
Dan

Tuto odpověď jsem napsal původně pro tato otázka . Když jsem to však dokončil, zjistil jsem, že odpověď byla uzavřena. Takže jsem to zveřejnil tady, možná to bude mít někdo, kdo má rád odkazy na standard.

Původní kód:

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

Ve skutečnosti existují 2 otázky:

1. Proč mohu deklarovat proměnnou za case label?

Je to proto, že v C++ musí být štítek ve formě:

N3337 6,1/1

označené prohlášení:

...

  • atribut-specifier-seqopt caseconstant-expression: statement

...

A v C++ je prohlášení také považováno za prohlášení (na rozdíl od C):

N3337 6/1:

prohlášení :

...

prohlášení-prohlášení

...

2. Proč mohu přeskočit deklaraci proměnné a poté ji použít?

Protože: N3337 6,7/3

Je možné přenést do bloku , ale ne způsobem, který obchází deklarace s inicializací . Program, který skočí ( převod z stavu příkazu přepínače na označení případu, se považuje za skok v tomto ohledu.)

od bodu, kdy proměnná s dobou automatického ukládání není v rozsahu do bodu, kde je v rozsahu, je špatně tvarovaná pokud proměnná nemá skalární typ , typ třídy s triviálním výchozím konstruktorem a triviální destruktorem, cv-kvalifikovanou verzi jednoho z těchto typů nebo pole jednoho z předchozích typy a je deklarována bez inicializátoru (8.5).

Protože k je skalárního typu a není inicializován v okamžiku skoku deklarace přes jeho deklaraci, je možné. To je sémanticky ekvivalent:

goto label;

int x;

label:
cout << x << endl;

To by však nebylo možné, pokud by x bylo inicializováno v okamžiku prohlášení:

 goto label;

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

    label:
    cout << x << endl;
3
PcAF

Dosud byly odpovědi na C++.

U C++ nelze přeskočit inicializaci. Můžete to provést v C. V C však prohlášení není prohlášení a za popisky případů musí následovat prohlášení.

Takže platný (ale ošklivý) C, neplatný 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();
}

Naopak v C++ je prohlášení deklarací, takže následující platí C++, neplatné C

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

Nové proměnné lze deklarovat pouze v rozsahu bloku. Musíte napsat něco takového:

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

Samozřejmě, newVal má rozsah pouze v rovnátkách ...

Na zdraví, Ralphe

1
Ralph Hempel

Blok switchnení to stejné jako posloupnost bloků if/else if. Překvapuje mě, že žádná jiná odpověď to nevysvětluje jasně.

Zvažte tento příkaz switch:

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

Může to být překvapivé, ale kompilátor jej neuvidí jako jednoduchý if/else if. Vytvoří následující kód:

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

Příkazy case jsou převedeny na štítky a poté zavolány pomocí goto. Závorky vytvářejí nový obor a nyní je snadné pochopit, proč v rámci bloku switch nelze deklarovat dvě proměnné se stejným názvem.

Může to vypadat divně, ale je nutné podporovat pád (tj. Nepoužívat break, aby provádění pokračovalo na další case).

1
Dalmas

newVal existuje v celém rozsahu přepínače, ale je inicializován, pouze pokud je zasažena končetina VAL. Pokud vytvoříte kolem kódu v VAL, mělo by to být v pořádku.

0
marijne

Standard C++ má: Je možné přenést do bloku, ale ne způsobem, který obchází deklarace s inicializací. Program, který skočí z bodu, ve kterém lokální proměnná s dobou automatického ukládání není v rozsahu do bodu, kde je v rozsahu, je špatně vytvořen, pokud proměnná nemá typ POD (3.9) a je deklarována bez inicializátoru (8.5).

Kód ilustrující toto pravidlo:

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

Kód zobrazující efekt inicializátoru:

#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
Jingguo Yao