Umíme to s Delphi: 121. díl – zkrácené vyhodnocování logických výrazů

V dnešním článku si prakticky na příkladech ukážeme koncepci zkráceného vyhodnocování logických výrazů. Vysvětlíme si, co to zkrácené vyhodnocování je, předvedeme si, jak se používá a poznáme, jakým způsobem jej můžeme zahrnout do aplikační logiky své aplikace.

Dnešní článek bude zaměřen na problematiku, kterou jsme načali před týdnem. Minulý díl seriálu se zabýval logickými výrazy a způsobem jejich vyhodnocování v Object Pascalu (a v Pascalu vůbec). V závěru článku jsem slíbil, že dnešní díl bude zaměřen více prakticky a demonstruje problematiku vyhodnocování logických výrazů na konkrétních příkladech.

Ještě předtím však zasadím dnešní článek do kontextu problematiky, kterou se náš seriál v posledních týdnech zabývá. Pravidelní čtenáři by jistě neměli tento souhrn zapotřebí, nicméně pro ty náhodné je snad užitečný. Nuže, v posledních dílech seriálu se zabýváme především možnostmi nastavení překladače Delphi tak, aby vyhovoval našim požadavkům a aby výsledný (vygenerovaný) spustitelný soubor EXE vypadal tak, jak chceme. Co to znamená? Pomocí nejrůznějších možností nastavení překladače můžeme například ovlivnit, jak si překladač poradí s přetečením, s optimalizací, s předáváním řetězců do podprogramů apod. Pomocí jednotlivých přepínačů tak třeba zajistíme, že aplikace je kompatibilní se staršími verzemi Delphi apod.

Všechny přepínače, kterými se zabýváme, můžeme zapínat/vypínat libovolnou z následujících dvou možností:

  • interaktivně – z hlavní nabídky v Delphi zvolíme Project – Options, vybereme záložku Compiler a zaškrtáváme požadovaná přepínací pole;
  • programově – ve zdrojovém kódu pomocí direktiv {$X+} a {$X-}, kde X je označení příslušné volby.

Nyní si shrneme, které přepínače z uvedené záložky Compiler jsme již úspěšně poznali a kterými jsme se v předchozích částech seriálu již zabývali:

  • skupina Code generation: přepínač Optimization (slouží k zapnutí/vypnutí optimalizace, která se projeví vypuštěním nepotřebných proměnných z výsledného programu),
  • skupina Code generation: přepínač Aligned recorded fields (slouží k zapnutí/vypnutí zarovnávání struktur v paměti do 32-bitových bloků),
  • skupina Code generation: přepínač Stack frames (slouží k ovlivnění způsobu ukládání parametrů procedur a funkcí do zásobníku),
  • skupina Code generation: přepínač Pentium-safe FDIV (slouží k zapnutí/vypnutí generování speciálního kódu odolného vůči chybě při dělení v plovoucí řádové čárce u starších procesorů Pentium),
  • skupina Runtime errors: přepínač Range checking (slouží k zapnutí/vypnutí hlídání rozsahů proměnných),
  • skupina Runtime errors: přepínač I/O chceking (slouží k zapnutí/vypnutí automatického testování chyb při vstupně/výstupních operacích, tj. především při práci se soubory),
  • skupina Runtime errors: přepínač Overflow checking (slouží k zapnutí/vypnutí hlídání přetečení výsledků některých aritmetických operací. Rozdíl mezi Range checking a Overflow checking jsme si vysvětlili v předchozím dílu seriálu).
  • skupina Syntax options: přepínač Strict var-strings (tato volba je funkční pouze v tom případě, že je vypnuta volba Open parametres. Používá se v souvislosti s řetězci typu ShortStrings – a to ještě jen v případě, že jsou parametry tohoto typu předávány do funkcí a procedur. Pokud je volba Strict var-strings zapnutá, hlídá překladač shodu formálních a skutečných parametrů.)
  • skupina Syntax options: přepínač Complete boolean eval (použití tohoto přepínače předznamenává, jak budou v aplikaci vyhodnocovány logické údaje. Pokud je tento přepínač zapnutý, je vždy v logických výrazech vyhodnocena kompletní podmínka. V opačném případě je použito tzv zkrácené vyhodnocování.)

V minulém článku jsme si teoreticky popsali princip vyhodnocování podmínek a logických výrazů v Delphi. Dnes si ukážeme, jak funguje tzv. zkrácené vyhodnocování a jak jej zapnout/vypnout pomocí uvedené direktivy.

Standardně je tato volba vypnutá, což znamená, že při běhu programu se provádí zmíněné zkrácené vyhodnocování. Praktický důsledek je takový, že logický výraz se začne vyhodnocovat zleva doprava, nicméně jeho vyhodnocování skončí v okamžiku, kdy je jednoznačně rozhodnuto o výsledné pravdivostní hodnotě. Vyhodnocování se tedy ukončí v okamžiku, kdy již nemůže být o výsledku výrazu pochyb. Dalšími částmi výrazu se již aplikace nezabývá, ignoruje je a jejich výskyt ve zdrojovém kódu se na běhu programu vůbec nijak neprojeví.

Ukážeme si příklad. Dejme tomu, že se budeme zabývat logickým operátorem AND. Následující tabulka shrnuje nejdůležitější informace o operátoru AND:

Operátor Význam Typ operátoru (počet operandů) Na jaké operandy (datové typy) lze aplikovat Výsledný výraz je pravdivý, pokud...
AND Logický součin Binární Logické (Boolean) jsou-li oba operandy pravdivé

Tyto informace o operátoru AND jsou sice velmi důležité, avšak nikoliv postačující. Potřebujeme totiž také vědět, jaké jsou pravdivostní hodnoty výrazů složených z operátoru AND. Tyto pravdivostní hodnoty shrnuje následující tabulka:

Hodnota A je Hodnota B je Hodnota A and B je
True (pravdivá) True (pravdivá) True (pravdivá)
True (pravdivá) False (nepravdivá) False (nepravdivá)
False (nepravdivá) True (pravdivá) False (nepravdivá)
False (nepravdivá) False (nepravdivá) False (nepravdivá)

Co tato tabulka říká? Že pokud budeme mít dva výroky a spojíme je logickým operátorem AND, bude výsledek (tj. výraz A and B) pravdivý v jediném případě: budou-li pravdivé oba původní výroky, tedy jak výrok A, tak i výrok B. Toto pravidlo plyne ze známé teorie booleovské logiky a věřím, že každý ze čtenářů se s ním již alespoň jednou setkal.

Ukážeme si ještě praktický příklad. Mějme následující dva výroky:

  • Včera venku pršelo.
  • Projíždějící autobus na mě stříkl spršku bahnité vody z kaluže.

Pokud tyto dva výroky spojíme logickým operátorem AND (uděláme tzv. konjunkci), vznikne následující výrok (označme jej třeba C):

  • Včera venku pršelo a projíždějící autobus na mě stříkl spršku bahnité vody z kaluže.

Tento výrok C (rovnající se A and B) je pravdivý pouze a jen v případě, že včera opravdu pršelo a kromě toho že mě autobus opravdu ušpinil vodou z kaluže. Pokud některý z původních výroků není pravdivý, je samozřejmě výrok C též nepravdivý (tj. např. nestačí, že včera pršelo; k pravdivosti C je nutné, abych měl opravdu postříkaný oděv od autobusu. A naopak.).

Co to znamená z hlediska vyhodnocování tohoto výrazu? Že pokud chceme stanovit pravdivost výroku C = A and B, můžeme klidně začít třeba zleva (podobně jako to provádí Pascal). Budeme tedy posuzovat výrok A. Pokud zjistíme, že včera opravdu pršelo (tj. že výrok A je pravdivý), je to pro nás sice zajímavé zjištění, ale z hlediska výroku C jsme se v podstatě nic nedozvěděli. Nedokážeme prozatím rozhodnout o tom, je-li výrok C pravdivý nebo nikoliv. K tomuto rozhodnutí musíme totiž posoudit ještě pravdivost výroku B. Je-li i tento výrok pravdivý (tj. autobus mě postříkl špinavou vodou), pak teprve můžeme prohlásit, že výrok C je pravdivý. A pokud zjistíme, že výrok B je nepravdivý, je i celý výrok C nepravdivý.

Co to znamená? Že pravdivost výroku A nám nedala dostatečnou informaci o pravdivosti celého složeného výroku A and B.

Co by se však stalo, kdyby výrok A (tj. výrok, který jsme posuzovali jako první) byl nepravdivý? Situace by zde byla odlišná. Pokud by byl výrok A nepravdivý, bylo by již zcela a nade vší pochybnost jasné, že výsledek (A and B) bude také nepravdivý. Pravdivost/nepravdivost výroku B by již nemohla toto rozhodnutí nikterak zvrátit: výrok A and B je prostě nepravdivý.

V takovém případě se může počítač rozhodnout, co udělá. Má dvě možnosti, mezi nimiž se rozhodne podle toho, co mu programátor nařídil – právě formou přepínače Complete boolean eval:

  • Pokud programátor nechal zapnuté zkrácené vyhodnocování (přepínač Complete boolean eval je vypnutý – nezatržený), vyhodnocování v tomto okamžiku končí a pravdivost výroku B se nezkoumá. Program pokračuje ve svém běhu následujícím příkazem a výraz C = A and B považuje za pravdivý.
  • Pokud programátor vypnul zkrácené vyhodnocování logických výrazů (přepínač Complete boolean eval je zapnutý – zatržený), vyhodnocování nekončí a pokračuje dál další částí výrazu, tedy výrazem B. Program ignoruje skutečnost, že o pravdivosti výroku C = A and B je již rozhodnuto a vyhodnocuje dál výrok B.

Takto tedy vypadá prakticky zkrácené vyhodnocování logického výrazu. Abychom si vše dokázali lépe představit a abychom viděli, k čemu se dá zapnutí/vypnutí zkráceného vyhodnocování použít, ukážeme si nyní praktický příklad.

Zkrácené vyhodnocování – praktický příklad

Vytvoříme v Delphi novou aplikaci, na formulář umístíme komponenty Memo a dvakrát Button. Cíl aplikace bude jednoduchý: uživatel bude zapisovat text do komponenty Memo a pomocí tlačítka Button1 bude mít možnost uložit obsah komponenty Memo do souboru. Pomocí tlačítka Button2 bude uživatel moci ukončit aplikaci.

Při ukončení aplikace (ať již bude vyvoláno pomocí tlačítka Button2 nebo pomocí systémové nabídky nebo klávesové zkratky Alt-F4) se aplikace nejprve (mírně stupidně) zeptá, zda uživatel chce aplikaci opravdu ukončit. Po odpovědi bude však následovat ještě jeden (naprosto stupidní) dotaz na to, je-li aktuální soubor uložen. Je samozřejmé, že v praxi bychom toto řešení nepoužili (na to, zda chce uživatel opravdu skončit bychom se neptali vůbec a to, zda je soubor uložen, bychom si měli zjistit sami), nicméně pro naše ryze demonstrační účely navržené řešení dobře postačí, protože ukáže, jak ovlivní zapnutí/vypnutí zkráceného vyhodnocování celou aplikační logiku tohoto bodu.

Ošetříme tedy nejprve událost OnClick tlačítka Button1:


procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.SaveToFile(`c:\soubor.txt`);
end;

V obsluze této události pouze uložíme aktuální obsah komponenty Memo do souboru c:\soubor.txt.

Následně ošetříme událost OnClick tlačítka Button2. Také tato obsluha bude velmi primitivní a nikoho jistě nepřekvapí – v obsluze pouza zavoláme metodu formuláře Close:


procedure TForm1.Button2Click(Sender: TObject);
begin
  Self.Close;
end;

Nakonec si pohrajeme s událostí OnClose hlavního formuláře. Tato událost nás bude zajímat z hlediska zkráceného vyhodnocování nejvíc. Zdrojový kód je uveden v následujícím výpisu:


procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if  (MessageDlg(`Chcete opravdu skoncit? `, mtConfirmation, [mbYes, mbNo], 0) = mrYes)
  and (MessageDlg(`Je aktualni soubor ulozen? `, mtConfirmation, [mbYes, mbNo], 0) = mrYes)
  then
    Action := caFree
  else
    Action := caNone;
end;

Nejprve budeme předpokládat, že jsme nijak neměnili standardní nastavení projektu a že je tedy vypnuto úplné vyhodnocování (jinak řečeno – je zapnuto zkrácené vyhodnocování).

Co tento kód udělá? Nejprve zobrazí dotaz, zda uživatel chce opravdu skončit, viz následující obrázek:

Klepněte pro větší obrázek

Potom bude v dané situaci (tedy při zapnutém zkráceném vyhodnocování – nebo přesně řečeno při nezapnutém kompletním vyhodnocování) záležet na tom, co udělá uživatel. Podívejte se prosím na následující podmínku použitou v obsluze FormClose:


  if  (MessageDlg(`Opravdu skoncit? `, mtConfirmation, [mbYes, mbNo], 0) = mrYes)
  and (MessageDlg(`Je soubor ulozen? `, mtConfirmation, [mbYes, mbNo], 0) = mrYes)
then
  Action := caFree
else
  Action := caNone;

Stále platí, že máme zapnuté zkrácené vyhodcování. Pokud uživatel zvolí při prvním dotazu (první část podmínky) Yes, aplikace musí pokračovat ve vyhodnocování, protože není jasné, jak vše dopadne (jaká bude výsledná hodnota). Z toho důvodu se zobrazí druhý dialogový box, který je vyvolán druhou částí podmínky, viz následující obrázek:

Klepněte pro větší obrázek

Pokud nyní uživatel zvolí opět Yes, je i druhá část podmínky pravdivá, proto je pravdivý celý výraz (celý „vnitřek“ příkazu if), a provede se příkaz za klíčovým slovem then (tedy se nastaví proměnná Action na hodnotu caFree, což způsobí uvolnění formuláře a ukončení aplikace). Odpoví-li uživatel na druhý dotaz No, je celý výraz nepravdivý a provede se část else (tedy nastavení Action na caNone, což naopak zakáže uvolnění formuláře).

Podívejme se však na to, co se stane, když uživatel na první dotaz odpoví No. V takovém případě se již nezobrazí druhý dialogový box (který se táže na uložení souboru), protože už po prvním dotazu je více než jasné, že podmínka nebude splněna a formulář se neuvolní.

Zkuste si spustit aplikaci a odpovědět na první dotaz No. Žádný druhý dotaz se nezobrazí. Je to způsobeno zkráceným vyhodnocováním výrazů.

Nyní zkusme, jak se chování aplikace změní, pokud zapneme kompletní vyhodnocování. Zapnout jej můžeme buď z uvedené nabídky Project – Options, karta Compiler, přepínač Complete boolean eval, a nebo pomocí direktivy {$B+}. Z důvodu názornosti použijeme v následujícím zdrojovém kódu direktivu {$B+}:


procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  {$B+}
  if  (MessageDlg(`Chcete opravdu skoncit? `, mtConfirmation, [mbYes, mbNo], 0) = mrYes)
  and (MessageDlg(`Je aktualni soubor ulozen? `, mtConfirmation, [mbYes, mbNo], 0) = mrYes)
  then
    Action := caFree
  else
    Action := caNone;
end;

Všimněte si především první řádky, v níž nastavujeme direktivu {$B+}, čímž zapneme kompletní vyhodnocování výrazů.

Když nyní aplikaci spustíme a pokusíme se ji ukončit, zobrazí se první dotaz. Bez ohledu na to, jakou odpověď uživatel zvolí, se vždy zobrazí i druhý dotaz. K ukončení aplikace však dojde pouze v případě, že uživatel zvolil v obou případech odpověď Yes. Může to být pro uživatele trochu matoucí: uživatel na první dotaz zvolí, že skončit nechce, přesto se mu zobrazí druhý dotaz a táže se na uložení souboru.

Zde je tedy dobře vidět rozdíl mezi zkráceným a kompletním vyhodnocováním. Pokud zapneme kompletní vyhodnocování, je vždy nutné vyhodnotit obě části podmínky, a proto se vždy zobrazí oba dotazy (oba MessageBoxy), bez ohledu na odpovědi uživatele. Pokud zapneme zkrácené vyhodnocování, bude se druhý dialog zobrazovat jen v tom případě, kdy uživatel odpověděl kladně na první dotaz.

Ukažme si ještě jeden krátký příklad využití zkráceného vyhodnocování. Díky němu totiž můžeme použít třeba i příkaz uvedený v následující obsluze události OnClick:


procedure TForm1.Button3Click(Sender: TObject);
var a, b: integer;

begin
  {$B-}
  a := 0;
  b := 0; // nastavíme pomocné proměnné na nulu

  if ((b <> 0) and (a div b > 5))
  then ShowMessage(`Ahoj`);
end;

Díky zkrácenému vyhodnocování nedojde při příkazu a div b nikdy k chybě dělení nulou, protože pokud by náhodou bylo b rovno nule, „odchytí“ tuto situaci první část podmínky a druhá část (tj. dělení) se nikdy neprovede.

Zkusme si ještě na samotný závěr zapnout kompletní vyhodnocování a znovu spustit tento příklad:


procedure TForm1.Button3Click(Sender: TObject);
var a, b: integer;

begin
  {$B+}
  a := 0;
b := 0; // nastavíme pomocné proměnné na nulu

  if ((b <> 0) and (a div b > 5))
  then ShowMessage(`Ahoj`);
end;

V tomto případě dojde k chybě dělení nulou, protože podmínka je vždy vyhodnocena celá, bez ohledu na to, že už její první část je jasně nepravdivá.

Na závěr

Dnešní článek vás prakticky provedl otázkami týkajícími se zkráceného vyhodnocování logických výrazů. Ukázali jsme si, jakým způsobem lze zkrácené vyhodnocování zahrnout do aplikační logiky a jakým způsobem se díky němu vyhnout chybovým stavům. Přestože uvedené příklady byly ryze ilustrativní, věřím, že dostatečně ukázaly, oč u zkráceného vyhodnocování vlastně běží.

Diskuze (10) Další článek: Elektronické brožury od Microsoftu zdarma

Témata článku: Software, Programování, Dialog box, První parametr, MTC, První případ, Projíždějící auto, Kompletní nabídka, Automatické testování, DEL, Pravdivá informace, Následující aplikace, Autobus, Fields, Vypnutí, Elsa, První článek, Formální ukončení, Action, Options, Pascal, Log, Druhý případ, První uvedení, Díl


Určitě si přečtěte

Proč byste měli rozmazávat SPZ aut na fotkách, které vystavujete na web

Proč byste měli rozmazávat SPZ aut na fotkách, které vystavujete na web

** Na fotkách aut nahraných na web je dobré rozmazat SPZ ** Značku dokáže z obrázku přečíst Google i Facebook ** SPZ může naplnit podstatu osobního údaje

Karel Kilián | 67

Nejlevnější router s Wi-Fi 6 v testu: vážně ještě chcete kupovat routery jen pro 802.11ac?

Nejlevnější router s Wi-Fi 6 v testu: vážně ještě chcete kupovat routery jen pro 802.11ac?

** Otestovali jsme TP-Link Archer AX10, nejdostupnější router s Wi-Fi 6 ** Šetřilo se, ale zatím ty ústupky tolik nebolí ** Pro domácí síťování pohodlná volba, do firmy ale chcete něco lepšího

Tomáš Holčík | 44

10 skrytých nastavení prohlížeče Google Chrome, která se můžou hodit

10 skrytých nastavení prohlížeče Google Chrome, která se můžou hodit

** Prohlížeč Google Chrome ukrývá mnoho zajímavých možností ** Našli jsme deset nejzajímavějších skrytých nastavení ** Můžete si například výrazně vylepšit práci s kartami

Karel Kilián | 18

Proč teď nedává smysl kupovat notebook a kdy přijde ten správný čas

Proč teď nedává smysl kupovat notebook a kdy přijde ten správný čas

** Během pár týdnů přijdou na trh výkonnější notebooky ** Čím dražší notebook vybíráte, tím víc se vám změní nabídka ** Také lehké notebooky budou téměř herní

Tomáš Holčík | 52

Dost bylo moderních Windows 10. Pojďme je přebarvit zpět na Windows 95

Dost bylo moderních Windows 10. Pojďme je přebarvit zpět na Windows 95

** Stařičké Windows 2000 nedávno oslavily 20 let ** Někteří z vás si postěžovali, že to byly poslední hezké Windows ** Fajn, ukážeme vám, jak přebarvit Desítky o 25 let zpět

Jakub Čížek | 44



Aktuální číslo časopisu Computer

Velký test autokamer

Test ATX skříní

Jak surfovat pohodlně

Sportovní aplikace

Jak funguje procesor