Jedna wartość, wiele stałych
Łańcuch else if, który ciągle porównuje tę samą zmienną ze stałymi, to wzorzec, dla którego C ma osobną instrukcję:
if (choice == 1) { /* ... */ }
else if (choice == 2) { /* ... */ }
else if (choice == 3) { /* ... */ }
else { /* ... */ }
switch mówi to bardziej bezpośrednio. Oblicza wyrażenie raz, a potem skacze prosto do pasującej etykiety.
Zmień choice i uruchom ponownie. Ta struktura pokazuje zbiór dozwolonych wartości na pierwszy rzut oka i to jest prawdziwy powód, by ją tu wybrać.
Składnia i zasady dotyczące etykiet case
switch (wyrazenieCalkowite) {
case STALA_1:
instrukcje
break;
case STALA_2:
instrukcje
break;
default:
instrukcje
break;
}
Cztery ograniczenia, których pilnuje C:
- Wyrażenie w switch musi mieć typ całkowity:
int,char,short,longalboenum. Niefloat, niedoublei nie napis. - Każda etykieta
casemusi być wyrażeniem stałym znanym w czasie kompilacji.case n:, gdzienjest zmienną, to błąd, acase 3 + 4:icase MAX:(stała z#definelub enum) są w porządku. - Żadne dwa przypadki nie mogą mieć tej samej wartości. Duplikaty to błąd kompilacji, a to małe, ale prawdziwe zabezpieczenie.
- Case nie może wyrazić zakresu.
case 1 ... 5:to rozszerzenie GCC, a nie standardowe C, więc do zakresów używajif.
default jest opcjonalne i może się pojawić w dowolnym miejscu bloku, choć umieszczanie go na końcu to niemal powszechna konwencja. Bez default niedopasowana wartość po prostu pomija cały switch.
break: dlaczego nie jest opcjonalne
Najbardziej zaskakującą rzeczą w switch w C jest to, że etykiety case to cele skoku, a nie pudełka. Gdy wykonanie trafi na etykietę, idzie dalej przez wszystkie instrukcje poniżej, także przez kolejne etykiety case, aż do break albo nawiasu zamykającego.
Pierwszy switch wypisuje jeden, dwa i trzy: dopasował case 1, a potem wykonał wszystko, co było dalej. Drugi wypisuje tylko jeden. To zachowanie nazywa się fall-through, a zapomniane break to klasyczny błąd przy switch w C: program jest po cichu błędny, zamiast nie dać się skompilować.
Skompiluj z gcc -Wall -Wextra -Wimplicit-fallthrough, a kompilator wskaże każdy przypadek, który przechodzi do następnego.
break w ostatnim przypadku jest formalnie zbędne, bo blok i tak się tam kończy. Mimo to je pisz, żeby dodanie później nowego przypadku poniżej nie stworzyło po cichu fall-through.
Celowe fall-through
Fall-through jest zaletą, gdy jest zamierzone. Ułożenie etykiet jedna pod drugą bez instrukcji pomiędzy to zwykły sposób, by powiedzieć „te wartości robią to samo”:
Trzy etykiety dzielą jedno ciało, bez powielonego kodu i bez długiego warunku z ||. Kompilatory nie ostrzegają przed takim układem, bo nie ma instrukcji, przez które można by przejść.
Fall-through z instrukcjami pomiędzy, gdzie wykonuje się przypadek 1, a potem celowo przechodzi do przypadku 2, jest rzadsze i zasługuje na wyraźny komentarz, bo czytelnik musi się dowiedzieć, że brak break to świadoma decyzja:
switch (level) {
case 3:
printf("szczegolowo\n");
/* fall through */
case 2:
printf("info\n");
/* fall through */
case 1:
printf("bledy\n");
break;
}
/* fall through */ to komentarz, który rozpoznaje ostrzeżenie GCC, więc wycisza też diagnostykę.
switch na znakach
Ponieważ char jest typem całkowitym, klasyfikacja znaków pasuje tu naturalnie:
Zwróć uwagę, że case 'a': używa pojedynczych cudzysłowów: to stała znakowa, czyli po prostu wartość całkowita tego znaku. case "a": byłoby napisem i się nie skompiluje.
switch na enumach
Najlepsze połączenie w C to switch z enumem. Etykiety case stają się czytelnymi nazwami, a kompilatory mogą ostrzec, gdy switch nie obsługuje każdej wartości wyliczenia:
Dwie rzeczy warte skopiowania. Każdy przypadek robi return, więc break nie jest potrzebne: return całkowicie opuszcza funkcję, co kończy też switch. I nie ma default: z -Wswitch (zawartym w -Wall) brak default sprawia, że kompilator ostrzeże w dniu, w którym ktoś doda piąty stan i zapomni o tej funkcji. default by to ukryło.
Deklarowanie zmiennych wewnątrz switch
Ciało switch to jeden blok, więc deklaracja w jednym przypadku jest widoczna, choć niekoniecznie zainicjalizowana, w przypadkach po nim. Przeskoczenie inicjalizacji to w C błąd:
switch (n) {
case 1:
int x = 10; // blad w C: etykieta nie moze tak poprzedzac deklaracji
printf("%d\n", x);
break;
case 2:
printf("%d\n", x); // x tu istnieje, ale nigdy nie zostalo zainicjalizowane
break;
}
Gdy przypadek potrzebuje zmiennych lokalnych, otocz go własnymi nawiasami klamrowymi:
switch (n) {
case 1: {
int x = 10;
printf("%d\n", x);
break;
}
case 2:
printf("dwa\n");
break;
}
switch czy if else?
Sięgaj po switch, gdy wszystko to jest prawdą: testujesz jedno wyrażenie, ma ono typ całkowity i porównujesz je ze stałymi. Obsługa menu, maszyny stanów na enumach, klasyfikacja znaków i rozdzielanie poleceń to przypadki podręcznikowe. Zyskujesz czytelność, sprawdzanie duplikatów i kompilator, który może ostrzec o nieobsłużonych wartościach wyliczenia.
Sięgaj po if else, gdy testy obejmują zakresy (score >= 90), wartości zmiennoprzecinkowe, napisy (potrzebujesz strcmp), kilka różnych zmiennych albo warunki łączone przez && i ||. Żadnego z nich nie da się wyrazić jako etykiety case.
Wydajność rzadko jest czynnikiem decydującym. Kompilatory zamieniają gęsty switch w tablicę skoków, a rzadki w porównania, więc przy kilku przypadkach obie formy są nie do odróżnienia. Wybierz tę, która lepiej wyraża intencję.
Najczęściej zadawane pytania
Jak działa instrukcja switch w C?
switch (expr) raz oblicza wyrażenie całkowite, skacze do etykiety case, której stała pasuje, i wykonuje kod od tego miejsca, aż trafi na break albo koniec switcha. Jeśli nic nie pasuje, skacze do default, o ile istnieje, a w przeciwnym razie pomija cały blok.
Dlaczego w switch w C potrzebny jest break?
Bo etykiety case to cele skoku, a nie osobne bloki. Bez break wykonanie przechodzi prosto do instrukcji następnego przypadku, co nazywa się fall-through. Brakujące break to najczęstszy błąd przy switch w C, a gcc -Wimplicit-fallthrough go wyłapie.
Czy w C można użyć switch na stringu?
Nie. switch w C wymaga wyrażenia całkowitego, więc działają wartości int, char i enum, ale napisy, liczby zmiennoprzecinkowe i zakresy już nie. Aby rozgałęzić kod według napisu, użyj strcmp w łańcuchu if else albo najpierw zamień napis na enum i zrób switch na nim.
Kiedy w C używać switch zamiast if else?
Używaj switch, gdy porównujesz jedną liczbę całkowitą lub znak z listą stałych: polecenia menu, stany enum, klasyfikację znaków. Użyj łańcucha if else, gdy testy obejmują zakresy, kilka zmiennych albo cokolwiek innego niż równość ze stałą znaną w czasie kompilacji.