Przełącz oświadczenie — Switch statement

W językach programowania komputerów , A Instrukcja switch jest rodzajem mechanizmu kontroli wybór wykorzystywane w celu umożliwienia wartość zmiennej lub wyrażenia zmienić przepływ sterowania z realizacją programu poprzez wyszukiwanie i mapy.

Instrukcje Switch działają nieco podobnie do ifinstrukcji używanych w językach programowania, takich jak C / C++ , C# , Visual Basic .NET , Java i istnieją w większości imperatywnych języków programowania wysokiego poziomu , takich jak Pascal , Ada , C / C++ , C# , Visual Basic. NET , Java , oraz w wielu innych typach języka, używając takich słów kluczowych jak switch, case, selectczy inspect.

Instrukcje switch występują w dwóch głównych wariantach: przełącznik strukturalny, jak w Pascalu, który przyjmuje dokładnie jedną gałąź, oraz przełącznik niestrukturalny, jak w C, który działa jako typ goto . Główne powody używania przełącznika to poprawa przejrzystości, poprzez zmniejszenie w innym przypadku powtarzalnego kodowania, a także (jeśli pozwala na to heurystyka ) również oferowanie możliwości szybszego wykonania dzięki łatwiejszej optymalizacji kompilatora w wielu przypadkach.

Przełącz oświadczenie w C
switch (age) {
  case 1:  printf("You're one.");            break;
  case 2:  printf("You're two.");            break;
  case 3:  printf("You're three.");
  case 4:  printf("You're three or four.");  break;
  default: printf("You're not 1,2,3 or 4!");
}

Historia

W swoim tekście Wprowadzenie do metamatematyki z 1952 roku Stephen Kleene formalnie dowiódł, że funkcja CASE (funkcja JEŻELI-WTEDY-ELSE jest jej najprostszą formą) jest pierwotną funkcją rekurencyjną , w której definiuje to pojęcie definition by casesw następujący sposób:

"#F. Funkcja zdefiniowana w ten sposób
φ(x 1 , ... , x n ) =
  • φ 1 (x 1 , ... , x n ) jeśli Q 1 (x 1 , ... , x n ),
  • . . . . . . . . . . . .
  • φ m (x 1 , ... , x n ) jeśli Q m (x 1 , ... , x n ),
  • φ m+1 (x 1 , ... , x n ) inaczej,
gdzie Q 1 , ... , Q m są wzajemnie wykluczającymi się predykatami (lub φ(x 1 , ... , x n ) ma wartość podaną w pierwszym zdaniu, które ma zastosowanie) jest pierwotną rekurencyjną w φ 1 , ... , φ m+1 , Q 1 , ..., Q m+1 .

Kleene dostarcza na to dowód w postaci funkcji rekurencyjnych typu Boole'a „znak” sg() i „nie znak” ~sg() (Kleene 1952:222-223); pierwszy zwraca 1, jeśli jego wejście jest dodatnie i -1, jeśli jego wejście jest ujemne.

Boolos-Burgess-Jeffrey poczynił dodatkową obserwację, że „definicja przez przypadki” musi być zarówno wzajemnie wykluczająca się, jak i zbiorowo wyczerpująca. Oni również oferują dowód na prymitywną rekurencyjność tej funkcji (Boolos-Burgess-Jeffrey 2002: 74-75).

IF-THEN-ELSE jest podstawą formalizmu McCarthy'ego : jego użycie zastępuje zarówno pierwotną rekurencję, jak i operator mu .

Typowa składnia

W większości języków programiści piszą instrukcję switch w wielu osobnych wierszach, używając jednego lub dwóch słów kluczowych. Typowa składnia obejmuje:

  • pierwszy select, po którym następuje wyrażenie, które jest często nazywane wyrażeniem sterującym lub zmienną sterującą instrukcji switch
  • kolejne wiersze definiujące rzeczywiste przypadki (wartości), wraz z odpowiadającymi im sekwencjami instrukcji do wykonania w przypadku dopasowania
  • W językach z zachowaniem błędnym, breakinstrukcja zwykle następuje po caseinstrukcji, aby ją zakończyć. [Studnie]

Każdy alternatywny rozpoczyna się od konkretnej wartości lub lista wartości (patrz niżej), że zmienna sterowania mogą być różne, i które powoduje, że kontrolę GoTo odpowiadającej sekwencji instrukcji. Wartość (lub lista/zakres wartości) jest zwykle oddzielona od odpowiedniej sekwencji instrukcji dwukropkiem lub strzałką implikacji. W wielu językach każdy przypadek musi być poprzedzony słowem kluczowym, takim jak caselub when.

Opcjonalna domyślna sprawa jest zazwyczaj również dozwolone, określony przez default, otherwiselub elsesłowa kluczowego. Jest wykonywany, gdy żaden z pozostałych przypadków nie pasuje do wyrażenia sterującego. W niektórych językach, takich jak C, jeśli żadna wielkość liter nie pasuje i defaultzostanie pominięte, switchinstrukcja po prostu kończy działanie. W innych, takich jak PL/I, zgłaszany jest błąd.

Semantyka

Semantycznie istnieją dwie główne formy instrukcji switch.

Pierwsza forma to przełączniki strukturalne, jak w Pascalu, gdzie pobierana jest dokładnie jedna gałąź, a przypadki traktowane są jako oddzielne, wyłączne bloki. Działa to jako uogólniony warunek warunkowy if – then – else , tutaj z dowolną liczbą rozgałęzień, a nie tylko dwoma.

Druga forma to niestrukturalne przełączniki, jak w C, gdzie przypadki są traktowane jako etykiety w jednym bloku, a przełącznik działa jako uogólnione goto. To rozróżnienie jest określane jako leczenie zaniku, które jest omówione poniżej.

Upadek

W wielu językach wykonywany jest tylko pasujący blok, a wykonanie jest kontynuowane na końcu instrukcji switch. Należą do nich rodzina Pascal (Object Pascal, Modula, Oberon, Ada itp.), a także PL/I , nowoczesne formy dialektów Fortran i BASIC pod wpływem Pascala, większość języków funkcjonalnych i wiele innych. Aby umożliwić wielu wartościom wykonanie tego samego kodu (i uniknąć konieczności powielania kodu ), języki typu Pascal zezwalają na dowolną liczbę wartości na wielkość liter, podaną jako lista rozdzielana przecinkami, jako zakres lub jako kombinacja.

Języki wywodzące się z języka C, a bardziej ogólnie te, na które wpływa obliczona przez Fortran obliczona wartość GOTO , zamiast tego posiadają funkcję przejściową, w której sterowanie przenosi się do pasującej wielkości liter, a następnie wykonanie jest kontynuowane („przechodzi”) do instrukcji powiązanych z następną wielkością liter w tekście źródłowym . Pozwala to również na dopasowanie wielu wartości do tego samego punktu bez specjalnej składni: są one po prostu wyświetlane z pustymi treściami. Wartości mogą być specjalnie uwarunkowane kodem w treści sprawy. W praktyce przeskakiwanie jest zwykle zapobiegane za pomocą breaksłowa kluczowego na końcu dopasowywanego ciała, które kończy wykonywanie bloku przełącznika, ale może to spowodować błędy z powodu niezamierzonego przeskoku, jeśli programista zapomni wstawić breakinstrukcję. Jest to zatem przez wielu postrzegane jako brodawka językowa i ostrzegane przed niektórymi narzędziami lintowymi. Pod względem składniowym przypadki są interpretowane jako etykiety, a nie bloki, a instrukcje switch i break jawnie zmieniają przepływ sterowania. Niektóre języki, na które ma wpływ C, takie jak JavaScript , zachowują domyślną wartość zastępczą, podczas gdy inne usuwają znaczniki zastępcze lub zezwalają na to tylko w szczególnych okolicznościach. Godne uwagi odmiany tego w rodzinie C obejmują C# , w którym wszystkie bloki muszą być zakończone znakiem breaklub, returnchyba że blok jest pusty (tj. Fallthrough jest używany jako sposób na określenie wielu wartości).

W niektórych przypadkach języki zapewniają opcjonalną rezerwę. Na przykład, Perl nie spełnia domyślnie, ale przypadek może jawnie zrobić to za pomocą continuesłowa kluczowego. Zapobiega to niezamierzonemu upadkowi, ale umożliwia to w razie potrzeby. Podobnie, Bash domyślnie nie przechodzi przez ;;, ale zezwala na przejście przez ;&lub ;;&zamiast tego.

Przykładem instrukcji switch, która opiera się na fallthrough, jest urządzenie Duffa .

Kompilacja

Kompilatory optymalizujące, takie jak GCC lub Clang, mogą skompilować instrukcję switch do tabeli rozgałęzień lub przeszukiwać wartości binarne w tych przypadkach. Tabela rozgałęzień pozwala instrukcji switch określić za pomocą małej, stałej liczby instrukcji, którą gałąź wykonać bez konieczności przechodzenia przez listę porównań, podczas gdy wyszukiwanie binarne obejmuje tylko logarytmiczną liczbę porównań, mierzoną liczbą przypadków w instrukcja switch.

Zwykle jedyną metodą sprawdzenia, czy ta optymalizacja wystąpiła, jest rzeczywiste spojrzenie na wynikowy zestaw lub kod maszynowy , który został wygenerowany przez kompilator.

Zalety i wady

W niektórych językach i środowiskach programistycznych użycie instrukcji caselub switchjest uważane za lepsze od równoważnych serii instrukcji if else if , ponieważ jest to:

  • Łatwiejsze debugowanie (np. ustawianie punktów przerwania w kodzie w porównaniu z tabelą wywołań, jeśli debugger nie ma możliwości warunkowego punktu przerwania)
  • Łatwiejsze dla osoby do czytania
  • Łatwiejsze do zrozumienia, a co za tym idzie łatwiejsze w utrzymaniu
  • Naprawiono głębokość: sekwencja instrukcji "jeśli inaczej, jeśli" może dać głębokie zagnieżdżenie, utrudniając kompilację (szczególnie w automatycznie generowanym kodzie)
  • Łatwiejsze sprawdzenie, czy wszystkie wartości są obsługiwane. Kompilatory mogą wygenerować ostrzeżenie, jeśli niektóre wartości wyliczenia nie są obsługiwane.

Ponadto zoptymalizowana implementacja może być wykonywana znacznie szybciej niż alternatywa, ponieważ często jest implementowana przy użyciu indeksowanej tabeli rozgałęzień . Na przykład, decydowanie o przebiegu programu na podstawie wartości pojedynczego znaku, jeśli jest prawidłowo zaimplementowane, jest znacznie bardziej wydajne niż alternatywa, znacznie redukując długość ścieżki instrukcji . Kiedy zaimplementowana jako taka, instrukcja switch zasadniczo staje się doskonałym hashem .

Jeśli chodzi o wykres przepływu sterowania , instrukcja switch składa się z dwóch węzłów (wejścia i wyjścia) oraz jednej krawędzi między nimi dla każdej opcji. Z kolei sekwencja instrukcji „if...else if...else if” ma dodatkowy węzeł dla każdego przypadku innego niż pierwszy i ostatni, wraz z odpowiednią krawędzią. Wynikowy wykres przepływu sterowania dla sekwencji „jeśli” ma zatem znacznie więcej węzłów i prawie dwa razy więcej krawędzi, które nie dodają żadnych użytecznych informacji. Jednak proste gałęzie w instrukcjach if są indywidualnie prostsze pod względem koncepcyjnym niż złożone gałęzie instrukcji switch. Jeśli chodzi o złożoność cyklomatyczną , obie te opcje zwiększają ją o k -1, jeśli dane k przypadków.

Przełącz wyrażenia

Wyrażenia przełącznika zostały wprowadzone w języku Java SE 12 19 marca 2019 r. jako funkcja zapoznawcza . Tutaj całe wyrażenie switch może zostać użyte do zwrócenia wartości. Istnieje również nowa forma etykiety przypadku, w case L->której prawa strona jest pojedynczym wyrażeniem. Zapobiega to również upadkom i wymaga, aby przypadki były wyczerpujące. W Java SE 13 wprowadzono yieldinstrukcję, aw Java SE 14 wyrażenia przełączające stają się funkcją standardowego języka. Na przykład:

int ndays = switch(month) {
    case JAN, MAR, MAY, JUL, AUG, OCT, DEC -> 31;
    case APR, JUN, SEP, NOV -> 30;
    case FEB -> {
        if(year % 400 ==0) yield 29;
        else if(year % 100 == 0) yield 28;
        else if(year % 4 ==0) yield 29;
        else yield 28; }
};

Alternatywne zastosowania

Wiele języków ocenia wyrażenia wewnątrz switchbloków w czasie wykonywania, co pozwala na wiele mniej oczywistych zastosowań konstrukcji. Zabrania to pewnych optymalizacji kompilatora, więc jest bardziej powszechne w językach dynamicznych i skryptowych, w których zwiększona elastyczność jest ważniejsza niż obciążenie wydajnościowe.

PHP

Na przykład w PHP , stała może być użyta jako "zmienna" do sprawdzenia, a pierwsza instrukcja case, która obliczy tę stałą, zostanie wykonana:

switch (true) {
    case ($x == 'hello'):
        foo();
        break;
    case ($z == 'howdy'): break;
}
switch (5) {
    case $x: break;
    case $y: break;
}

Ta funkcja jest również przydatna do sprawdzania wielu zmiennych względem jednej wartości, a nie jednej zmiennej względem wielu wartości. COBOL obsługuje również ten formularz (i inne formularze) w EVALUATEoświadczeniu. PL/I ma alternatywną formę SELECTinstrukcji, w której wyrażenie sterujące jest całkowicie pomijane i wykonywane jest pierwsze, WHENktórego wynikiem jest prawda .

Rubin

W Ruby , ze względu na obsługę ===równości, instrukcja może być użyta do testowania klasy zmiennej:

case input
when Array then puts 'input is an Array!'
when Hash then puts 'input is a Hash!'
end

Ruby zwraca również wartość, która może być przypisana do zmiennej i tak naprawdę nie wymaga caseżadnych parametrów (działa trochę jak else ifinstrukcja):

catfood =
  case
  when cat.age <= 1
    junior
  when cat.age > 10
    senior
  else
    normal
  end

Monter

Instrukcja switch w asemblerze :

switch:
  cmp ah, 00h
  je a
  cmp ah, 01h
  je b
  jmp swtend   ; No cases match or "default" code here
a:
  push ah
  mov al, 'a'
  mov ah, 0Eh
  mov bh, 00h
  int 10h
  pop ah
  jmp swtend   ; Equivalent to "break"
b:
  push ah
  mov al, 'b'
  mov ah, 0Eh
  mov bh, 00h
  int 10h
  pop ah
  jmp swtend   ; Equivalent to "break"
  ...
swtend:

Obsługa wyjątków

Wiele języków implementuje formę instrukcji switch w obsłudze wyjątków , gdzie jeśli wyjątek zostanie zgłoszony w bloku, wybierana jest osobna gałąź, w zależności od wyjątku. W niektórych przypadkach obecna jest również gałąź domyślna, jeśli nie zostanie zgłoszony żaden wyjątek. Wczesnym przykładem jest Modula-3 , który używa składni TRY... EXCEPT, gdzie każdy EXCEPTdefiniuje przypadek. Można to również znaleźć w Delphi , Scala i Visual Basic.NET .

Alternatywy

Niektóre alternatywy dla instrukcji switch to:

  • Seria warunków warunkowych if-else , które sprawdzają wartość docelową po jednej wartości. Zachowanie opadowe można osiągnąć za pomocą sekwencji warunkowych if bez klauzuli else .
  • Tabeli odnośników , które zawiera, jak klucze, casewartości i, jak wartościami, część pod caseoświadczeniem.
(W niektórych językach tylko rzeczywiste typy danych są dozwolone jako wartości w tabeli przeglądowej. W innych językach możliwe jest również przypisanie funkcji jako wartości tabeli przeglądowej, co zapewnia taką samą elastyczność, jak w przypadku prawdziwej switchinstrukcji. Więcej informacji można znaleźć w artykule Tablica kontrolna na to).
Lua nie obsługuje instrukcji case/switch: http://lua-users.org/wiki/SwitchStatement . Ta technika wyszukiwania jest jednym ze sposobów implementacji switchinstrukcji w języku Lua, który nie ma wbudowanego switch.
W niektórych przypadkach, tablice przeglądowe są bardziej wydajne niż nie- zoptymalizowanych switch sprawozdań od wielu językach można zoptymalizować wyszukiwań stole, natomiast sprawozdanie przełączające nie są zoptymalizowane, chyba że zakres wartości jest mała z kilkoma przerwami. Jednak niezoptymalizowane, niebinarne wyszukiwanie wyszukiwania będzie prawie na pewno wolniejsze niż niezoptymalizowany przełącznik lub równoważne wielokrotne instrukcje if-else .
  • Tabela sterująca (które mogą być realizowane jako prosty tabeli przeglądowej) może być również dostosowane do pomieścić wiele warunków na wielu nakładów w razie potrzeby i zazwyczaj wykazuje większą zwartość wizualną „” niż równoważny przełącznik (które mogą zajmować wiele wypowiedzi).
  • Dopasowywanie wzorców , które jest używane do implementowania funkcji podobnych do przełączników w wielu językach funkcjonalnych .

Zobacz też

Bibliografia

Dalsza lektura