Pojďte s námi programovat !

1. 11. 2008

Sdílet

V listopadové lekci kurzu programování v jazyce C++ se blíže seznámíme s inkrementačně- dekrementačními oper


V listopadové lekci kurzu programování v jazyce C++ se blíže seznámíme s inkrementačně-
dekrementačními operátory, složenými přiřazovacími operátory, relačními operátory, logickými operátory, bitovými operátory a operátory bitového posunu.


Operátory inkrementace a dekrementace

Operátory ++ a –– jsou unární operátory, které uskutečňují inkrementaci a dekrementaci svých operandů. Připomeňme, že inkrementace znamená přičtení jedničky k operandu, zatímco dekrementace představuje odečtení jedničky od operandu. Jak jsme zmínili v předcházející lekci tohoto programátorského kurzu, inkrementační a dekrementační operátory smí být zapisovány buď před svými operandy (prefixová notace), nebo až za nimi (postfixová notace). V následujícím fragmentu zdrojového kódu jazyka C++ vidíme samostatné příkazy, jež znázorňují prefixové a postfixové užití operátorů ++ a ––:

Pro prostudování kódu vidíme, že pokud je inkrementace nebo dekrementace hodnoty proměnné syntakticky vyjádřena jedním, tzv. inkrementačním, resp. dekrementačním příkazem, pak je jedno, zda použijeme unární operátory ++ a –– v prefixové nebo v postfixové notaci. Rádi bychom ovšem zdůraznili, že uvedené tvrzení platí jenom ve vzpomenutých případech, kdy je inkrementace nebo dekrementace jedinou operací, jejíž realizaci předepisuje zapsaný příkaz. Finální efekt operátorů ++ a –– v předcházející ukázce smíme substituovat užitím přiřazovacích příkazů, samozřejmě bez narušení funkcionality programu.

Uvažujme kupříkladu následující dva výpisy zdrojového kódu jazyka C++:

Předmětem našeho zájmu je třetí řádek v těle hlavní funkce main se zněním y = ++x;. Víme, že jde o přiřazovací příkaz. Pohlédneme-li na pravou stranu, vidíme inkrementační výraz ++x. Jak ovšem přiřazení probíhá? Jestliže je v souvislosti s operandem (proměnnou x v našem případě) aplikován unární inkrementační operátor v prefixové verzi, pak se nejprve provede inkrementační operace, jejímž důsledkem bude přičtení 1 k aktuální hodnotě operandu. Poté se (již inkrementovaná) hodnota operandu v procesu operace přiřazení uloží do cílové proměnné (y). Po zpracování analyzovaného přiřazovacího příkazu tedy bude v proměnné x uložena hodnota 1 (to je výsledek inkrementace proměnné x) a v proměnné y bude uložena rovněž hodnota 1 (to je výsledek přiřazení inkrementované hodnoty proměnné x do proměnné y). Obě proměnné budou mít stejnou hodnotu, o čemž se můžeme přesvědčit, když se podíváme na výstup programu.

V 2. výpisu jsme provedli jedinou, ovšem přesto velice významnou změnu: namísto prefixové notace inkrementačního operátoru jsme upřednostnili postfixovou verzi. Opět se soustředíme na průběh přiřazovací operace. Velice důležité sdělení: pokud je na operand aplikován inkrementační operátor v postfixové notaci, nejprve bude získána aktuální hodnota operandu, která bude vzápětí přiřazena do cílové proměnné. Poté se uskuteční inkrementace předepsaná operátorem ++, která modifikuje hodnotu operandu. Řečeno jednodušeji, celý proces je zahájen přiřazením aktuální hodnoty proměnné x do proměnné y. Vzhledem k tomu, že explicitní inicializační hodnotou proměnné x je nula, bude nula přiřazena rovněž do proměnné y. V dalším stádiu se o 1 zvýší hodnota proměnné x, což znamená, že po zpracování přiřazovacího příkazu budou mít proměnné x a y následující obsahy: proměnná x bude obsahovat jedničku a proměnná y bude naplněna nulou.

Složené přiřazovací operátory

Kromě standardního přiřazovacího operátoru (=) mohou programátoři v jazyce C++ pracovat také s kolekcí složených přiřazovacích operátorů. Složené přiřazovací operátory kombinují schopnosti standardního přiřazovacího operátoru a jiných operátorů (nejčastěji aritmetických). Přehled nejběžněji užívaných složených přiřazovacích operátorů přináší tabulka.
Z akademických zkušeností plyne, že složené přiřazovací operátory jsou ze strany studentů dobře chápány, když si uvědomíme, že ve skutečnosti předepisují provedení určité operace s následným přiřazením.

Relační operátory

Relační operátory programovacího jazyka C++ zkoumají existenci relací mezi operandy. Jejich přehled uvádíme v tabulce (všimněme si prosím, že v sloupci Použití operátoru jsou zapsány výrazy a nikoliv příkazy).
Na základě povahy relačního výrazu vracejí relační operátory buď logickou hodnotu true (pokud relace existuje), nebo logickou hodnotu false (jestliže relace neexistuje). Datovým typem návratových hodnot relačních operátorů (a tedy datovým typem logických výrazů) je v jazyce C++ primitivní typ bool. V praktickém programování se se samostatně působícími relačními operátory (a potažmo i relačními výrazy) často nesetkáme. Relační operátory nacházejí své uplatnění zejména v souvislosti s rozhodovacími příkazy a programovými cykly.

Logické operátory

V jazykové specifikaci C++ nacházíme tři hlavní logické operátory, jež uskutečňují základní operace matematické logiky, ke kterým patří logická konjunkce (logický součin), logická disjunkce (logický součet) a logická negace. Soupravu logických operátorů prezentuje tabulka.
A nyní již slíbená demonstrace praktického užití logických operátorů na dvou testovacích operandech (proměnných p a q typu bool) – více informací nabízí tabulka.
Ještě než logické operátory přikročí k výkonu předepsaných logických operací, provedou implicitní konverze, díky kterým se hodnoty integrálních datových typů transformují na logické hodnoty typu bool. Přitom platí tato pravidla:

Pokud je hodnota integrálního datového typu nulová, bude konvertována na logickou nepravdu (false).

Pokud je hodnota integrálního datového typu nenulová (kladná nebo záporná), bude konvertována na logickou pravdu (true).

Logické operátory && a || provádějí tzv. zkrácené vyhodnocování logických výrazů. To znamená, že pokud dokáže logický operátor determinovat finální hodnotu logického výrazu již podle logické hodnoty prvního operandu, pak druhý operand nebude vůbec testován a vyhodnocován.
Pro logickou konjunkci to znamená následující:

Bude-li hodnotou 1. operandu logická nepravda (false), tak 2. operand nebude vůbec vyhodnocován. To je smysluplné, neboť z charakteru logické konjunkce plyne, že hodnotou této operace bude logická pravda právě tehdy, jsou-li oba operandy současně pravdivé (obsahují logickou pravdu). V situaci, kdy je hodnotou 1. operandu logická nepravda, je zcela zbytečné ztrácet čas s vyhodnocováním 2. operandu, neboť ať už je jeho logická hodnota jakákoliv, na finální hodnotu logického výrazu nebude mít žádný vliv (finální hodnotou bude logická nepravda).

Bude-li hodnotou 1. operandu logická pravda (true), pokračuje operátor logické konjunkce ve vyhodnocování logického výrazu testováním také 2. operandu. Je to proto, že ačkoliv operátor vyhodnotil 1. operand, nedovede určit finální hodnotu logického výrazu (existuje 50% šance, že hodnotou 2. operandu bude také logická pravda).

Prostudujme následující výpis zdrojového kódu:

Naším cílem je zjistit, jak pracuje zkrácené vyhodnocování logických výrazů. Proto jsme definovali logický výraz (x + y < 0) && (x < y), který jsme zasadili do rozhodovacího příkazu if-else. Přestože rozhodovací příkazy probereme už brzy velmi podrobně, s jedním z nich, příkazem if-else, se seznámíme již nyní. Příkaz if-else provádí tzv. dvoucestné rozhodování v závislosti na podmínce. Touto podmínkou je velice často hodnota logického výrazu, což je také náš případ. Pokud je podmínka splněna (hodnotou logického výrazu je logická pravda), vykonají se příkazy větve if. Naopak, není-li podmínka splněna (hodnotou logického výrazu je logická nepravda), zpracování budou podrobeny příkazy ve větvi else. Jelikož diskutujeme o zkráceném vyhodnocování logických výrazů, zajímá nás především postup, jak operátor logické konjunkce zapsaný podmínkový výraz vyhodnotí. Začínáme vyhodnocením 1. operandu: naším operandem je výraz x + y < 0 (všimneme si, že operandy mohou být nejenom proměnné, nýbrž také korektně navržené výrazy). Konkrétněji řečeno, 1. operand je relačním výrazem, protože v něm jasně identifikujeme použití relačního operátoru <. Relační operátor < je binární a pracuje tedy se dvěma operandy: 1. operandem je opět výraz, tentokrát jde o výraz aritmetický (x + y), poněvadž používáme binární aritmetický operátor +. 2. operandem relačního výrazu je nula, jako diskrétní celočíselná konstanta.
Jelikož víme, že inicializační hodnotou proměnné x je 0 a inicializační hodnotou proměnné y je 1, snadno určíme hodnotu aritmetického výrazu (1). Hodnota aritmetického výrazu se nyní stává operandem relačního výrazu. Dále testujeme, zda relační výraz 1 < 0 splňuje předepsanou relaci. Je evidentní, že ne, z čehož plyne, že hodnotou relačního výrazu bude logická nepravda (false). Ještě nekončíme, protože logická nepravda jako hodnota relačního výrazu se stává operandem původního logického výrazu. V této chvíli operátor logické konjunkce ví, že 1. operand logického výrazu je logická nepravda. To mu stačí k tomu, aby zamítnul další vyhodnocovací operace a okamžitě poskytl finální hodnotu logického výrazu, jíž je logická nepravda. Jak jsme uvedli, pokud je hodnotou podmínkového výrazu logická nepravda, testovaná podmínka nebyla splněna. Za těchto okolností se provede příkaz umístěný ve větvi else. Právě proto spatříme na výstupu hodnotu 2.
Analogická pravidla platí i pro zkrácené vyhodnocování logických výrazu pomocí operátoru logické disjunkce:

Bude-li hodnotou 1. operandu logická pravda (true), tak 2. operand nebude vůbec vyhodnocován. Tento přístup je rozumný, neboť jak víme, při logické disjunkci stačí, aby byl alespoň jeden z testovaných operandů pravdivý (to znamená 1. operand, nebo 2. operand, anebo oba operandy současně). Jestliže to bude hned 1. operand, bylo by vyhodnocování 2. operandu jen plýtváním výkonu, poněvadž finální hodnota logického výrazu by tak jako tak zůstala nezměněná.

Bude-li hodnotou 1. operandu logická nepravda (false), tak 2. operand bude vyhodnocen, přičemž na základě jeho hodnoty bude operátorem logické disjunkce stanovena finální hodnota logického výrazu.

Bitové operátory

Zatímco logické operátory vykonávají logické operace na logických hodnotách svých operandů, bitové operátory se zabývají zpracováním bitových operací na jednotlivých bitech binárních hodnot operandů. V jazyce C++ existují čtyři bitové operátory, které znázorňuje tabulka.
Pomocí bitových operátorů smíme realizovat bitovou konjunkci (bitový součin), bitovou disjunkci (inkluzivní bitový součet), bitovou nonekvivalenci (exkluzivní bitový součet) a bitový doplněk (bitovou negaci). Operátory bitové konjunkce, disjunkce a nonekvivalence jsou binárními operátory. Operátor bitového doplňku je unárním operátorem.
Jako operandy bitových operátorů smí vystupovat hodnoty integrálních datových typů char, short, int a long (se znaménkem i bez).
Práci bitových operátorů si předvedeme na praktickém experimentu s operátorem bitové konjunkce. Předpokládejme, že máme takovýto fragment zdrojového kódu jazyka C++:

Když program přeložíme a spustíme, na výstupu obdržíme výsledek 2. Pojďme se podívat, jak program k tomuto výsledku dospěl. Práci bitového operátoru & budeme sledovat v následujících krocích:

1.
Nejprve jsou oba operandy (hodnoty proměnných a a b) převedeny do své binární podoby. Tedy (3)10 = (11)2 a (2)10 = (10)2.

2.
Operátor & začne porovnávat bity obou operandů, které stojí na stejných pozicích.

3.
Jestliže mají oba testované bity hodnotu 1 (jednotkový bit), operátor & vrací hodnotu 1. Pokud mají testované bity rozdílné hodnoty, operátor & vrací hodnotu 0 (nulový bit).

4.
Finální hodnota bitového výrazu bude mít podobu bitové posloupnosti 10, což odpovídá hodnotě 2 v desítkové soustavě.
Praktické implikace plynoucí z užití bitových operátorů v spojení se dvěma testovacími operandy můžeme pozorovat v tabulce.

Operátory bitového posunu

Poslední kategorie operátorů, kterou si představíme, je tvořena dvěma operátory pro realizaci bitového posunu. Jelikož operátory bitového posunu jsou bitovými operátory, je jasné, že předepsaný posun bitů budou provádět na operandech integrálních datových typů. Operátory bitového posunu jsou dva:

Operátor bitového posunu doleva <<.

Operátor bitového posunu doprava >>.

Oba operátory jsou binární, přičemž jejich generické aplikace jsou následující:

O << PB
O >> PB
kde:

O je operand, jehož bity budou posouvány.

PB je hodnota udávající počet bitů k posunutí.

Operátor << posouvá PB bitů směrem doleva a operátor >> posouvá PB bitů směrem doprava. Pracovní modely obou operátorů si vysvětlíme na praktických příkladech.

1. praktický příklad:

Provedeme-li základní analýzu programu, zjistíme, že operátor << je aplikován v bitovém výrazu, jehož smyslem má být posunutí všech bitů operandu (hodnoty proměnné m) o 2 pozice směrem doleva. Jak budeme postupovat? Výpočet odstartujeme převodem hodnoty 20 do binární soustavy, a zjistíme, že (20)10 = (10100)2. Všechny bity v binární hodnotě 10100 posuneme o 2 pozice doleva. Když tak uděláme, zjistíme, že posunutí hodnot nám vytvořilo na pravé straně prázdný prostor o velikosti 2 binárních hodnot. Tyto prázdné pozice zaplníme binárními nulami. Poté finální binární hodnotu přečteme zleva doprava, čímž dospějeme k této bitové posloupnosti: 1010000. Převodem finální bitové hodnoty do desítkové soustavy shledáme, že (1010000)2 = (80)10. Můžeme konstatovat, že proměnná n bude inicializována celočíselnou hodnotou 80.

2. praktický příklad:

Zde je naším závěrem posunout bity operandu (hodnoty proměnné m) o 2 pozice směrem doprava. Počáteční etapa bitového posunu doprava je stejná jako u posunu bitů doleva. Získáme binární hodnotu a všechny její bity budeme posouvat o 2 pozice doprava. Bity, které napravo přesahují, odstraníme. Do volných pozic, jež se nám vytvořily nalevo, umístíme nulové bity, pokud pracujeme s neznaménkovou binární hodnotou, anebo jednotkové bity, jestliže pracujeme se znaménkovou binární hodnotou (jde o tzv. propagaci znaménkového bitu). V našem příkladě získáme finální bitovou hodnotu 00101, která je ekvivalentem čísla 5 v desítkové soustavě. 8 0631/CZ o

Ve všeobecnosti platí následující vztah:

H>> n = H / 2n
kde:

H je zdrojová hodnota, na níž bude aplikována operace bitového posunu.

n je počet bitů k posunutí.
Posunout bity binární hodnoty o 2 pozice doprava má stejné důsledky jako vydělit tuto hodnotu číslem 2n. V našem příkladě: 20 >> 2 = 20 / 22, což je 5. Ve všeobecnosti platí následující vztah:

H<< n = H * 2n
kde:

H je zdrojová hodnota, na níž bude aplikována operace bitového posunu.

n je počet bitů k posunutí.
Méně formálně: když posuneme bity hodnoty H o n pozic směrem doleva, má tato operace v aritmetice stejný význam jako vynásobení hodnoty H n-tou mocninou čísla 2. V našem konkrétním případě platí 20 << 2 = 20 * 22, z čehož zřetelně vyplývá finální hodnota 80 (20 * 4). Pokud se inkrementace, resp. dekrementace uskuteční jako součást složeného výrazu nebo příkazu, pak je použití prefixové nebo postfixové notace významné. Jakýkoliv přiřazovací příkaz, jenž pracuje se složeným přiřazovacím operátorem, můžeme nahradit přiřazovacím příkazem se standardním přiřazením a příslušným výrazem (v našem případě aritmetickým). Více informací uvádí tabulka.
Než se seznámíme s charakterem jednotlivých logických operací, dovolíme si poukázat na tyto skutečnosti:

Operátory logické konjunkce (&&) a logické disjunkce (||) jsou binární operátory.

Operátor logické negace (!) je unární operátor.

Návratovou hodnotou logických operátorů, resp. hodnotou logických výrazů, je buď logická pravda (true), nebo logická nepravda (false).

Datovým typem návratové hodnoty logických operátorů, resp. datovým typem logických výrazů, je primitivní typ bool.