Menu

Instrukcja switch w C: case, break i fall-through

Jak instrukcja switch w C wybiera jedną gałąź z listy stałych: etykiety case, dlaczego break ma znaczenie, celowe i przypadkowe przechodzenie dalej (fall-through), default, switch na znakach i enumach oraz kiedy switch wygrywa z łańcuchem else if.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

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, long albo enum. Nie float, nie double i nie napis.
  • Każda etykieta case musi być wyrażeniem stałym znanym w czasie kompilacji. case n:, gdzie n jest zmienną, to błąd, a case 3 + 4: i case MAX: (stała z #define lub 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żywaj if.

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.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ