Rozważ następujące switch
stwierdzenie:
switch( value )
{
case 1:
return 1;
default:
value++;
// fall-through
case 2:
return value * 2;
}
Ten kod jest kompilowany, ale czy jest prawidłowy (= zdefiniowane zachowanie) dla C90 / C99? Nigdy nie widziałem kodu, w którym domyślny przypadek nie jest ostatnim przypadkiem.
EDYCJA:
Jak piszą Jon Cage i KillianDS : to jest naprawdę brzydki i zagmatwany kod i jestem tego świadomy. Interesuje mnie tylko ogólna składnia (czy jest zdefiniowana?) I oczekiwane dane wyjściowe.
c
switch-statement
tanascius
źródło
źródło
goto
nie jest zły. Wyznawcy kultu Cargo są! Nie możesz sobie wyobrazić, do jakich skrajności ludzie mogą się posunąć,goto
ponieważ jest to z całą pewnością tak złe, co powoduje prawdziwy nieczytelny bałagan w ich kodzie.goto
głównie do symulacji czegoś w rodzajufinally
klauzuli w funkcjach, gdzie zasoby (pliki, pamięć) muszą być zwolnione podczas zatrzymywania, a powtarzanie dla każdego przypadku błędu listyfree
iclose
nie pomaga w czytelności. Jest jednak jedno użyciegoto
, którego chciałbym uniknąć, ale nie mogę, polega na tym, że chcę wyrwać się z pętli i jestemswitch
w tej pętli.Odpowiedzi:
Standard C99 nie jest na ten temat jednoznaczny, ale biorąc wszystkie fakty razem, jest całkowicie ważny.
A
case
idefault
etykieta są odpowiednikiemgoto
etykiety. Patrz 6.8.1 Oznakowane oświadczenia. Szczególnie interesujący jest 6.8.1.4, który umożliwia wspomniane już urządzenie Duffa:Edycja : kod w przełączniku nie jest niczym specjalnym; jest to normalny blok kodu, jak w instrukcji
if
, z dodatkowymi etykietami skoku. To wyjaśnia zachowanie upadku i dlaczegobreak
jest to konieczne.6.8.4.2.7 podaje nawet przykład:
Stałe wielkości liter muszą być unikalne w instrukcji switch:
Wszystkie przypadki są oceniane, a następnie przeskakuje do domyślnej etykiety, jeśli jest podana:
źródło
default
przypadek dominujący nad innymi przypadkami o około 100: 1 i nie wiem, czy jest ważny, czy nieokreślony, aby zrobićdefault
pierwszy przypadek.Instrukcje case i instrukcja default mogą występować w dowolnej kolejności w instrukcji switch. Klauzula domyślna jest klauzulą opcjonalną, która jest dopasowywana, jeśli żadna ze stałych w instrukcjach case nie może zostać dopasowana.
Dobry przykład :-
bardzo przydatne, jeśli chcesz, aby twoje sprawy były prezentowane w logicznej kolejności w kodzie (jak w nie mówiąc przypadek 1, przypadek 3, przypadek 2 / domyślny), a twoje sprawy są bardzo długie, więc nie chcesz powtarzać całej sprawy kod na dole dla domyślnego
źródło
W niektórych przypadkach jest to ważne i bardzo przydatne.
Rozważ następujący kod:
Chodzi o to, że powyższy kod jest bardziej czytelny i wydajny niż kaskadowy
if
. Mógłbyś todefault
zakończyć, ale jest to bezcelowe, ponieważ skupi twoją uwagę na przypadkach błędów zamiast normalnych przypadków (tak jest w tymdefault
przypadku).Właściwie to nie jest taki dobry przykład, ponieważ
poll
wiesz, ile zdarzeń może wystąpić najwyżej. Moje prawdziwe jest to, że tam są przypadki z określonego zestawu wartości wejściowych, gdzie istnieją „wyjątki” i normalnych przypadkach. Czy lepiej jest umieścić wyjątki lub normalne przypadki na pierwszym planie, to kwestia wyboru.W dziedzinie oprogramowania myślę o innym bardzo zwykłym przypadku: rekurencji z pewnymi wartościami końcowymi. Jeśli możesz to wyrazić za pomocą przełącznika,
default
będzie zwykłą wartością zawierającą wywołanie rekurencyjne i wyróżnione elementy (pojedyncze przypadki) wartości końcowe. Zwykle nie ma potrzeby skupiania się na wartościach końcowych.Innym powodem jest to, że kolejność przypadków może zmienić zachowanie skompilowanego kodu, co ma znaczenie dla wydajności. Większość kompilatorów wygeneruje skompilowany kod asemblera w tej samej kolejności, w jakiej kod pojawia się w przełączniku. To sprawia, że pierwszy przypadek bardzo różni się od pozostałych: wszystkie przypadki z wyjątkiem pierwszego będą wymagały przeskoku, co spowoduje opróżnienie potoków procesora. Możesz to rozumieć tak, jak predyktor gałęzi, który domyślnie uruchamia pierwszy występujący przypadek w przełączniku. Jeśli przypadek jest znacznie bardziej powszechny niż inne, masz bardzo dobre powody, aby umieścić go jako pierwszy.
Czytanie komentarzy jest to szczególny powód, dla którego oryginalny plakat zadał to pytanie po przeczytaniu reorganizacji Branch Loop kompilatora Intel na temat optymalizacji kodu.
Wtedy stanie się to arbitrażem między czytelnością i wydajnością kodu. Prawdopodobnie lepiej jest zamieścić komentarz wyjaśniający przyszłemu czytelnikowi, dlaczego przypadek pojawia się jako pierwszy.
źródło
case
. Przygnębiające jest to, że wygląda tak samo jak cukier składniowy i nie zepsuje żadnego istniejącego kodu, jeśli jest obsługiwany.tak, to jest ważne, aw pewnych okolicznościach nawet przydatne. Generalnie, jeśli tego nie potrzebujesz, nie rób tego.
źródło
W instrukcji switch nie ma zdefiniowanej kolejności. Możesz patrzeć na przypadki jako na coś w rodzaju nazwanej etykiety, jak
goto
etykieta. W przeciwieństwie do tego, co ludzie wydają się tutaj sądzić, w przypadku wartości 2 nie przeskakuje się do domyślnej etykiety. Aby zilustrować klasycznym przykładem, oto urządzenie Duffa , które jest plakatem potomnym skrajnościswitch/case
w C.źródło
Jeden scenariusz, w którym uznałbym za stosowne umieszczenie „wartości domyślnej” w innym miejscu niż instrukcja końca przypadku, dotyczy automatu stanowego, w którym nieprawidłowy stan powinien zresetować maszynę i postępować tak, jakby był to stan początkowy. Na przykład:
rozwiązanie alternatywne, jeśli nieprawidłowy stan nie powinien resetować maszyny, ale powinien być łatwo rozpoznawalny jako nieprawidłowy stan:
Kod w innym miejscu może następnie sprawdzić (widget_state == WIDGET_INVALID_STATE) i zapewnić wszelkie działania związane z raportowaniem błędów lub resetowaniem stanu, które wydają się odpowiednie. Na przykład kod paskowy stanu może wyświetlać ikonę błędu, a opcja menu „start widget”, która jest wyłączona w większości stanów bezczynności, może być włączona zarówno dla WIDGET_INVALID_STATE, jak i WIDGET_IDLE.
źródło
Wbijając inny przykład: może to być przydatne, jeśli „domyślny” jest nieoczekiwanym przypadkiem i chcesz zarejestrować błąd, ale także zrobić coś rozsądnego. Przykład z mojego własnego kodu:
źródło
Istnieją przypadki, gdy konwertujesz ENUM na ciąg lub konwertujesz ciąg na wyliczenie w przypadku, gdy piszesz / czytasz do / z pliku.
Czasami trzeba ustawić jedną z wartości jako domyślną, aby pokryć błędy popełnione podczas ręcznej edycji plików.
źródło
default
Warunek może być wszędzie w przełączniku, że klauzula przypadek może istnieć. Nie jest wymagane, aby była to ostatnia klauzula. Widziałem kod, który ustawił wartość domyślną jako pierwszą klauzulę. Jestcase 2:
wykonywany normalnie, mimo że klauzula default jest nad nim.W ramach testu umieściłem przykładowy kod w funkcji, wywołałem
test(int value){}
i uruchomiłem:Wynik to:
źródło
Jest ważny, ale raczej paskudny. Sugerowałbym, że generalnie złe jest zezwalanie na upadki, ponieważ może to prowadzić do bardzo niechlujnego kodu spaghetti.
Prawie na pewno lepiej jest rozbić te przypadki na kilka instrukcji switch lub mniejszych funkcji.
[edytuj] @Tristopia: Twój przykład:
byłoby jaśniejsze co do jego intencji (myślę), gdyby zostało napisane w ten sposób:
[edit2] @Tristopia: Twój drugi przykład jest prawdopodobnie najczystszym przykładem dobrego wykorzystania do kontynuacji:
... ale osobiście podzieliłbym rozpoznawanie komentarzy na jego własną funkcję:
źródło
r
to tablica docelowa,wc
towchar_t
przełącznik wejściowy (utf8_length) {/ * Uwaga: kod przechodzi przez przypadki! * / przypadek 3: r [2] = 0x80 | (wc & 0x3f); wc >> = 6; wc | = 0x800; przypadek 2: r [1] = 0x80 | (wc & 0x3f); wc >> = 6; wc | = 0xc0; przypadek 1: r [0] = wc; }for(i=0; s[i]; i++) { switch(s[i]) { case '"': case '\'': case '\\': d[dlen++] = '\\'; /* fall through */ default: d[dlen++] = s[i]; } }