Dlaczego zmiennych nie można zadeklarować w instrukcji switch?

944

Zawsze się nad tym zastanawiałem - dlaczego nie możesz zadeklarować zmiennych po etykiecie sprawy w instrukcji switch? W C ++ możesz deklarować zmienne niemal wszędzie (a deklarowanie ich przy pierwszym użyciu jest oczywiście dobrą rzeczą), ale następujące działania nadal nie będą działać:

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

Powyższe daje mi następujący błąd (MSC):

inicjalizacja „newVal” jest pomijana przez etykietę „case”

Wydaje się, że jest to ograniczenie także w innych językach. Dlaczego to taki problem?

Obrabować
źródło
10
Aby uzyskać wyjaśnienie oparte na gramatyce C BNF, zobacz stackoverflow.com/questions/1180550/weird-switch-error-in-obj-c/…
johne
Oto naprawdę dobra lektura na temat instrukcji i etykiet przełączników (ABC :) w ogóle.
Etherealone
4
Powiedziałbym: „Dlaczego zmiennych nie można zainicjować w instrukcji switch, a nie zadeklarować”. Po prostu zadeklarowanie zmiennej daje mi tylko ostrzeżenie w MSVC.
ZoomIn

Odpowiedzi:

1141

Caseinstrukcje są tylko etykietami . Oznacza to, że kompilator zinterpretuje to jako skok bezpośrednio do etykiety. W C ++ problemem jest tutaj zakres. Twoje nawiasy klamrowe definiują zakres jako wszystko w switchinstrukcji. Oznacza to, że masz zakres, w którym skok zostanie wykonany dalej w kodzie pomijając inicjalizację.

Prawidłowym sposobem radzenia sobie z tym jest zdefiniowanie zakresu specyficznego dla tej caseinstrukcji i zdefiniowanie w niej zmiennej:

switch (val)
{   
case VAL:  
{
  // This will work
  int newVal = 42;  
  break;
}
case ANOTHER_VAL:  
...
break;
}
TJ Seabrooks
źródło
94
W stosunku do otwarcia nowego zakresu - sprzyjają czytelności i spójności w kodzie. W dawnych czasach mogłeś automatycznie otrzymać „dodatkową” ramkę stosu, ale teraz nie powinno tak być w przypadku żadnego przyzwoitego kompilatora optymalizującego.
Tall Jeff,
10
Zgadzam się z Jeffem - zbyt łatwo jest „założyć” zasięg podczas czytania instrukcji switch ze względu na styl wcięcia, którego używa większość ludzi. Moim własnym stylem jest zawsze otwieranie nowego zakresu dla każdej sprawy / wartości domyślnej, jeśli ma więcej niż jedną linię.
Licytuje
39
workmad3 - Czy możesz w ogóle znaleźć kompilator C ++, który wygeneruje nową ramkę stosu, jeśli nie zadeklarujesz żadnych nowych zmiennych? Martwiłeś mnie krótko, ale żaden z G ++ 3.1, Visual C ++ 7 ani Intel C ++ 8 nie wygeneruje żadnego kodu dla nowych zakresów, w których nie deklarujesz żadnych zmiennych.
Chris Jefferson
10
@ workmad3, wprowadzając nowy blok nawiasów klamrowych, nie powoduje nowej ramki stosu stackoverflow.com/questions/2759371/…
MTVS 14.01.2013
3
@TallJef Nie wiem o jakich „dawnych czasach” mówisz. Nigdy nie spotkałem kompilatora, w którym całe miejsce na stosie dla metody nie jest przydzielane po wprowadzeniu metody przez 40 lat.
user207421 10.04.17
331

To pytanie jest pierwotnie oznaczone jako [c] i [C ++] w tym samym czasie. Oryginalny kod jest rzeczywiście niepoprawny zarówno w C, jak i C ++, ale z zupełnie innych niezwiązanych powodów.

  • W C ++ ten kod jest nieprawidłowy, ponieważ case ANOTHER_VAL:etykieta przeskakuje w zakres zmiennej, newValpomijając jej inicjalizację. Skoki, które omijają inicjalizację automatycznych obiektów, są nielegalne w C ++. Ta strona problemu jest poprawnie rozwiązana przez większość odpowiedzi.

  • Jednak w języku C pominięcie inicjalizacji zmiennej nie jest błędem. Przeskakiwanie do zakresu zmiennej podczas jej inicjalizacji jest legalne w C. Oznacza to po prostu, że zmienna pozostaje niezainicjowana. Oryginalny kod nie kompiluje się w C z zupełnie innego powodu. Etykieta case VAL:w oryginalnym kodzie jest dołączona do deklaracji zmiennej newVal. W języku C deklaracje nie są instrukcjami. Nie można ich oznakować. I to powoduje błąd, gdy ten kod jest interpretowany jako kod C.

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

Dodanie dodatkowego {}bloku rozwiązuje zarówno problemy z C ++, jak i C, nawet jeśli problemy te są bardzo różne. Po stronie C ++ ogranicza zakres newVal, upewniając się, że case ANOTHER_VAL:nie przeskakuje już do tego zakresu, co eliminuje problem z C ++. Po stronie C, która dodatkowo {}wprowadza instrukcję złożoną, dzięki czemu case VAL:etykieta ma zastosowanie do instrukcji, co eliminuje problem C.

  • W przypadku C problem można łatwo rozwiązać bez {}. Wystarczy dodać pustą instrukcję po case VAL:etykiecie, a kod stanie się prawidłowy

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

    Zauważ, że pomimo tego, że jest teraz poprawny z punktu widzenia C, pozostaje nieważny z punktu widzenia C ++.

  • Symetrycznie, w przypadku C ++ problem można łatwo rozwiązać bez {}. Wystarczy usunąć inicjalizator z deklaracji zmiennej, a kod stanie się prawidłowy

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

    Zauważ, że pomimo tego, że jest teraz poprawny z punktu widzenia C ++, pozostaje niepoprawny z punktu widzenia C.

Mrówka
źródło
4
@AnT: Rozumiem, dlaczego ten, który naprawia C ++, nie ma zastosowania do C; jednak nie jestem w stanie zrozumieć, jak to rozwiązuje problem C ++ polegający na pominięciu inicjalizacji? Czy nadal nie pominie deklaracji i przypisania, newValkiedy do niej przejdzie ANOTHER_VAL?
legends2k
13
@ legends2k: Tak, nadal go pomija. Kiedy jednak mówię „rozwiązuje problem”, mam na myśli to, że naprawia błąd kompilatora C ++ . W C ++ pomijanie deklaracji skalarnej za pomocą inicjatora jest nielegalne , ale idealnie jest pominąć deklarację skalarną bez inicjatora . W case ANOTHER_VAL:punkcie newValwidoczna jest zmienna , ale o nieokreślonej wartości.
AnT
3
Fascynujący. Znalazłem to pytanie po przeczytaniu §A9.3: Compound Statementz K&R C (drugie wydanie). W pozycji wspomniano o technicznej definicji wyrażenia złożonego, którym jest {declaration-list[opt] statement-list[opt]}. Zdezorientowany, ponieważ myślałem, że deklaracja była oświadczeniem, przejrzałem ją i od razu znalazłem to pytanie, przykład, w którym wspomniana rozbieżność staje się widoczna i faktycznie psuje program. Sądzę, że innym rozwiązaniem (dla C) byłoby umieszczenie innej deklaracji (być może instrukcji zerowej?) Przed deklaracją, aby spełnić deklarację z etykietą .
Braden Best
Ups, właśnie zauważyłem, że zaproponowane przeze mnie rozwiązanie wyrażenia zerowego jest już w twojej odpowiedzi. Nieważne więc.
Braden Best
3
Warto zauważyć, że poprawka dodania pustej instrukcji działa tylko dla C99 i późniejszych. W C89 zmienne muszą być deklarowane na początku ich otaczającego bloku.
Arthur Tacca,
136

Ok. Wyjaśnienie tego ściśle nie ma nic wspólnego z deklaracją. Odnosi się tylko do „przeskakiwania inicjalizacji” (ISO C ++ '03 6.7 / 3)

Wiele postów tutaj wspomniało, że przeskakiwanie deklaracji może spowodować, że zmienna „nie zostanie zadeklarowana”. To nie jest prawda. Obiekt POD można zadeklarować bez inicjatora, ale będzie on miał nieokreśloną wartość. Na przykład:

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

Jeśli obiekt nie jest POD lub agreguje, kompilator domyślnie dodaje inicjator, więc nie można przeskoczyć takiej deklaracji:

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

To ograniczenie nie ogranicza się do instrukcji switch. Błędem jest także użycie „goto” do przeskoczenia inicjalizacji:

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

Ciekawostką jest to, że jest to różnica między C ++ i C. W C przeskakiwanie inicjalizacji nie jest błędem.

Jak wspomnieli inni, rozwiązaniem jest dodanie zagnieżdżonego bloku, aby czas życia zmiennej był ograniczony do indywidualnej etykiety przypadku.

Richard Corden
źródło
2
„Błąd przeskakiwania podczas inicjalizacji” ??? Nie z moim GCC. Może wyświetlać ostrzeżenie „j może być zastosowane w jednostkach” przy użyciu j poniżej etykiety, ale nie ma błędu. Jednak w przypadku przełącznika występuje błąd (błąd twardy, a nie słabe ostrzeżenie).
Mecki
9
@Mekki: W C ++ jest to nielegalne. ISO C ++ '03 - 6.7 / 3: „... Program, który skacze z miejsca, w którym zmienna lokalna z automatycznym czasem przechowywania nie wchodzi w zakres, do punktu, w którym jest objęty zakresem, jest źle sformułowany, chyba że zmienna ma typ POD (3.9) i jest zadeklarowany bez inicjatora (8.5). ”
Richard Corden
1
Tak, ale nie jest to nielegalne w C (przynajmniej gcc twierdzi, że nie jest). j będzie niezainicjowany (ma pewną liczbę losową), ale kompilator go skompiluje. Jednak w przypadku instrukcji switch kompilator nawet jej nie skompiluje i nie widzę różnicy między przypadkiem goto / label a przypadkiem przełącznika.
Mecki
8
@ Mecki: Zasadniczo zachowanie jednego kompilatora niekoniecznie odzwierciedla to, co jest dozwolone przez język. Sprawdziłem zarówno C'90, jak i C'99 i oba standardy zawierają przykład z przeskoczeniem inicjalizacji w instrukcji switch.
Richard Corden
38

Cała instrukcja switch ma ten sam zakres. Aby obejść ten problem, wykonaj następujące czynności:

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

    case ANOTHER_VAL:
      ...
    break;
}

Zwróć uwagę na nawiasy.

Mark Ingram
źródło
30

Po przeczytaniu wszystkich odpowiedzi i kilku dalszych badań otrzymuję kilka rzeczy.

Case statements are only 'labels'

W C, zgodnie ze specyfikacją,

§6.8.1 Stwierdzenia oznaczone:

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

W C nie ma żadnej klauzuli, która dopuszcza „deklarację z etykietą”. To po prostu nie jest częścią języka.

Więc

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

To się nie skompiluje , patrz http://codepad.org/YiyLQTYw . GCC podaje błąd:

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

Parzysty

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

to również nie jest kompilowane , patrz http://codepad.org/BXnRD3bu . Tutaj również pojawia się ten sam błąd.


W C ++, zgodnie ze specyfikacją,

etykieta-deklaracja jest dozwolona, ​​ale etykieta -inicjalizacja nie jest dozwolona.

Zobacz http://codepad.org/ZmQ0IyDG .


Rozwiązaniem takiego stanu są dwa

  1. Użyj nowego zakresu za pomocą {}

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
  2. Lub użyj fałszywego oświadczenia z etykietą

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
  3. Zadeklaruj zmienną przed switch () i zainicjuj ją z różnymi wartościami w instrukcji case, jeśli spełnia twoje wymagania

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

Więcej rzeczy z instrukcją switch

Nigdy nie pisz w przełączniku żadnych instrukcji, które nie są częścią żadnej etykiety, ponieważ nigdy nie zostaną wykonane:

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

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

    default:
        break;
}

Zobacz http://codepad.org/PA1quYX3 .

Jeegar Patel
źródło
2
Poprawnie opisałeś problem C. Ale twierdzenie, że inicjalizacja z etykietą C ++ jest niedozwolona, ​​jest całkowicie nieprawdziwe. Nie ma nic złego w oznaczaniu inicjalizacji w C ++. To, czego nie pozwala C ++, to przeskakiwanie inicjalizacji zmiennej aw jej zakres a. Tak więc, z punktu widzenia C, problemy dotyczą case VAL:etykiety i opisałeś ją poprawnie. Ale z punktu widzenia C ++ problemem jest case ANOTHER_VAL:etykieta.
AnT
W C ++, inaczej niż w C, deklaracje są podzbiorem instrukcji.
Keith Thompson,
20

Nie możesz tego zrobić, ponieważ caseetykiety są w rzeczywistości tylko punktami wejścia do bloku zawierającego.

Najwyraźniej ilustruje to urządzenie Duffa . Oto kod z Wikipedii:

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

Zauważ, że caseetykiety całkowicie ignorują granice bloków. Tak, to jest złe. Ale dlatego twój przykład kodu nie działa. Przeskakiwanie do caseetykiety jest takie samo jak używanie goto, więc nie można przeskakiwać nad zmienną lokalną za pomocą konstruktora.

Jak wskazało kilka innych plakatów, musisz umieścić własny blok:

switch (...) {
    case FOO: {
        MyObject x(...);
        ...
        break; 
    }
    ...
 }
emk
źródło
1
Implementacja tego Duffa zawiera błąd, który powoduje, że jest on bardzo wolny: count jest typ int, więc% musi wykonać prawdziwą operację dzielenia / modulo. Uczyń count bez znaku (lub jeszcze lepiej, zawsze używaj size_t dla zliczeń / indeksów), a problem zniknie.
R .. GitHub ZATRZYMAJ LÓD
1
@R ..: Co ?! W systemie uzupełnień do dwóch, sygnatura nie wpływa na moduły o potęgach 2 (to tylko AND na dolnych bitach) i nie wpływa na podziały przez potęgi 2, o ile architektura procesora ma arytmetyczną operację przesunięcia w prawo ( SARw x86, w przeciwieństwie SHRdo przesunięć bez znaku).
Chris Jester-Young,
@Chris: Wydaje mi się, że ma na myśli, kiedy kompilator musi uwzględniać wartości ujemne, w przypadku gdy „tylko I na dolnych bitach” nie ma miejsca; na przykład -1% 8 daje -1 w systemie dopełniania tych dwóch za pomocą g ++ (w tym przypadku znakiem jest implementacja zdefiniowana zgodnie z 5.6 / 4).
3
@Chris: Zgadzam się z tobą, że R wyolbrzymia wpływ; Widziałem tylko twój komentarz i wiedziałem, że prosty ORAZ nie wystarczy.
1
Warto również zauważyć, że oryginalny kod Wikipedii służy do wysyłania danych do wyjścia odwzorowanego w pamięci, co wygląda tutaj dziwnie, ponieważ nie zostało wspomniane, a każdy bajt jest kopiowany do tej samej lokalizacji „do”. Można to obejść, dodając do tego postfix ++ lub wspominając, że przypadek użycia dotyczy IO odwzorowanego w pamięci. Całkowicie peryferyjne w stosunku do pierwotnego pytania :-).
Peter
16

Większość odpowiedzi do tej pory są źle pod jednym względem: Państwo może zadeklarować zmienne po instrukcji case, ale nie można ich zainicjować:

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

Jak wspomniano wcześniej, dobrym sposobem na obejście tego jest użycie nawiasów klamrowych do utworzenia zakresu dla twojej skrzynki.

MrZebra
źródło
1
Pan 32, źle zrozumiałeś, jaki jest twój błąd: tak, to nie będzie się kompilować, ale nie dlatego, że deklarujesz zmienną wewnątrz przełącznika. Błąd polega na tym, że próbujesz zadeklarować zmienną po instrukcji, która jest nielegalna w C.
MrZebra
1
Teraz dni, które są legalne w c90 i nowszej wersji c
Jeegar Patel
12

Moją ulubioną sztuczką polegającą na zamianie zła jest użycie if (0), aby pominąć niechcianą etykietę skrzynki.

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

Ale bardzo złe.

Jeremy
źródło
Bardzo dobrze. Przykład dlaczego: przypadek 0 i przypadek 1 mogą na przykład zainicjować zmienną inaczej, która jest następnie używana w przypadku 2.
hlovdal
1
Jeśli chcesz, aby zarówno przypadek 0, jak i przypadek 1 wypadły przez przypadek 2. (bez przypadku 0 przypadkiem przypadek 1). Nie wiem, czy to naprawdę przydatne, ale na pewno działa.
Petruza,
1
Możesz po prostu przejść do wymaganej etykiety gotobez zaciemniania kodu
SomeWittyUsername 24.09.16
10

Spróbuj tego:

switch (val)
{
    case VAL:
    {
        int newVal = 42;
    }
    break;
}
Dan Shield
źródło
7

Możesz zadeklarować zmienne w instrukcji switch, jeśli uruchomisz nowy blok:

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

Powodem jest przydzielanie (i odzyskiwanie) miejsca na stosie do przechowywania zmiennych lokalnych.

Seb Rose
źródło
1
Zmienna może być zadeklarowana, ale nie można jej zainicjować. Jestem też całkiem pewien, że problem w żaden sposób nie dotyczy stosu i zmiennych lokalnych.
Richard Corden,
6

Rozważać:

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

W przypadku braku instrukcji break czasami newVal zostaje zadeklarowany dwukrotnie i nie wiesz, czy to robi, dopóki nie zostanie uruchomione. Domyślam się, że ograniczenie wynika z tego rodzaju zamieszania. Jaki byłby zakres newVal? Konwencja dyktuje, że byłby to cały blok przełączników (między nawiasami klamrowymi).

Nie jestem programistą C ++, ale w C:

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

Działa w porządku. Zadeklarowanie zmiennej w bloku przełącznika jest w porządku. Deklaracja po tym, jak strażnik nie jest.

szczupły
źródło
3
@ Mr.32: w rzeczywistości twój przykład pokazuje, że printf nie jest wykonywany, ale w tym przypadku int x nie jest instrukcją, ale deklaracją, x jest deklarowane, miejsce jest zarezerwowane za każdym razem, gdy środowisko funkcji jest układane w stos, patrz: codepad.org/4E9Zuz1e
Petruza,
Spodziewałem się tego podczas czytania tytułu pytania, ponieważ pytanie nie dotyczy deklarowania zmiennych w etykietach „case:”, ale w instrukcjach switch. I tylko ty (i VictorH, podkreślając twoją odpowiedź) faktycznie mówiłeś o zmiennych w instrukcjach switch.
cesss,
4

Cała sekcja przełącznika jest kontekstem pojedynczej deklaracji. Nie można zadeklarować zmiennej w takiej instrukcji case. Spróbuj zamiast tego:

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

źródło
Zmienna może być zadeklarowana, ale nie można jej zainicjować.
Richard Corden,
@Richard Corden Jestem pewien, że inicjalizacja zadziała. Czy nadal twierdzisz, że nie można go zainicjować?
chux - Przywróć Monikę
3

Jeśli Twój kod mówi „int newVal = 42”, można oczekiwać, że newVal nigdy nie zostanie niezainicjowany. Ale jeśli masz problem z tym stwierdzeniem (co robisz), właśnie tak się dzieje - newVal jest objęty zakresem, ale nie został przypisany.

Jeśli tak naprawdę chciałeś się wydarzyć, to język musi wyraźnie to powiedzieć, mówiąc „int newVal; newVal = 42;”. W przeciwnym razie możesz ograniczyć zakres newVal do pojedynczej skrzynki, co jest bardziej prawdopodobne, co chciałeś.

Może to wyjaśnić, jeśli weźmiesz pod uwagę ten sam przykład, ale z „const int newVal = 42;”


źródło
3

Chciałem tylko podkreślić, szczupły „s punkt . Konstrukcja przełącznika tworzy cały zakres pierwszorzędnego obywatela. Można więc zadeklarować (i zainicjować) zmienną w instrukcji switch przed etykietą pierwszego przypadku, bez dodatkowej pary nawiasów:

switch (val) {  
  /* This *will* work, even in C89 */
  int newVal = 42;  
case VAL:
  newVal = 1984; 
  break;
case ANOTHER_VAL:  
  newVal = 2001;
  break;
}
VictorH
źródło
-1 tutaj int newVal = 42; nigdy nie zostanie stracony. zobacz ten codepad.org/PA1quYX3
Jeegar Patel,
4
deklaracja int newVal zostanie wykonana, ale nie = 42przypisanie.
Petruza
3

Do tej pory odpowiedzi były na C ++.

W przypadku C ++ nie można przeskoczyć inicjalizacji. Możesz w C. Jednak w C deklaracja nie jest instrukcją, a po etykietach przypadków muszą znajdować się instrukcje.

Tak więc poprawne (ale brzydkie) C, nieprawidłowe 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();
}

I odwrotnie, w C ++ deklaracja jest instrukcją, więc następujące jest poprawne C ++, nieprawidłowe C

switch (something)
{
  case 1:
    do_something();
    break;
  case 2:
    int i = 12;
    do_something_else();
}
Piotr
źródło
1
Drugi przykład NIE jest poprawnym C ++ (test z vc2010 i gcc 4.6.1 C ++ nie zezwala na pominięcie części inicjalizacyjnej. Komunikat o błędzie gcc to: krzyżowa inicjalizacja 'int i'
zhaorufei
3

Ciekawe, że to dobrze:

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

case 1:  
    break;
}

... ale to nie jest:

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

case 1:  
    break;
}

Rozumiem, że poprawka jest dość prosta, ale nie rozumiem jeszcze, dlaczego pierwszy przykład nie przeszkadza kompilatorowi. Jak wspomniano wcześniej (2 lata wcześniej hehe), deklaracja nie jest przyczyną błędu, nawet pomimo logiki. Problemem jest inicjalizacja. Jeśli zmienna zostanie zainicjowana i zadeklarowana w różnych wierszach, zostanie skompilowana.

Dan
źródło
1
Po pierwsze nie jest w porządku w gcc 4.2: „błąd: oczekiwane wyrażenie przed„ int ””. Jak mówią Piotr i Pan. 32, „przypadek 0:; int j; ...” i „przypadek 0:; int j = 7; ...” działają oba. Problem w C polega na tym, że „case <etykieta>: deklaracja” jest niepoprawną składnią C.
dubiousjim
3

Napisałem tę odpowiedź orginally na to pytanie . Jednak kiedy skończyłem, okazało się, że odpowiedź została zamknięta. Więc zamieściłem go tutaj, może ktoś, kto lubi odniesienia do standardu, uzna to za pomocne.

Kod oryginalny, o którym mowa:

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

Tak naprawdę są 2 pytania:

1. Dlaczego mogę zadeklarować zmienną po caseetykiecie?

To dlatego, że w C ++ etykieta musi być w formie:

N3337 6.1 / 1

etykieta-oświadczenie:

...

  • atrybut-specyfikator-seqopt case constant-expression :statement

...

A w C++ deklaracji oświadczenie jest również uważane za oświadczenie (w przeciwieństwie do C):

N3337 6/1:

oświadczenie :

...

oświadczenie-oświadczenie

...

2. Dlaczego mogę przeskoczyć deklarację zmiennej, a następnie jej użyć?

Ponieważ: N3337 6,7 / 3

Możliwe jest przeniesienie do bloku, ale nie w sposób, który omija deklaracje podczas inicjalizacji . Program, który przeskakuje ( przeniesienie z warunku instrukcji switch na etykietę sprawy jest pod tym względem uważane za skok ).

od punktu, w którym zmienna z automatycznym czasem przechowywania nie jest objęta zakresem, do punktu, w którym mieści się w zakresie, jest źle sformułowana, chyba że zmienna ma typ skalarny, typ klasy z trywialnym domyślnym konstruktorem i trywialny destruktor, wersję zakwalifikowaną do CV jednego z tych typów lub tablicy jednego z poprzednich typów i jest zadeklarowany bez inicjatora (8.5).

Ponieważ kjest typu skalarnego i nie jest inicjowany w punkcie deklaracji, możliwe jest przeskakiwanie jego deklaracji. Jest to semantycznie równoważne:

goto label;

int x;

label:
cout << x << endl;

Nie byłoby to jednak możliwe, gdyby xzostało zainicjowane w punkcie deklaracji:

 goto label;

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

    label:
    cout << x << endl;
PcAF
źródło
1

Nowe zmienne mogą być odkodowane tylko w zakresie bloków. Musisz napisać coś takiego:

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

Oczywiście newVal ma zakres tylko w nawiasach klamrowych ...

Pozdrawiam, Ralph


źródło
1

switchBlok nie jest taka sama jak kolejnych if/else ifbloków. Dziwi mnie, że żadna inna odpowiedź nie wyjaśnia tego wyraźnie.

Rozważ to switchoświadczenie:

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

Może to być zaskakujące, ale kompilator nie uzna tego za proste if/else if. Wygeneruje następujący kod:

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

Te casestwierdzenia są konwertowane na etykiety, a następnie wywołana goto. Nawiasy tworzą nowy zakres i łatwo jest teraz zrozumieć, dlaczego nie można zadeklarować dwóch zmiennych o tej samej nazwie w switchbloku.

Może to wyglądać dziwnie, ale konieczne jest wsparcie przewrotu (to znaczy nie używanie, breakaby pozwolić na wykonanie następnego case).

Dalmas
źródło
0

Wydaje mi się, że problemem jest to, że oświadczenie zostało pominięte, a ty próbowałeś użyć var ​​gdzie indziej, nie zostałby zadeklarowany.

William Keller
źródło
0

newVal istnieje w całym zakresie przełącznika, ale jest inicjowany tylko po uderzeniu w kończynę VAL. Jeśli utworzysz blok wokół kodu w VAL, powinno być OK.

marihuana
źródło
0

Standard C ++ ma: Możliwe jest przeniesienie do bloku, ale nie w sposób, który omija deklaracje podczas inicjalizacji. Program, który skacze z miejsca, w którym zmienna lokalna z automatycznym czasem przechowywania nie wchodzi w zakres, do punktu, w którym jest objęty zakresem, jest źle sformułowany, chyba że zmienna ma typ POD (3.9) i jest zadeklarowana bez inicjatora (8.5).

Kod ilustrujący tę regułę:

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

Kod pokazujący efekt inicjalizacji:

#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;
}
Jingguo Yao
źródło
0

Wygląda na to, że anonimowe obiekty mogą być deklarowane lub tworzone w instrukcji case switch, ponieważ nie można do nich odwoływać się i jako takie nie mogą przejść do następnej sprawy. Rozważ ten przykład kompiluje się w GCC 4.5.3 i Visual Studio 2008 (może to być problem ze zgodnością, więc eksperci powinni się zastanowić)

#include <cstdlib>

struct Foo{};

int main()
{
    int i = 42;

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

    default:
        break;
    }
    return EXIT_SUCCESS;
}
Olumide
źródło
Jeśli masz zamiar głosować, proszę wyjaśnić dlaczego. Ciekawe, dlaczego tworzenie anonimowego obiektu wydaje się być wyjątkiem.
Olumide,
1
nie DV, ale: Całe pytanie dotyczy deklaracji / zakresu nazwanych zmiennych. Tymczasowa („obiekt anonimowy” nie jest terminem) nie jest zmienną nazwaną, ani nie jest deklaracją, ani nie jest objęta zakresem (chyba że jest powiązana z constodnośnikiem własnym zakresem). To wyrażenie, które żyje i umiera w swoim oświadczeniu (gdziekolwiek to może być). Dlatego jest to całkowicie nieistotne.
underscore_d
Foo();nie jest deklaracją; pytanie dotyczy deklaracji.
MM