Instrukcja przełączania

W językach programowania komputerowego instrukcja switch jest rodzajem mechanizmu kontroli wyboru używanego do umożliwienia wartości zmiennej lub wyrażenia zmiany przepływu sterowania wykonaniem programu poprzez wyszukiwanie i mapowanie.

Instrukcje switch działają nieco podobnie do instrukcji if 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 i w wielu innych typach języków, używając takich słów kluczowych jak przełączyć , obudowa , wybrać lub sprawdzić .

Instrukcje switch występują w dwóch głównych wariantach: przełącznik strukturalny, jak w Pascalu, który zajmuje dokładnie jedną gałąź, oraz przełącznik niestrukturalny, jak w C, który działa jako rodzaj goto . Głównymi powodami używania przełącznika są poprawa przejrzystości poprzez ograniczenie powtarzalnego kodowania oraz (jeśli heurystyka ) także oferowanie możliwości szybszego wykonania dzięki łatwiejszej optymalizacji kompilatora w wielu przypadkach.

Instrukcja switch w języku C
  
                 
                 
     
      switch  (  wiek  )  {  przypadek  1  :  printf  (  „Jesteś jeden.”  );  przerwa  ;  przypadek  2  :  printf  (  "Jesteście dwa razy."  );  przerwa  ;  przypadek  3  :  printf  (  "Masz trzy lata."  );  przypadek  4  :  printf  (  "Masz trzy lub cztery lata."  );   
   
 przerwa  ;  default  :  printf  (  "Nie jesteś 1, 2, 3 ani 4!"  );  } 

Historia

W swoim tekście Wprowadzenie do metamatematyki z 1952 r . Stephen Kleene formalnie udowodnił, że funkcja CASE (funkcja JEŻELI-TO-JEŻELI jest jej najprostszą postacią) jest prymitywną funkcją rekurencyjną , w której definiuje definicję pojęcia przez przypadki w 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 ) będzie miało wartość określoną przez pierwsze zdanie, które ma zastosowanie) jest prymitywnie rekurencyjne w φ 1 , ... , φ m+1 , Q 1 , ..., Q m+1 .

Kleene dostarcza na to dowód w kategoriach rekurencyjnych funkcji typu boolowskiego „sign-of” sg() i „not sign of” ~sg() (Kleene 1952:222-223); pierwszy zwraca 1, jeśli jego wejście jest dodatnie, a −1, jeśli jego wejście jest ujemne.

Boolos-Burgess-Jeffrey poczynił dodatkową obserwację, że „definicja według przypadków” musi być zarówno wzajemnie wykluczająca się , jak i zbiorowo wyczerpująca . One również stanowią dowód prymitywnej rekurencyjności tej funkcji (Boolos-Burgess-Jeffrey 2002:74-75).

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

Typowa składnia

W większości języków programiści piszą instrukcję switch w wielu pojedynczych 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 określane jako wyrażenie sterujące lub zmienna sterująca instrukcji switch
  • kolejne wiersze określające rzeczywiste przypadki (wartości), z odpowiadającymi im sekwencjami instrukcji do wykonania w przypadku wystąpienia dopasowania
  • W językach z zachowaniem awaryjnym instrukcja break zwykle następuje po instrukcji case , aby zakończyć tę instrukcję. [studnie]
  • W niektórych językach, np. PL/I , wyrażenie kontrolne jest opcjonalne; jeśli nie ma wyrażenia sterującego, wówczas każda alternatywa zaczyna się od WHEN zawierającej wyrażenie logiczne i następuje dopasowanie dla pierwszego przypadku, dla którego to wyrażenie ma wartość true. To użycie jest podobne do struktur if/then/elseif/else w niektórych innych językach, np. Perl .
  • W niektórych językach, np. Rexx , żadne wyrażenie kontrolne nie jest dozwolone, a każda alternatywa zaczyna się od klauzuli WHEN zawierającej wyrażenie boolowskie i następuje dopasowanie dla pierwszego przypadku, dla którego to wyrażenie ma wartość true.

Każda alternatywa zaczyna się od określonej wartości lub listy wartości (patrz poniżej), do których może pasować zmienna kontrolna i które spowodują, że kontrolka przejdzie do odpowiedniej 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ć również poprzedzony słowem kluczowym, takim jak przypadek lub kiedy .

Zazwyczaj dozwolona jest również opcjonalna domyślna wielkość liter, określona przez słowo kluczowe default , else lub else . Jest to wykonywane, 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 pominięto wartość domyślną , instrukcja switch po prostu kończy działanie. W innych, takich jak PL/I, zgłaszany jest błąd.

Semantyka

Pod względem semantycznym istnieją dwie główne formy instrukcji switch.

Pierwszą formą są przełączniki strukturalne, jak w Pascalu, gdzie pobierana jest dokładnie jedna gałąź, a przypadki są traktowane jako osobne, wykluczające się bloki. Funkcjonuje to jako uogólniony warunek warunkowy if-then-else , tutaj z dowolną liczbą gałęzi, a nie tylko dwoma.

Drugą formą są przełączniki nieustrukturyzowane, jak w C, gdzie przypadki są traktowane jako etykiety w obrębie pojedynczego bloku, a przełącznik działa jak uogólnione goto. To rozróżnienie jest określane jako traktowanie opadania, które zostało omówione poniżej.

Przejście

W wielu językach wykonywany jest tylko pasujący blok, a następnie wykonywanie jest kontynuowane na końcu instrukcji switch. Należą do nich Pascal (Object Pascal, Modula, Oberon, Ada itp.), a także PL/I , nowoczesne formy dialektów Fortran i BASIC , na które wpływ miał Pascal, 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 przypadek, podaną jako lista oddzielona przecinkami, jako zakres lub jako kombinacja.

Języki wywodzące się z języka C, a bardziej ogólnie te, na które wpływ miał obliczony przez Fortran GOTO , zamiast tego oferują przejście, w którym kontrola przenosi się do pasującego przypadku, a następnie wykonanie jest kontynuowane („przechodzi”) do instrukcji powiązanych z następnym przypadkiem 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 wymienione z pustymi treściami. Wartości mogą być specjalnie uwarunkowane kodem w treści sprawy. W praktyce przed opadami zwykle zapobiega się przerwą słowo kluczowe na końcu pasującego ciała, które kończy wykonanie bloku przełącznika, ale może to spowodować błędy z powodu niezamierzonego błędu, jeśli programista zapomni wstawić instrukcję break . Jest to zatem postrzegane przez wielu jako brodawka językowa i ostrzegane przed niektórymi narzędziami do kłaczków. Składniowo 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ą awarię, podczas gdy inne usuwają awarię lub zezwalają na nią tylko w szczególnych okolicznościach. Godne uwagi odmiany tego w rodzinie C obejmują C # , w którym wszystkie bloki muszą być zakończone przerwą lub powrotem , chyba że blok jest pusty (tj. przejście pośrednie jest używane jako sposób na określenie wielu wartości).

W niektórych przypadkach języki zapewniają opcjonalną rezerwę. Na przykład Perl domyślnie nie przechodzi, ale przypadek może to zrobić jawnie, używając słowa kluczowego continue . Zapobiega to niezamierzonemu opadowi, ale pozwala na to, gdy jest to pożądane. Podobnie Bash domyślnie nie przechodzi, gdy kończy się przez ;; , ale zamiast tego zezwól na przechodzenie przez ;& lub ;;& .

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

Kompilacja

Kompilatory optymalizujące, takie jak GCC lub Clang , mogą kompilować instrukcję switch do tabeli rozgałęzień lub przeszukiwać binarnie wartości w przypadkach. Tablica rozgałęzień pozwala instrukcji switch określić za pomocą małej, stałej liczby instrukcji, którą gałąź wykonać bez konieczności przeglądania listy porównań, podczas gdy wyszukiwanie binarne wymaga tylko logarytmicznej liczby porównań, mierzonej liczbą przypadków w instrukcja switch.

Zwykle jedyną metodą sprawdzenia, czy ta optymalizacja nastąpiła, jest faktyczne przyjrzenie się wynikowemu zestawowi lub wyjściu kodu maszynowego , który został wygenerowany przez kompilator.

Zalety i wady

W niektórych językach i środowiskach programistycznych użycie instrukcji case lub switch jest uważane za lepsze od równoważnej serii instrukcji if else if, ponieważ:

  • Ł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 do odczytania przez człowieka
  • Łatwiejsze do zrozumienia, a co za tym idzie łatwiejsze w utrzymaniu
  • Naprawiono głębokość: sekwencja instrukcji „if else if” może spowodować głębokie zagnieżdżenie, utrudniając kompilację (szczególnie w kodzie generowanym automatycznie)
  • Łatwiej sprawdzić, czy wszystkie wartości są obsługiwane. Kompilatory mogą generować ostrzeżenie, jeśli niektóre wartości wyliczeniowe nie są obsługiwane.

Ponadto zoptymalizowana implementacja może działać 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 poprawnie zaimplementowane, jest znacznie bardziej wydajne niż alternatywa, znacznie zmniejszając długość ścieżki instrukcji . Zaimplementowana jako taka instrukcja switch zasadniczo staje się idealnym skrótem .

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. Natomiast 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 sterowania przepływem dla sekwencji „jeśli” ma zatem o wiele więcej węzłów i prawie dwa razy więcej krawędzi, przy czym nie dodają one żadnych użytecznych informacji. Jednak proste gałęzie w instrukcjach if są indywidualnie koncepcyjnie łatwiejsze niż złożone gałęzie instrukcji switch. Pod względem cyklomatyczna złożoność , obie te opcje zwiększają ją o k −1, jeśli dane są k przypadków.

Przełącz wyrażenia

Wyrażenia przełączające zostały wprowadzone w Javie SE 12 , 19 marca 2019 r., jako funkcja podglądu. W tym przypadku do zwrócenia wartości można użyć całego wyrażenia przełączającego. Istnieje również nowa forma etykiety przypadku, przypadek L-> , gdzie prawa strona jest pojedynczym wyrażeniem. Zapobiega to jednak upadkom i wymaga, aby przypadki były wyczerpujące. W Javie SE 13 wprowadzono instrukcję yield , aw Javie SE 14 wyrażenia przełączające stają się standardową funkcją języka. Na przykład:

    
             
          
       
             0  int  ndays  =  switch  (  miesiąc  )  {  przypadek  STYCZEŃ  ,  MAR  ,  MAJ  ,  LIPIEC  ,  SIERPIEŃ  ,  PAŹDZIERNIK  ,  GRUDZIEŃ  ->  31  ;  przypadek  KWIECIEŃ  ,  CZERWIEC  ,  WRZESIEŃ  ,  LISTOPAD  ->  30  ;  przypadek  FEB  ->  {  if  (  rok  %  400  ==  )  wydajność  
              0  
              0  
           
 29  ;  jeszcze  if  (  rok  %  100  ==  )  wydajność  28  ;  jeszcze  if  (  rok  %  4  ==  )  wydajność  29  ;  w przeciwnym razie  uzyskaj  28  ;  }  }; 

Alternatywne zastosowania

Wiele języków ocenia wyrażenia wewnątrz bloków przełączników w czasie wykonywania, umożliwiając szereg mniej oczywistych zastosowań konstrukcji. Uniemożliwia to pewne optymalizacje kompilatora, więc jest bardziej powszechne w językach dynamicznych i skryptowych, w których zwiększona elastyczność jest ważniejsza niż narzut związany z wydajnością.

PHP

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

  
       
        
        
        

  
      
      
 switch  (  prawda  )  {  case  (  $x  ==  'cześć'  )  :  foo  ();  przerwa  ;  sprawa  (  $z  ==  'cześć'  )  :  przerwa  ;  }  przełącz  (  5  )  {  przypadek  $x  :  przerwa  ;  przypadek  $y  :  przerwa  ;  } 

Ta funkcja jest również przydatna do sprawdzania wielu zmiennych z jedną wartością, a nie jednej zmiennej z wieloma wartościami. COBOL obsługuje również ten formularz (i inne formularze) w EVALUATE . PL/I ma alternatywną postać SELECT , w której całkowicie pomija się wyrażenie kontrolne i wykonuje się pierwsze WHEN , które ma wartość true .

Rubin

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

 
    
    
 case  input  , gdy  Array  umieszcza  następnie  „input is an Array!”  kiedy  Hash  wstawia  „input is a Hash!  koniec 

Ruby zwraca również wartość, którą można przypisać zmiennej, i tak naprawdę nie wymaga, aby przypadek miał jakiekolwiek parametry (działając trochę jak instrukcja else if ):

 
  
     
    
     
    
  
    
   catfood  =  przypadek  , gdy  cat  .  wiek  <=  1  młodszy  , gdy  kat  .  wiek  >  10 lat  starszy  inny  normalny  koniec 

Monter

Instrukcja switch w asemblerze :


    
   
    
   
      

   
    
    
    
   
   
      

    przełącznik:  cmp  ah  ,  00h  je  a  cmp  ah  ,  01h  je  b  jmp  swtend  ; Żaden przypadek nie pasuje ani nie ma kodu „domyślnego”   a:  push  ah  mov  al  ,  'a'  mov  ah  ,  0Eh  mov  bh  ,  00h  int  10h  pop  ah  jmp  swtend  ; Odpowiednik „przerwy”   b:  naciśnij  ah 
    
    
    
   
   
      
  
 mov  al  ,  'b'  mov  ah  ,  0Eh  mov  bh  ,  00h  int  10h  pop  ah  jmp  swtend  ; Odpowiednik „przerwy”   ...  swtend: 

Pyton

W przypadku Pythona 3.10.6 zaakceptowano PEP 634-636, które dodały słowa kluczowe typu match i case . W przeciwieństwie do innych języków, Python w szczególności nie wykazuje zachowania awaryjnego.

  0 
 
            
     letter  =  input  (  "Wstaw jedną literę: "  )  .  rozebrać  ()[  ]  .  casefold  ()  # pierwszy niebiały znak wejścia, mała  litera  dopasowania  :  case  'a'  |  'e'  |  'ja'  |  'o'  |  'u'  :  # W przeciwieństwie do warunków w instrukcjach if, słowa kluczowego `or` nie można tutaj użyć do rozróżnienia przypadków  print  ( 
   
    
    
     f  „Litera  {  litera  }  to samogłoska!”  )  case  'y'  :  print  (  f  "Litera  {  litera  }  może być samogłoską.)  case  _  :  # ` case _` jest odpowiednikiem `default` z C i innych  print  (  f  "Litera  {  litera  }  nie jest samogłoską !"  ) 

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 oddzielna gałąź, w zależności od wyjątku. W niektórych przypadkach istnieje również domyślna gałąź, jeśli nie zostanie zgłoszony żaden wyjątek. Wczesnym przykładem jest Modula-3 , która używa składni TRY ... EXCEPT , gdzie każdy EXCEPT definiuje wielkość liter. Można to również znaleźć w Delphi , Scala i Visual Basic .NET .

Alternatywy

Niektóre alternatywy dla instrukcji switch mogą być:

  • Seria warunków warunkowych if-else , które badają wartość docelową pojedynczo. Zachowanie awaryjne można osiągnąć za pomocą sekwencji if bez klauzuli else .
  • Tabela przeglądowa , która zawiera jako klucze wartości wielkości liter i jako wartości część pod instrukcją case .
(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ż przypisywanie funkcji jako wartości w tabeli przeglądowej, uzyskując taką samą elastyczność jak prawdziwa instrukcja switch . Więcej informacji można znaleźć w artykule dotyczącym tabeli sterującej szczegóły na ten temat).
Lua nie obsługuje instrukcji case/switch. Ta technika wyszukiwania jest jednym ze sposobów implementacji switch w języku Lua, który nie ma wbudowanego przełącznika .
W niektórych przypadkach tabele przeglądowe są bardziej wydajne niż niezoptymalizowane switch , ponieważ wiele języków może optymalizować przeszukiwanie tabeli, podczas gdy instrukcje switch nie są optymalizowane, chyba że zakres wartości jest mały z kilkoma przerwami. Jednak niezoptymalizowane, niebinarne wyszukiwanie będzie prawie na pewno wolniejsze niż niezoptymalizowany przełącznik lub równoważne wielokrotne instrukcje if-else . [ potrzebne źródło ]
  • tabelę sterującą (którą można zaimplementować jako prostą tabelę przeglądową) można również dostosować w celu uwzględnienia wielu warunków na wielu wejściach i zazwyczaj wykazuje ona większą „wizualną zwartość” niż równoważny przełącznik (który może zajmować wiele instrukcji).
  • Dopasowywanie wzorców , które jest używane do implementacji funkcjonalności podobnej do przełącznika w wielu językach funkcjonalnych .

Zobacz też

Dalsza lektura