Přepnout prohlášení - Switch statement

V počítačových programovacích jazycích je příkaz switch typem mechanismu řízení výběru, který umožňuje hodnotě proměnné nebo výrazu změnit řídicí tok provádění programu pomocí vyhledávání a mapy.

Přepínací příkazy fungují poněkud podobně jako ifpříkazy používané v programovacích jazycích jako C / C ++ , C# , Visual Basic .NET , Java a existují ve většině imperativních programovacích jazyků na vysoké úrovni, jako jsou Pascal , Ada , C / C ++ , C# , Visual Basic. NET , Java , a v mnoha jiných typů jazyka, používání takových klíčových slov , jako switch, case, selectnebo inspect.

Příkazy přepínačů se dodávají ve dvou hlavních variantách: strukturovaný přepínač, jako v Pascalu, který má přesně jednu větev, a nestrukturovaný přepínač, jako v jazyce C, který funguje jako typ goto . Mezi hlavní důvody pro použití přepínače patří zlepšení srozumitelnosti snížením jinak opakujícího se kódování a (pokud to heuristika dovoluje) také nabízí potenciál pro rychlejší provedení díky snazší optimalizaci překladače v mnoha případech.

Přepnout příkaz v 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!");
}

Dějiny

V jeho 1952 textu Úvod do metamathematics , Stephen Kleene formálně prokázáno, že funkce CASE (IF-THEN-ELSE funkce je nejjednodušší forma) je primitivní rekurzivní funkce , kde se definuje pojem definition by casesnásledujícím způsobem:

"#F. Takto definovaná funkce φ."
φ (x 1 , ..., x n ) =
  • φ 1 (x 1 , ..., x n ) pokud Q 1 (x 1 , ..., x n ),
  • . . . . . . . . . . . .
  • φ m (x 1 , ..., x n ) pokud Q m (x 1 , ..., x n ),
  • φ m+1 (x 1 , ..., x n ) jinak,
kde Q 1 , ..., Q m jsou vzájemně se vylučující predikáty (nebo φ (x 1 , ..., x n ) musí mít hodnotu danou první klauzulí, která platí) je primitivní rekurzivní v φ 1 , ... , φ m+1 , Q 1 , ..., Q m+1 .

Kleene je toho důkazem ve smyslu booleovských rekurzivních funkcí „sign-of“ sg () a „not sign of“ ~ sg () (Kleene 1952: 222-223); první vrátí 1, pokud je jeho vstup kladný, a −1, pokud je jeho vstup záporný.

Boolos-Burgess-Jeffrey učinil další poznámku, že „definice podle případů“ musí být vzájemně se vylučující a kolektivně vyčerpávající. I oni nabízejí důkaz primitivní rekurzivity této funkce (Boolos-Burgess-Jeffrey 2002: 74-75).

IF-THEN-ELSE je základem McCarthyho formalismu : jeho použití nahrazuje jak primitivní rekurzi, tak mu-operátor .

Typická syntaxe

Ve většině jazyků programátoři píší příkaz switch na mnoha jednotlivých řádcích pomocí jednoho nebo dvou klíčových slov. Typická syntaxe zahrnuje:

  • první select, za kterým následuje výraz, který je často označován jako kontrolní výraz nebo ovládací proměnná příkazu switch
  • následující řádky definující skutečné případy (hodnoty), s odpovídajícími sekvencemi příkazů pro provedení v případě shody
  • V jazycích s přechodovým chováním breakpříkaz obvykle následuje za casepříkazem k ukončení uvedeného prohlášení. [Wells]

Každá alternativa začíná konkrétní hodnotu, nebo seznam hodnot (viz níže), že řídící proměnné může odpovídat, a které způsobí, že ovládací prvek Goto odpovídající posloupnost příkazů. Hodnota (nebo seznam/rozsah hodnot) je obvykle oddělena od odpovídající sekvence příkazů dvojtečkou nebo implikační šipkou. V mnoha jazycích musí každému případu také předcházet klíčové slovo jako casenebo when.

Volitelný standardní případ je typicky také povoleno, specifikována default, otherwisenebo elseklíčového slova. Spustí se, když žádný z ostatních případů neodpovídá výrazu ovládacího prvku. V některých jazycích, například C, pokud se žádný případ neshoduje a defaultje vynechán, switchpříkaz jednoduše skončí. V jiných, jako PL/I, je vyvolána chyba.

Sémantika

Sémanticky existují dvě hlavní formy příkazů přepínače.

První formou jsou strukturované přepínače, jako v Pascalu, kde je brána přesně jedna větev, a případy jsou považovány za samostatné, exkluzivní bloky. Funguje to jako generalizovaná podmínka , pokud - pak - jinak , zde s libovolným počtem větví, nejen se dvěma.

Druhou formou jsou nestrukturované přepínače, jako v C, kde jsou případy zpracovány jako popisky v rámci jednoho bloku a přepínač funguje jako generalizovaný přechod. Toto rozlišení se označuje jako zacházení s propadem, které je popsáno níže.

Propadnout

V mnoha jazycích je spuštěn pouze odpovídající blok a potom provádění pokračuje na konci příkazu switch. Patří sem rodina Pascalů (Object Pascal, Modula, Oberon, Ada atd.) A také PL/I , moderní formy dialektů Fortran a BASIC ovlivněné Pascalem, většinou funkčních jazyků a mnoha dalšími. Aby bylo možné spouštět stejný kód více hodnotami (a vyhnout se nutnosti duplikovat kód ), jazyky typu Pascal povolují libovolný počet hodnot na případ, udávaný jako seznam oddělený čárkami, jako rozsah nebo jako kombinace.

Jazyky odvozené z jazyka C, a obecněji ty, které jsou ovlivněny vypočítaným GOTO od Fortranu , místo toho obsahují propad, kde se ovládání přesune na odpovídající případ a poté pokračuje provádění („propadá“) do příkazů spojených s dalším případem ve zdrojovém textu . To také umožňuje, aby více hodnot odpovídalo stejnému bodu bez speciální syntaxe: jsou uvedeny pouze s prázdnými těly. Hodnoty mohou být speciálně podmíněné kódem v těle pouzdra. V praxi se obvykle propadu zabrání breakklíčovým slovem na konci odpovídajícího těla, které ukončí provádění bloku přepínačů, ale to může způsobit chyby v důsledku neúmyslného propadu, pokud programátor zapomene vložit breakpříkaz. To je tedy mnohými vnímáno jako jazyková bradavice a varováno před některými nástroji na odstraňování vláken. Syntakticky jsou případy interpretovány jako popisky, nikoli bloky a příkazy switch a break výslovně mění tok řízení. Některé jazyky ovlivněné jazykem C, například JavaScript , si zachovávají výchozí propad, zatímco jiné pád procházejí, nebo jej povolují pouze za zvláštních okolností. Pozoruhodné variace na toto v C-rodině zahrnují C# , ve kterém musí být všechny bloky zakončeny a breaknebo returnpokud není blok prázdný (tj. Jako způsob určení více hodnot se používá propad).

V některých případech jazyky poskytují volitelný přechod. Například Perl ve výchozím nastavení nepropadá, ale případ to může výslovně udělat pomocí continueklíčového slova. Tím se zabrání neúmyslnému propadnutí, ale v případě potřeby to umožní. Podobně Bash ve výchozím nastavení ;;nepropadne, když skončí s , ale povolí propad s ;&nebo ;;&místo.

Příkladem příkazu switch, který se spoléhá na propad, je zařízení Duff .

Sestavení

Optimalizace kompilátorů, jako je GCC nebo Clang, může zkompilovat příkaz switch do větvící tabulky nebo binárního vyhledávání hodnotami v případech. Odvětvová tabulka umožňuje příkazu switch určit pomocí malého, konstantního počtu instrukcí, kterou větev provést, aniž byste museli procházet seznamem srovnání, zatímco binární vyhledávání trvá pouze logaritmický počet srovnání, měřeno počtem případů v příkaz switch.

Normálně je jediným způsobem, jak zjistit, zda k této optimalizaci došlo, skutečným pohledem na výslednou sestavu nebo výstup strojového kódu , který byl generován kompilátorem.

Výhody a nevýhody

V některých jazycích a programovacích prostředích je použití příkazu a casenebo switchpovažováno za lepší než ekvivalentní řada příkazů if else if, protože je:

  • Jednodušší ladění (např. Nastavení zarážek na kódu vs. tabulka volání, pokud ladicí program nemá schopnost podmíněného zarážky)
  • Čtení pro člověka je snazší
  • Snadněji pochopitelné a následně snadněji udržovatelné
  • Opravená hloubka: sekvence příkazů „if else if“ může vést k hlubokému vnoření, což komplikuje kompilaci (zejména v automaticky generovaném kódu)
  • Jednodušší ověření, že jsou zpracovány všechny hodnoty. Kompilátory mohou vydat varování, pokud nejsou zpracovány některé hodnoty výčtu.

Navíc je optimalizovaná implementace může vykonat mnohem rychleji než druhá možnost, protože to je často realizována pomocí indexovaných větev tabulky . Například rozhodování o toku programu na základě hodnoty jednoho znaku, je -li správně implementováno, je mnohem efektivnější než alternativa, což značně zkracuje délku cesty instrukce . Při implementaci jako takové se příkaz switch v podstatě stane dokonalým hashem .

Pokud jde o graf toku toku , příkaz přepínače se skládá ze dvou uzlů (vstup a výstup) plus jedné hrany mezi nimi pro každou možnost. Naopak sekvence příkazů „if ... else if ... else if“ má další uzel pro každý jiný případ než první a poslední, spolu s odpovídající hranou. Výsledný graf řídicího toku pro sekvence „if“ s má tedy mnohem více uzlů a téměř dvakrát tolik hran, přičemž tyto nepřidávají žádné užitečné informace. Jednoduché větve v příkazech if jsou však koncepčně jednodušší než komplexní větev příkazu switch. Pokud jde o cyklomatickou složitost , obě tyto možnosti ji zvýší o k −1, pokud je dáno k případů.

Přepnout výrazy

Výrazy přepínačů jsou v Java SE 12 , 19. března 2019, zavedeny jako funkce náhledu. Zde lze pro vrácení hodnoty použít celý výraz přepínače. K dispozici je také nová forma označení případu, case L->kde na pravé straně je jeden výraz. To také zabraňuje pádu a vyžaduje, aby případy byly vyčerpávající. V jazyce Java SE 13 je tento yieldpříkaz zaveden a v jazyce Java SE 14 se výrazy přepínačů stávají funkcí standardního jazyka. Například:

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; }
};

Alternativní použití

Mnoho jazyků vyhodnocuje výrazy uvnitř switchbloků za běhu, což umožňuje řadu méně zřejmých použití pro konstrukci. To zakazuje určité optimalizace kompilátoru, takže je běžnější v dynamických a skriptovacích jazycích, kde je vylepšená flexibilita důležitější než režie výkonu.

PHP

Například v PHP lze konstantu použít jako „proměnnou“ ke kontrole a provede se první příkaz případu, který tuto konstantu vyhodnotí:

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

Tato funkce je také užitečná pro kontrolu více proměnných oproti jedné hodnotě místo jedné proměnné podle mnoha hodnot. COBOL také podporuje tento formulář (a další formuláře) v EVALUATEprohlášení. PL/I má alternativní formu SELECTpříkazu, kde je ovládací výraz zcela vynechán a je proveden první, WHENkterý vyhodnotí hodnotu true .

Rubín

V Ruby , kvůli jeho zpracování ===rovnosti, lze příkaz použít k testování třídy proměnné:

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

Ruby také vrací hodnotu, kterou lze přiřadit proměnné, a ve skutečnosti nevyžaduje, caseaby měla nějaké parametry (působí trochu jako else ifpříkaz):

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

Montér

Přepínač v jazyce sestavení :

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:

Zpracování výjimek

Řada jazyků implementuje formu příkazu switch při zpracování výjimek , kde pokud je v bloku vyvolána výjimka, je vybrána samostatná větev v závislosti na výjimce. V některých případech je k dispozici také výchozí větev, pokud není vyvolána žádná výjimka. Raným příkladem je Modula-3 , který používá TRY... EXCEPTsyntaxi, kde každý EXCEPTdefinuje případ. To je také v Delphi , Scala a Visual Basic.NET .

Alternativy

Některé alternativy k přepnutí příkazů mohou být:

  • Série podmíněných podmínek if-else, které zkoumají cílovou hodnotu najednou. Přepadového chování lze dosáhnout pomocí sekvence if podmíněných bez klauzule else .
  • Vyhledávací tabulka , která obsahuje, jako klíče, o casehodnotách a jako hodnoty, v části pod caseprohlášení.
(V některých jazycích jsou jako hodnoty ve vyhledávací tabulce povoleny pouze skutečné datové typy. V jiných jazycích je také možné přiřadit funkce jako hodnoty vyhledávací tabulky a získat stejnou flexibilitu jako skutečné switchprohlášení. Podrobnější informace naleznete v článku Ovládací tabulka Na toto).
Lua nepodporuje příkazy case/switch: http://lua-users.org/wiki/SwitchStatement . Tato vyhledávací technika je jedním ze způsobů implementace switchpříkazů v jazyce Lua, který nemá žádný vestavěný switch.
V některých případech jsou vyhledávací tabulky účinnější než neoptimalizované switch příkazy, protože mnoho jazyků může optimalizovat vyhledávání tabulek, zatímco příkazy přepínače nejsou optimalizovány, pokud není rozsah hodnot malý s několika mezerami. Non-optimalizované, non- binární vyhledávání vyhledávání, nicméně, téměř jistě bude pomalejší než jeden přepínač non-optimalizovaná nebo ekvivalentní násobku if-else závěrky.
  • Kontrolní stůl (která může být provedena jako jednoduchá vyhledávací tabulka) může být také přizpůsoben pro uložení více podmínek na více vstupů v případě potřeby a obvykle vykazuje větší ‚vizuální kompaktnosti‘, než ekvivalentní přepínač (které mohou zabírat mnoho příkazy).
  • Pattern matching , který se používá k implementaci funkcí podobných přepínačům v mnoha funkčních jazycích.

Viz také

Reference

Další čtení