Distanční opora předmětu: Programování v jazyce C Tématický blok č. 3: Datové typy a operátory Autor: RNDr. Jan Lánský, Ph.D. Obsah kapitoly 1 Datové typy 1.1 Celočíselné typy 1.2 Logické typy 1.3 Znakový typ 1.4 Výčtový typ 1.5 Reálné typy 1.5 Automatické číselné konverze 2 Operátory 2.1 Základní matematické operátory 2.2 Přetypování 2.3 Bitové a logické operátory 2.4 Zkrácené vyhodnocování 2.5 Relační operátory 2.6 Přiřazení 2.7 Podmíněný výraz 2.8 Sekvence 2.9 Pravidla vyhodnocování Studijní cíle Cíle nutné k zahájení studia dalšího tématického bloku Znát klíčové pojmy tohoto tématického bloku (alespoň pasivní znalost je podmínkou pro studium dalších bloků). Ve svých programech umět vhodně používat datové typy a operátory. Pochopení principu zkráceného vyhodnocování logických výrazů. Znalost priority jednotlivých skupin operátorů. Znalost ternárního operátoru ?: podmíněného výrazu. Znalost rozdílu mezi prefixním a postfixním operátorem ++. Podle slovního zadání umět napsat a odladit jednoduchý program pracující se vstupem velikosti několik celých čísel (tedy bez použití řetězců a polí). Další cíle Znalost výčtových typů. Aktivní znalost kombinovaných (zkrácených přiřazení). Znalost bitových operací. Znalost operátoru sekvence.
Čas potřebný ke studiu 2 - 4 hodiny na prostudování výukových textů + zodpovězení otázek k rekapitulaci 2 - 6 hodiny na vypracování modelových úloh na PC 1 - 3 hodiny na praktické zopakování učiva na PC ( v jiný den) 30 min - 1 hodina na (znovu)zodpovězení otázek k rekapitulaci (v jiný den) Časy jsou hodně individuální a jsou závislé na míře znalostí z předmětu Úvod do programování a Programování a případných programátorských zkušenostech z jiných jazyků. Úvod V tomto bloku probereme následující témata. Naučíme se, jaké je vhodné používat datové typy pro celá a reálná čísla, znaky a logické hodnoty. Vysvětlíme si možné problémy, které datové typy v jazyce C přinášejí. Vysvětlíme si výčtový typ. Probereme většinu operátorů, které v jazyce C jsou. Vysvětlíme si principy, podle kterých se vyhodnocuje výraz obsahující operátory. Naučíme se používat ternární operátor ?: podmíněného výrazu. Vysvětlíme si rozdíl mezi prefixní a postfixní verzí operátoru ++.
Výkladová část Vysvětlivky Červený text – Porušením nebo opomenutím takto označených pravidel vznikají těžko odladitelné chyby (zejména pro začínající programátory). Modrý text – Doporučení jak programovat v praxi. Často prevence závažných chyb. 1 Datové typy Všechny proměnné, výrazy a příkazy mají svojí hodnotu, která je daného datového typu. V případě proměnné určujeme její datový typ při deklaraci (viz tématický blok 2, kapitola 1.1), v případě výrazu a příkazu se datový typ určuje automaticky kompilátorem. Pro jednoduchost se budeme dále zabývat pouze datovým typem u proměnné, u příkazů a výrazů platí analogická tvrzení. Datový typ udává jakých hodnot může proměnná nabývat (například unsigned char od 0 do 255) a jaké operace můžeme s ním provádět (například +, -, *, /). Každý datový typ má svoji velikost, která udává minimální velikost paměti nutnou pro uložení proměnná s tímto datovým typem (například .char má velikost 1 byte). Z jiného úhlu pohledu můžeme říci, že datový typ určuje způsob, jakým se interpretuje a zobrazuje paměť přidělená dané proměnné.
1.1 Celočíselné typy V jazyku C máme čtyři základní celočíselné typy char, short, int a long. V základní variantě jsou tyto datové typy znaménkové, reprezentují hodnoty z intervalu <-2n-1, 2n-1), kde n je velikost typu v bitech. Při deklaraci můžeme použít klíčové slovo unsigned před názvem typu (příklad unsigned int) nebo za názvem typu (příklad int unsigned). Celočíselný typ se pak stane bezznaménkovým a reprezentuje hodnoty z intervalu <0, 2n). Existuje také klíčové slovo signed, které explicitně říká, že celočíselný typ je znaménkový, což by byl ale i bez použití tohoto klíčového slova. Existuje jen málo reálných situací, kde použití bezznaménkového typu je vyloženě nezbytné. Dobrým příkladem je, pokud s celočíselným typem pracujeme jako s polem bitů. Pokud není dobrý důvod, je lepší bezznaménkové typy nepoužívat, ušetříme si řadu nemilých překvapení, které mohou nastat při jejich vzájemném kontaktu se znaménkovými typy. Norma jazyka C neříká, jakou mají mít celočíselné typy velikost, což může způsobit problémy při přenosu zdrojového kódu mezi různými překladači, zejména při přenosu programu odladěného pomocí nového překladače (64/32 bit) na překladač starší (32/16 bit). Existují pouze dvě pravidla: typ char je vždy velikosti 1 byte a platí nerovnost: char ≤ short ≤ int ≤ long. Na slajdu č. 37 vidíme tabulku s velikostí (v bitech) celočíselných typů v různých typech překladačů (8bit – 64 bit). Z tabulky se tedy dovíme, že typ int na 32bitovém překladači má velikost 32 bitů, tedy 4 byty, nabývá hodnot cca od -2 miliard do +2 miliard. Všimněme si, že například pro 64bitové překladače jsou dvě varianty velikosti typu long (4 byty a 8 bytu). V současné době jsou stále ještě nejrozšířenější 32bitové překladače, ale časem budou 64bitové. Pokud si chceme být skutečně jistí, jak velký je datový typ (nejen celočíselný) v našem konkrétním překladači, může použít operátor sizeof, který nám udá jeho velikost v bytech. Norma jazyka C nepočítá se 64bitovým celočíselným typem, ale obvyklé v každém překladači takový typ existuje. Protože jeho název není dán normou, překladač od překladače se liší. Ve Visual Studiu se 64bitový celočíselný typ nazývá long long. V nápovědě narazíte na celou řadu typů název_t (například size_t, wchat_t), které obvykle slouží jako parametry nebo návratové hodnoty standardních knihovních funkci. Obvykle se jedná pouze o jiný název pro typ int nebo jiný celočíselný typ. Jaký celočíselný typ by jsme měli používat pro běžné pomocné proměnné, řídící proměnné cyklu atd.? Předpokládejme, že pracujeme s 32bitovým překladačem, tedy veškeré operace se provádí s čísli velikosti 32 bitů. Podívejme se na následující příklad. Pokud chci sčítat dvě proměnné typu char a výsledek opět uložit do proměnné typu char, provedou se následující operace. První 8-bitová proměnná se načte z paměti a převede se na 32bitů (doplní nulami), druhá proměnná se načte a převede na 32bitů. Nyní se proměnné sečtou, výsledek se ořízne na 8 bitů a uloží. Pokud by obě proměnné i výsledek byly typu int, ušetříme si převod z 8 bitů na 32 a zpět, zrychlíme tím běh našeho programu. Jako negativum spotřebujeme více paměti, ale té je v dnešní době dostatek. Pro běžné celočíselné proměnné můžeme používat typ int, aniž by jsme zkoumali, jak velkých hodnot budou nabývat. Toto doporučení neplatí pro velká pole (>106 prvků) nebo jednotlivé prvky struktury (uživatelsky definované složené typy), od nichž budeme tato pole tvořit, zde by jsme s pamětí už hodně plýtvali.
1.2 Logické typy V jazyku C neexistuje speciální logický typ, který by měl dvě hodnoty pravda a nepravda. Jako náhradu za logický typ v jazyku C lze použít libovolný celočíselný typ. V jazyku C++ také dlouho neexistoval speciální logický typ. V roce 1999 byl do C++ zaveden typ bool s hodnotami true (=1) a false (=0). Mezi programátory však velkého rozšíření nedosáhl. V podmínce (reprezentované výrazem) u podmíněném příkazu nebo u cyklu potřebujeme rozhodnout, zda je splněna nebo není. Každá hodnota výrazu různá od nuly se bere jako splnění (obdoba true), nula se bere jako nesplnění. (obdoba false). Logické a porovnávací operátory vracejí jako svůj výsledek celé číslo 0 (nesplněno) nebo 1 (splněno). Důsledkem toho je možné při zpracování podmínky obsahující porovnání výrazu na rovnost nebo nerovnost s nulou použít zkrácený zápisem. Výraz x!=0 lze nahradit výrazem x, výraz x==0 lze nahradit výrazem !x. 1.3 Znakový typ V jazyku C se pro reprezentaci znaků používá celočíselný typ char. Pro každý znak z ANSI znakové sady je definována znaková konstanta, kterou syntakticky tvoří znak uzavřený v apostrofech. Hodnota této konstanty odpovídá pořadí znaku v ANSI znakové sadě. (například pro znak a je konstanta 'a', s hodnotou 97, pro znak + je konstanta '+' s hodnotou 43). Pro přehlednost zdrojového kódu je nutností používání znakových konstant místo přímého psaní číselné hodnoty. Problém dělají české znaky (znaky národních abeced obecně). Znaky se na standardní výstup vypisují v ANSI znakové sadě, zatímco příkazová řádka nám je zobrazuje v ASCII (například znaku ě ANSI odpovídá znak ý v ASCII). Pro naše výukové potřeby postačí psát na výstup texty bez diakritiky, které se zobrazí vždy správně. Datový typ char je mezi celočíselnými typy výjimečný tím, že norma jazyka C neříká, zda je znaménkový nebo bezznaménkový. Většina kompilátorů (včetně Visual Studia) ho považuje v základním nastavení za znaménkový. Pokud chceme mít jistotu, že znaménkovým bude i na jiných kompilátorech, musíme to buď explicitně vyjádřit ve zdrojovém kódu (signed char), nebo v nastavené kompilátoru vybrat tuto volbu. Kvůli této vlastnosti typu char nevíme jak dopadne vyhodnocení výrazu 'a' < 'ž'. Různé výsledky získáme pouhou změnou nastavení překladače. Konstanta 'a' má vždy hodnotu 97, ale konstanta 'ž' může mít hodnotu 158 jako unsigned char nebo -98 jako signed char.
1.4 Výčtový typ Výčtový typ slouží k zpřehlednění zdrojového kódu, pokud v něm používáme číselné konstanty, které mají nějaký logický smysl. Z několika konstant, které používáme v podobných situacích (například hodnoty jedné proměnné) můžeme vytvořit výčtový typ, ve kterém tyto konstanty pojmenujeme a můžeme jím případně přiřadit i číselné hodnoty, které reprezentují.
Proč výčtové typy používat? Představme si program, ve kterém evidujeme zaměstnance a u každého z nich chceme znát i jeho pohlaví. To jde vyřešit pomocí proměnné typu int a hodnotami například 0 pro muže a 1 pro ženy. Tuto naší konvenci si musíme během výroby programu pamatovat a navíc ji musíme i pomocí komentářů do zdrojového kódu poznamenat. Pokud náš program bude číst jiný programátor, bude muset tento komentář vyhledat. Představme si, že podobných položek jako je pohlaví bude v našem programu několik tisíc. Deklaraci několika výčtových typů vidíme na slajdu č. 39. Výčtový typ se definuje jako posloupnost klíčového slova enum, jména typu (například pohlaví) a složených závorek obsahující seznam názvů prvků výčtového typu oddělených čárkou. Jednotlivým prvkům výčtového typu je vždy přiřazena celočíselná hodnota. První prvek má implicitně hodnotu 0, každý další má hodnotu o jedna větší než jeho předchůdce. V případě našeho typu pohlaví je prvnímu prvku p_muz přiřazena 0, dalšímu p_zena přiřazena 1. Hodnotu lze libovolnému prvku přiřadit i explicitně pomocí operátoru = a uvedení hodnoty, jak vidíme ve spodním příkladě s výčtovým typem porty. V jazyku C se z hlediska kompilátoru výčtový typ bere stejně jako typ int. Konstanta ve výčtovém typu je pouze jiný název pro číslo, jehož hodnotu reprezentuje. Pod definicí výčtového typu pohlavi na slajdu č. 39 vidíme deklaraci proměnní p typu pohlavi. Další řádek ukazuje možnost použít hodnotu výčtového typu na místě, kde se očekává hodnota typu int. Další řádek je červenou barvou a ukazuje možnost použít hodnotu typu int na místě, kde se očekává hodnota typu pohlavi. V jazyku C je tento zápis povolen, ale v jazyku C++ je zakázán a protože používáme kompilátor C++, tento zápis bude označen za chybný. Při výpisu hodnoty výčtového typu na obrazovku se vypisuje hodnota typu int, která je danému prvku výčtového typu přiřazena. Název prvku výčtového typu je pouze identifikátor pro kompilátor a stejně třeba jako název proměnné nebo funkce nejde nijak automaticky vypsat. 1.5 Reálné typy Základním reálným datovým typem je double, který uchovává číslo s garantovanou přesností 15 platných cifer a exponentem z intervalu <-308, 308>. Pro výpočty s vyšší přesností slouží typ long double. Existuje i typ float, který má zhruba poloviční přesnost oproti typu double, nicméně tento typ není doporučeno používat, kvůli jeho nízké přesnosti. Při reprezentaci čísla pomocí reálného typu, dochází ke ztrátě přesnosti, protože my používáme desítkovou soustavu a čísla jsou v paměti uložena v soustavě dvojkové. Tato ztráta se kumuluje s každou matematickou operací, kterou s číslem provedeme. Na slajdu č. 40 napravo vidíme příklad. Po vydělení čísla 1 číslem 3 a opětovné vynásobení číslem 3 nemusíme už získat původní číslo 1. Pokud nejprve provedeme stokrát vydělení a poté stokrát provedeme vynásobení, původní číslo nezískáme téměř jistě. Reálné typy se nejčastěji využívají pro numerické nebo fyzikální výpočty, kde mírná ztráta přesnosti nevadí. Nejsou naopak vhodná pro uchovávání přesných hodnot, jako může být neceločíselná finanční suma, například 15,60 Kč. Zde je lepší použít typ int nebo long a finanční sumu uchovávat jako 1560 haléřů.
1.5 Automatické číselné konverze Výpočty výrazů a předávání parametrů funkcí probíhá vždy ve velikosti alespoň int (4 byty). Na 32bitovém kompilátoru se výrazy typu char a short automaticky konvertují na výrazy typu int. Praktické využití tohoto mechanizmu popisuje poslední odstavec kapitoly 1.1. U binárních operací můžeme mít operandy rozdílných číselných typů. Například můžeme sčítat celé číslo (int) s reálným číslem (double). Dochází k automatické konverzi menšího typu (int) na typu větší (double). Tohoto většího typu bude i výsledek binární operace. Na slajdu č. 41 dole najdete uspořádání typů podle jejich velikosti. 2 Operátory Přehled všech operátorů v jazyce C vidíme na slajdu č.42, modře označené řádky jsou operátory přidané až v C++. Představme si, že obě tabulky na slajdu jsou spojené za sebou. Po posledním řádku levé tabulky následuje první řádek tabulky pravé. Operátory jsou v tabulce řazeny sestupně podle své priority, podle které se ve zdrojovém kódu vyhodnocují. Operátor s vyšší prioritou se vyhodnotí dříve než operátor s nižší prioritou. Vodorovnými čárami v tabulce jsou odděleny prioritní skupiny operátorů. V rámci skupiny mají operátory stejnou prioritu. Například u výrazu 1+2-3*4 se nejprve provede operátor * a poté až operátory + a -, protože operátor * je v tabulce nejvýše má tedy nejvyšší prioritu. Operátory + a – jsou ve stejné skupině, mají tedy stejnou prioritu. V levém sloupci tabulky je typ operátoru. Postfixní operátory se píší za svůj operand (například a++), prefixní operátory se píší před svůj operand (například ++a). Písmena L a P označují infixní operátory, u kterých se operátor píše mezi operandy (například a+b). Písmena L a P určují, v jakém pořadí se vyhodnocují operátory ze stejné prioritní skupiny. Písmeno L udává směr vyhodnocování zleva doprava, písmeno P zprava doleva. Například u výrazu 1+2-3 jsou operátory + a – ve stejné prioritní skupině, která má označení L, tedy operátory se vykonávají zleva doprava. První se vykoná +, poté –. Všimněme si jednoduchého pravidla. Postfixní operátory mají vyšší prioritu než prefixní. Infuzní operátory se dělí na několik prioritních skupin. Ale všechny tyto skupiny mají nižší prioritu než tyto postfixní a prefixní operátory. Infuzní operátory se vyhodnocují zleva doprava s výjimkou operátorů, ve kterých se vyskytuje právě jeden symbol =, ty se vyhodnocují zprava doleva. Prioritu vykonávání operátorů lze upravit pomocí kulatých závorek, do kterých uzavřeme část výrazu, která se má spočítat přednostně. Závorky lze použít i pro zpřehlednění zdrojového kódu, když by se operátory vykonávaly ve stejném pořadí jako bez nich. Pokud si nejsme jistí prioritou operátorů, je lepší výraz uzávorkovat. 2.1 Základní matematické operátory Binární operátory +, -, * a unární operátory + a - se chovají velmi podobně jako v matematice. Pokud spolu například sčítáme celé a reální číslo, díky automatickým konverzím (kapitola 1.5) výpočet proběhne v pořádku.
Jediný problém může nastat při přetečení datového typu, kdy hodnota výsledku operace je vyšší než maximální hodnota, kterou datový typ dokáže uchovat. U bezznaménkového typu pak místo velkého kladného čísla získáme malé kladné číslo. U znaménkového typu pak místo velkého kladného čísla získáme záporné číslo jehož absolutní hodnota je velká. Operátor / se chová jako celočíselné dělení pokud jsou jeho oba operandy celá čísla. Pokud jeden z operandů je reálná číslo, tak se operátor chová jako klasické dělení. O typu dělení (celočíselné nebo normální) rozhoduje typ operandů, nikoliv typ proměnné, do které se bude ukládat výsledek. Chceme-li vydělit dvě celá čísla normálním dělením, musíme jedno z nich přetypovat (kapitola 2.2) na číslo reálné. Pokud jsou oba operandy celá čísla, lze použít i operátor % (modulo), který vrací zbytek po celočíselném dělení. Na slajdu č. 43 vidíme těchto výsledky operací v závislosti na typu operandů. 2.2 Přetypování V některých situacích potřebujeme změnit typ výrazu explicitně. Například převést jeden z celočíselných operandů na reálný, aby se místo celočíselného dělení použilo dělení normální. Přetypování se provádí uvedením nového typu uzavřeného v kulatých závorkách před přetypovávaný výraz. Například u výrazu 5/(double)3 jsme celé číslo 3 přetypovali na reálné číslo a docílili tak použití normálního dělení místo celočíselného. Přetypování je velmi nebezpečná konstrukce, která by se měla používat jen ve výjimečných situacích, které se postupem času uvedeme. Obecně lze přetypovat jakýkoliv typ na jakýkoliv jiný. 2.3 Bitové a logické operátory Mezi logické operace patří logická konjunkce AND (binární operátor &&), logická disjunkce OR (binární operátor ||) a logická negace (unární prefixní operátor !). Výsledkem logických operací je celočíselná hodnota 1 (= splněno) nebo 0 (= nesplněno). Disjunkce je splněna pokud alespoň jeden z operandů je nenulový, konjunkce je splněna pokud oba dva operandy jsou nenulové. Negace je splněna, pokud operand je nula, nesplněna pokud operand je nenulový. Mezi základní bitové operace patří bitová konjunkce AND (binární operátor &), bitová disjunkce OR (binární operátor |), bitová exkluzivní disjunkce XOR (binární operátor ^) a bitová negace (unární prefixní operátor ~). Exkluzivní konjunkce je splněna při kombinací bitů 0 a 1 nebo 1 a 0. Bitové operace se vyhodnocují na každém z bitů čísel samostatně, jejich smysluplné použití je v běžných programech velmi vzácné. Velmi často jsou však použity chybně místo logických operací. Ukážeme si, co se může stát po záměně logické konjunkce (operátor &&) za konjunkci bitovou (operátor &). U podmíněného příkazu je podmínka (4 && 2) vyhodnocena jako 1, tedy splněna a vykoná se tedy jeho tělo. Po vynechání jednoho symbolu & získáme konjunkci bitovou (4 & 2), která je vyhodnocena jako 0 (100 & 010 = 000) a podmínka je nesplněna, tedy tělo podmíněného příkazu se nevykoná. Na slajdu č. 44 v tabulce vidíme několik příkladů. V levé části tabulky jsou bitové operace, v pravé části odpovídající logické operace.
Mezi bitové operace patři ještě bitový posun doleva (binární operátor <<) a bitový posun doprava (binární operátor >>). Operátor << odstraní několik prvních bitů z čísla a přidá stejný počet nulových bitů na konec čísla. Operátor >> odstraní několik posledních bitů z čísla a přidá stejný počet nulových bitů na začátek čísla. Tyto operátory se v praxi používají zejména k vynásobení nebo vydělení čísla mocninou dvojky, ušetří několik řádek zdrojového kódu. Například 3<<5 = 3*32 = 96 nebo 33>>2 = 33/4 = 8. 2.4 Zkrácené vyhodnocování O výsledku logické konjunkce (binární operátor &&), nebo disjunkce binární operátor ||), může být rozhodnuto už po vyhodnocení prvního operandu. Pokud je u konjunkce první operand nulový, bez ohledu na hodnotu druhého operandu bude její výsledek 0 (= nesplněna). Druhý operand se tedy ani vyhodnocovat nebude. Pokud je u disjunkce první operand nenulový, bez ohledu na hodnotu druhého operandu bude její výsledek 1 (= splněna). Druhý operand se tedy ani vyhodnocovat nebude. Tomuto mechanizmu se říká zkrácené vyhodnocování. Na slajdu č. 45 vlevo vidíme příklad, který bychom bez zkráceného vyhodnocování museli rozepsat pomocí vnořené podmínky. V levém operandu konjunkce otestujeme, zda proměnná i má vhodnou hodnotu a poté ji použijeme jako index pole v pravém operandu konjunkce. Pravý operand konjunkce se bude vyhodnocovat pouze v případě, že bude splněn první operand, nebudeme tedy indexovat pole nesmyslnými hodnotami. Kvůli zkrácenému vyhodnocování nikdy nevíme, zda pravý operand logické konjunkce či disjunkce se bude vyhodnocovat. Neměl by tedy obsahovat, žádné vedlejší efekty (například modifikace hodnot proměnných nebo čtení ze souboru). Na slajdu č. 45 vpravo na červeném podkladě vidíme, že pravý operand modifikuje hodnotu proměnné pomocí operátoru ++. Zdrojový kód se tím stává nepřehledným. 2.5 Relační operátory Relační operátory jsou vždy binární a patří mezi ně < (menší), <= (menší nebo rovno), >= (větší nebo rovno), > (větší), == (rovno), != (nerovno). Výsledkem relačních operací je celočíselná hodnota 1 (= splněno) nebo 0 (= nesplněno). Relační operace je splněna pokud levý operand je k pravému operandu ve vztahu daném operátorem (například 5 > 3 je splněno, protože 5 je větší než 3). Velmi často dochází k záměně operátoru přiřazení (=) a operátoru porovnání (==). Kompilátor tuto záměnu nenahlásí jako chybu, protože i přiřazení vrací svojí hodnotu. Visual Studio naštěstí ve většině případů oznámí alespoň varování Na slajdu č. 45 vpravo dole je příklad. Reálná čísla není vhodné porovnávat na rovnost či nerovnost kvůli jejich nepřesné reprezentaci (kapitola 1.5 druhý odstavec). Vhodnější je porovnávat, zda rozdíl těchto čísel je menší než nějaká malá hodnota, například 10-12.
2.6 Přiřazení Přiřazovací operátory jsou takové operátory, které obsahují právě jeden symbol =. Operátor přiřazení (=) do svého levého operandu okopíruje hodnotu pravého operandu. Levý operand nesmí být konstantní (třeba hodnota 5), obvykle je jím proměnná. Existuje i velké množství kombinovaných operátorů přiřazení (+=, -= , *= , /= , %= , &= , |= , ^= , <<= , >>=), které modifikují svůj levý operand na výsledek binární operace, která je s levým a pravým operandem provedena. Například výraz x+=5 je zkráceným zápisem pro x=x+5. Kombinované přiřazení se používá ke zkrácení zdrojového kódu. Efekt je tím výraznější, čím delší je název proměnné.. Na slajdu č. 46 nahoře vpravo vidíme další příklady. Přiřazovací operátory mají oproti ostatním binárním operátorům rozdílný způsob vyhodnocování. Pokud je ve výrazu více operátorů se stejnou prioritou, ty se obvykle vyhodnocují směrem zleva doprava. V případě přiřazovacích operátorů je směr vyhodnocování zprava doleva. To nám umožňuje psát zápisy typu a=b=5, kdy několika proměnným přiřadíme stejnou hodnotu. Nejprve se do proměnné b okopíruje hodnota 5 a následně se do proměnné a okopíruje hodnota proměnné b, tedy 5. 2.6 Inkrementace a dekrementace Pro zvýšení hodnoty proměnné o 1 můžeme použít operátor ++, pro snížení hodnoty proměnné o 1 můžeme použít operátor --. Oba tyto operátory mají svoji prefixní a postfixní verzi, které se od sebe liší. Pro jednoduchost se budeme dále zabývat jen operátorem ++, pro operátor -- platí vše analogicky. V obou výrazech y++ a ++y jsme zvýšili hodnotu proměnné y o 1. Rozdíl je ve výsledku těchto výrazů. Výsledkem výrazu y++ je stará hodnota y, výsledkem výrazu ++y je nová zvýšená hodnota y. Pokud bychom tyto výrazy zakončili středníkem, získaly bychom příkazy, ve kterých návratovou hodnotu ignorujeme, nebyl by mezi zápisy žádný rozdíl. Pokud použijeme tyto výrazy například jako index pole nebo podmínku u cyklu nebo podmíněného výrazu, rozdíl se projeví. Na slajdu č.46 veprostřed vpravo vidíme příklad. Do proměnné sum v každé iteraci cyklu přičítáme hodnotu uloženou v poli x na pozici dané proměnnou i a následně zvyšujeme hodnotu proměnné i o jedna. V proměnné sum budou tedy sečteny prvky pole s indexy 0 až 8. Pokud by jsme nahradili i++ za ++i budou v proměnné sum sečteny prvky pole s indexy 1 až 9, protože hodnota proměnné i by se zvyšovala dříve, než by se do sum přičítala hodnota pole s indexem i. 2.7 Podmíněný výraz Ternárního operátor ?: slouží k tvorbě podmíněného výrazu a?b:c. První operand a je podmínka, při jejímž splnění bude výsledkem celého výrazu hodnota druhého operandu b, při nesplnění podmínky bude výsledkem výrazu hodnota třetího operandu c. Operand, jehož hodnota nebude výsledkem výrazu, se nevyhodnocuje. Výsledek podmíněného výrazu pak můžeme například přiřadit do proměnné.
Pokud v kladné i else větvi podmíněného příkazu pouze modifikujeme hodnotu jedné proměnné, můžeme tento podmíněný příkaz nahradit podmíněným výrazem používajícím ternární operátor ?:. Na slajdu č. 47 nahoře vpravo vidíme takovýto příklad podmíněného příkazu, zkráceného do podmíněného výrazu (nahoře vlevo). 2.8 Sekvence Binární operátor sekvence , (čárka) se také někdy označuje jako operátor zapomnění. Nejprve se vyhodnotí levý operand, výsledek vyhodnocení se zapomene, poté se vyhodnotí pravý operand, jehož hodnota je hodnotou celého výrazu. Obecně se operátor sekvence chová podobně jako středník, který odděluje dva příkazy. Operátor sekvence spojuje dva výrazy do jednoho a jeho smysluplné použití je velmi limitované, obvykle bývá chybně použit na místo středníku nebo desetinné tečky u reálných čísel. Operátor sekvence se dá využít například ve for cyklu, pokud chceme mít více nezávislých inicializačních nebo inkrementačních výrazů, tak tyto nezávislé výrazy pomocí operátoru sekvence spojíme do jednoho. Na slajdu č. 47 dole vidíme, jak pomocí operátoru sekvence nahradíme postfixní operátor ++. 2.9 Pravidla vyhodnocování Existuje jen velmi málo pravidel, která platí pro vyhodnocování výrazů. Některé operace mohou mít vedlejší efekty (například tisk na obrazovku, změna hodnoty proměnné ++, +=). Pokud je volána funkce, tak se nejprve vyhodnotí její parametry, ale není dáno jejich vzájemné pořadí vyhodnocení. Pouze platí, že vedlejší efekty parametrů jsou vyhodnoceny před zavoláním funkce. U logické konjunkce a logické disjunkce se druhý parametr nevyhodnocuje, pokud je po vyhodnocení prvního parametru znám výsledek celého operace. V podmíněném výrazu se po vyhodnocení podmínky vyhodnotí pouze ten operand, který bude výsledkem celého výrazu. Operátor sekvence vyhodnotí svůj levý operand (včetně vedlejších efektů) a teprve poté vyhodnotí svůj pravý operand. Žádná další pravidla nejsou. Ostatní operátory mohou své parametry vyhodnocovat v libovolném pořadí a vedlejší efekty se mohou projevit kdykoliv během výpočtu. Případným nejednoznačnostem jde zabránit dodržováním následujícího pravidla. Pokud modifikujeme proměnou ve výrazu pomocí vedlejšího efektu (++, +=), neměla by se tato proměnné v daném výrazu znova vyskytnout. Na slajdu č. 48 dole vidíme příklad, ve kterém je v jednom příkaze dvakrát použita inkrementace proměnné i. Podle toho, kdy se tyto vedlejší efekty projeví, může se tento příkaz vykonat třemi různými způsoby. Na slajdu č. 49 vidíme několik příkladů. První příklad ukazuje vyhledávání hodnoty v setříděném spojovém seznamu s využitím zkráceného vyhodnocování logické konjunkce, takže nebudeme přistupovat k nulovému ukazateli při zjišťování hodnoty p->v. Druhý příklad ukazuje jak testovat přečtený znak z klávesnice na dvě různé hodnoty v rámci jednoho příkazu. Třetí příklad ukazuje kopírování řetězců v rámci jednoho příkazu. Dole vidíme i dva chybné příklady. První z nich jsme už viděli na předchozím slajdu. Poslední z příkladů obsahuje čtení znaku ze standardního vstupu v pravém operandu logické konjunkce. Jestli se bude pravý operand vyhodnocovat, ale záleží na výsledku vyhodnocení levého operandu.
Klíčové pojmy Datové typy (celočíselný – znaménkový a bezznaménkový, znakový, reálný, výčtový, velikost typu) Automatické číselné konverze Přetypování Přetečení Operátor (priorita, infuzní, prefixní, postfixní, aritmetický, logický, porovnání, bitový, ternární) Zkrácené vyhodnocování Vedlejší efekt Podmíněný výraz Otázky k rekapitulaci Upozornění: odpovědi na některé zde uvedené otázky nelze najít ve studijním textu tohoto tématického bloku. Lze je získat vlastním experimentováním se zdrojovými kódy nebo studiem doporučené literatury. Je pevně daná velikost jednotlivých datových typů? Jaké celočíselné datové typy znáte? Jaké jsou rozdíly mezi znaménkovým a bezznaménkovým typem? Můžeme za běhu programu zjistit jak je datový typ velký? Jaké reálné typy znáte? Na jaké situace není vhodné reálné typy používat a proč? Existuje v jazyce C logický typ? Jakým způsobem se vyhodnocuje splnění podmínky například v podmíněném příkazu? Existuje v jazyce C znakový typ? Jaké problémy přináší práce se znaky s diakritikou? Co je to znaková konstanta a z jakého důvodu se používá? Rozhodněte zda platí: 'a' < 'z' . Rozhodněte zda platí: 'a' < 'ž' . Jak převedu malé písmeno na velké písmeno bez použití knihovních funkcí? Jak se definuje výčtový typ a jaké má použití? Jak vypadá vypsaná hodnota výčtového typu na obrazovce? Můžeme sčítat celé a reálné číslo? Jaký datový typ (a z jakého důvodu) použijete pro řídící proměnou for cyklu, který bude mít 10 iterací? Je vhodné používat jako řídící proměnnou cyklu reálný datový typ? Jaký je rozdíl mezi celočíselným a normálním dělením? Jak kompilátor určuje, zda použije celočíselné nebo normální dělení? Co je to přetečení? Co je to přetypování a k čemu ho lze využít? Jakým způsobem se rozhoduje o pořadí vyhodnocení operátorů ve výrazu? Jaký je rozdíl mezi infixním, postfixním a prefixním operátorem?
Jaké znáte příklady unárních, binárních a ternárních operátorů? Který z matematických operátorů vyžaduje celočíselné operandy? Jaký je rozdíl mezi celočíselným a normálním dělením? Mohu reálným dělením vydělit i dvě celá čísla? Jaký je rozdíl mezi bitovými a logickými operátory? Můžeme bitové a logické operátory vzájemně zaměňovat? Jaký je princip exkluzivní disjunkce XOR? Co je to zkrácené vyhodnocování, jaké přináší výhody a problémy? Co je to bitový posun, jak se dá využít? Čím se liší přiřazovací operátory od ostatních operátorů z hlediska způsobu vyhodnocování? Čím se liší přiřazovací operátory od ostatních operátorů z hlediska požadavků na své operandy? Můžeme více proměnným přiřadit stejnou hodnotu v rámci jednoho příkazu? Co je to kombinované (zkrácené) přiřazení a kdy se používá? Co je to vedlejší efekt? Jaký je rozdíl mezi prefixní a postfixní verzí operátoru ++? Jak lze vyjádřit postfixní operátor ++ pomocí operátoru sekvence? Jaký typ podmíněného příkazu lze vyjádřit podmíněným výrazem? Co je operátor sekvence a k čemu se dá využít? Platí nějaká pravidla o vyhodnocování skutečných parametrů funkcí? Platí nějaká pravidla o pořadí vyhodnocování parametrů operátorů? Pokud ve výrazu modifikujeme nějakou proměnnou, jaká jsou pravidla pro její bezpečné použití v tom samém výrazu? Své odpovědi zdůvodněte. Můžete přidat i syntaktické zápisy tam, kde je to vhodné. Doporučené příklady k naprogramování 1. Napište funkci, která bude počítat zbytek po celočíselném dělení (modulo) bez použití operátoru %. 2. Napište program, který ze vstupních dat spočítá aritmetický průměr a najde třetí největší prvek. Vstupní data se čtou ze standardního vstupu. 3. Prozkoumejte vlastnosti celočíselných a reálných typů. Pokuste se o přetečení celočíselného typu. Na reálném čísle proveďte sérií matematicky ekvivalentních úprav a dosáhněte rozdílného čísla než bylo číslo původní. Vypište ANSI znakovou sadu a zjistěte závislosti mezi písmeny anglické abecedy. 4. Naprogramujte karetní hru prší s použitím výčtových typů pro hodnoty a barvy karet. Pokud neznáte pravidla, tak si určete vlastní. 5. Napište funkci, která vypíše na obrazovku číslo ve dvojkové soustavě. 6. Vyzkoušejte si jednotlivé bitové operace, včetně bitových posunů. K zobrazení výsledků použijte funkci z bodu (5).
Studijní literatura Výklad často odkazuje na slajdy (ve formátu .ppt), které je vhodné si vytisknout. Je vhodné si pořídit nějakou knihu o programování v C nebo C++. Uvedené příklady knih berte pouze jako inspirativní. Miroslav Virius: Programování v C++ (ČVUT, 2. vydání 2004) Jesse Liberty, Bradley L. Jones: Naučte se C++ za 21 dní (Computer Press, 2. vydání, 2007) Knihu je dobré číst postupně a vlastním tempem, můžete mít i mírné zpoždění oproti našemu výkladu. Pořadí kapitol v knize neodpovídá úplně přesně pořadí, v jakém učivo probíráme. Tento tématický blok se zaměřte na datové typy a operátory Miroslav Virius: Pasti a propasti jazyka C++ (Brno, 2. vydání 2005) Kapitola 1.2 Operátory