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?
c++
switch-statement
Obrabować
źródło
źródło
Odpowiedzi:
Case
instrukcje 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 wswitch
instrukcji. 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
case
instrukcji i zdefiniowanie w niej zmiennej:źródło
To pytanie
jestpierwotnie 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,newVal
pomijają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 zmiennejnewVal
. 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.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 zakresnewVal
, upewniając się, żecase 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 czemucase VAL:
etykieta ma zastosowanie do instrukcji, co eliminuje problem C.W przypadku C problem można łatwo rozwiązać bez
{}
. Wystarczy dodać pustą instrukcję pocase VAL:
etykiecie, a kod stanie się prawidłowyZauważ, ż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łowyZauważ, że pomimo tego, że jest teraz poprawny z punktu widzenia C ++, pozostaje niepoprawny z punktu widzenia C.
źródło
newVal
kiedy do niej przejdzieANOTHER_VAL
?case ANOTHER_VAL:
punkcienewVal
widoczna jest zmienna , ale o nieokreślonej wartości.§A9.3: Compound Statement
z 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ą .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:
Jeśli obiekt nie jest POD lub agreguje, kompilator domyślnie dodaje inicjator, więc nie można przeskoczyć takiej deklaracji:
To ograniczenie nie ogranicza się do instrukcji switch. Błędem jest także użycie „goto” do przeskoczenia inicjalizacji:
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.
źródło
Cała instrukcja switch ma ten sam zakres. Aby obejść ten problem, wykonaj następujące czynności:
Zwróć uwagę na nawiasy.
źródło
Po przeczytaniu wszystkich odpowiedzi i kilku dalszych badań otrzymuję kilka rzeczy.
W C, zgodnie ze specyfikacją,
§6.8.1 Stwierdzenia oznaczone:
W C nie ma żadnej klauzuli, która dopuszcza „deklarację z etykietą”. To po prostu nie jest częścią języka.
Więc
To się nie skompiluje , patrz http://codepad.org/YiyLQTYw . GCC podaje błąd:
Parzysty
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
Użyj nowego zakresu za pomocą {}
Lub użyj fałszywego oświadczenia z etykietą
Zadeklaruj zmienną przed switch () i zainicjuj ją z różnymi wartościami w instrukcji case, jeśli spełnia twoje wymagania
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:
Zobacz http://codepad.org/PA1quYX3 .
źródło
a
w jej zakresa
. Tak więc, z punktu widzenia C, problemy dotyczącase VAL:
etykiety i opisałeś ją poprawnie. Ale z punktu widzenia C ++ problemem jestcase ANOTHER_VAL:
etykieta.Nie możesz tego zrobić, ponieważ
case
etykiety są w rzeczywistości tylko punktami wejścia do bloku zawierającego.Najwyraźniej ilustruje to urządzenie Duffa . Oto kod z Wikipedii:
Zauważ, że
case
etykiety całkowicie ignorują granice bloków. Tak, to jest złe. Ale dlatego twój przykład kodu nie działa. Przeskakiwanie docase
etykiety jest takie samo jak używaniegoto
, 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:
źródło
SAR
w x86, w przeciwieństwieSHR
do przesunięć bez znaku).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ć:
Jak wspomniano wcześniej, dobrym sposobem na obejście tego jest użycie nawiasów klamrowych do utworzenia zakresu dla twojej skrzynki.
źródło
Moją ulubioną sztuczką polegającą na zamianie zła jest użycie if (0), aby pominąć niechcianą etykietę skrzynki.
Ale bardzo złe.
źródło
goto
bez zaciemniania koduSpróbuj tego:
źródło
Możesz zadeklarować zmienne w instrukcji switch, jeśli uruchomisz nowy blok:
Powodem jest przydzielanie (i odzyskiwanie) miejsca na stosie do przechowywania zmiennych lokalnych.
źródło
Rozważać:
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:
Działa w porządku. Zadeklarowanie zmiennej w bloku przełącznika jest w porządku. Deklaracja po tym, jak strażnik nie jest.
źródło
Cała sekcja przełącznika jest kontekstem pojedynczej deklaracji. Nie można zadeklarować zmiennej w takiej instrukcji case. Spróbuj zamiast tego:
źródło
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
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:
źródło
int newVal
zostanie wykonana, ale nie= 42
przypisanie.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 ++
I odwrotnie, w C ++ deklaracja jest instrukcją, więc następujące jest poprawne C ++, nieprawidłowe C
źródło
Ciekawe, że to dobrze:
... ale to nie jest:
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.
źródło
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:
Tak naprawdę są 2 pytania:
1. Dlaczego mogę zadeklarować zmienną po
case
etykiecie?To dlatego, że w C ++ etykieta musi być w formie:
N3337 6.1 / 1
A w
C++
deklaracji oświadczenie jest również uważane za oświadczenie (w przeciwieństwie doC
):N3337 6/1:
2. Dlaczego mogę przeskoczyć deklarację zmiennej, a następnie jej użyć?
Ponieważ: N3337 6,7 / 3
Ponieważ
k
jest typu skalarnego i nie jest inicjowany w punkcie deklaracji, możliwe jest przeskakiwanie jego deklaracji. Jest to semantycznie równoważne:Nie byłoby to jednak możliwe, gdyby
x
zostało zainicjowane w punkcie deklaracji:źródło
Nowe zmienne mogą być odkodowane tylko w zakresie bloków. Musisz napisać coś takiego:
Oczywiście newVal ma zakres tylko w nawiasach klamrowych ...
Pozdrawiam, Ralph
źródło
switch
Blok nie jest taka sama jak kolejnychif/else if
bloków. Dziwi mnie, że żadna inna odpowiedź nie wyjaśnia tego wyraźnie.Rozważ to
switch
oświadczenie:Może to być zaskakujące, ale kompilator nie uzna tego za proste
if/else if
. Wygeneruje następujący kod:Te
case
stwierdzenia są konwertowane na etykiety, a następnie wywołanagoto
. Nawiasy tworzą nowy zakres i łatwo jest teraz zrozumieć, dlaczego nie można zadeklarować dwóch zmiennych o tej samej nazwie wswitch
bloku.Może to wyglądać dziwnie, ale konieczne jest wsparcie przewrotu (to znaczy nie używanie,
break
aby pozwolić na wykonanie następnegocase
).źródło
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.
źródło
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.
źródło
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łę:
Kod pokazujący efekt inicjalizacji:
źródło
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ć)
źródło
const
odnoś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.Foo();
nie jest deklaracją; pytanie dotyczy deklaracji.