Distanční opora předmětu: Programování v jazyce C Tématický blok č. 2: Proměnná, výraz, příkaz, podmínka, cyklus Autor: RNDr. Jan Lánský, Ph.D. Obsah kapitoly 1 Proměnné 1.1 Deklarace a inicializace proměnných 1.2 Lokální proměnné 1.3 Globální proměnné 1.4. Předávání parametrů funkcí hodnotou 2 Motivační příklad: Malá násobilka 3 Výraz 4 Příkaz 4.1 Podmíněný příkaz if 4.2 Vnořování podmíněných příkazů 4.3 Vícenásobné větvení switch 5 Cykly 5.1 Cyklus while 5.2 Cyklus for 5.3 Ukončení cyklu break 5.4 Ukončení jedné iterace continue 6 Příkaz skoku goto 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 aktivně používat příkazy if, for, while, včetně jejich vzájemného zanoření. Ve svých programech umět aktivně využívat lokální proměnné a parametry funkcí. 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 Ve svých programech umět aktivně používat příkazy switch, break, continue. Umět definovat výraz a příkaz. Pasivní znalost existence příkazu goto Čas potřebný ke studiu 1 - 3 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 minut na (znovu)zodpovězení otázek k rekapitulaci (v jiný den) 2 - 8 hodin vypracování úlohy POT 1 Č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 ve svých programech používat lokálních proměnných a vysvětlíme si mechanizmus předávání parametrů funkcí. Ukážeme si i jak deklarovat globální proměnné. Vysvětlíme si pojmy výraz a příkaz, naučíme se vytvářet a správně používat složené příkazy. Probereme problematiku podmíněných příkazů, vícenásobného větvení a cyklů. Zaměříme si i na časté chyby při psaní zdrojového kódu a prevenci proti nim. 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 Proměnné Proměnná je místo v paměti, kde je uložena nějaká informace ( = hodnota proměnné). Každá proměnná má svůj název, pomocí kterého s ní pracujeme. Proměnná má také svůj typ, který udává, jak hodnotu proměnné ( = obsah paměti) interpretovat. Typ proměnné může být například celé číslo či reálné číslo. 1.1 Deklarace a inicializace proměnných Proměnné se deklarují uvedením názvu datového typu následovaného jménem proměnné. Deklarace proměnné končí středníkem. Na slajdu č. 24 v zeleném rámečku vidíme deklaraci několika proměnných. Například zápis int p; deklaruje proměnnou typu celé číslo s názvem p. Můžeme hromadně deklarovat i více proměnných jednoho typu, pokud jejich názvy oddělíme čárkou. Například zápis int q, r; deklaruje dvě proměnné q a r typu celé číslo. Při deklaraci proměnné můžeme určit její počáteční hodnotu (= inicializovat proměnnou), pokud za jejím jménem následuje operátor = a hodnota. Zápis int s = 5; například deklaruje proměnnou s typu celé číslo, která je inicializována na hodnotu 5. Deklarovat můžeme více proměnných najednou a zároveň jednu nebo některé z nich inicializovat. Například zápis int x, z = 0, y; deklaruje proměnné x, z a y typu celé číslo, navíc je proměnná z inicializována na hodnotu 0.
1.2 Lokální proměnné V jazyce C se lokální proměnné deklarují na začátku těla funkce, ihned po otevíracích složených závorkách. Mezi deklaracemi proměnných nesmí být žádný příkaz. V jazyku C++ lze proměnné deklarovat kdekoliv v těle funkce. Přestože používáme překladač C++, budeme se snažit dodržovat deklarace podle jazyka C a deklarovat proměnné na začátku těla funkce. Lokální proměnnou můžeme používat pouze v těle funkce, kde je deklarována, při pokusu o její použití mimo tělo této funkce kompilátor zahlásí chybu neznámý identifikátor. V různých funkcích můžeme používat stejný název proměnné. Velmi často se ve více funkcích používá název proměnné i pro řídící proměnnou cyklu. Lokální proměnná existuje jen po dobu běhu funkce. Po skončení běhu funkce je hodnota lokální proměnné nenávratně ztracena. Pokud lokální proměnné v jazyku C neurčíme explicitně inicializační hodnotu, je její hodnota náhodná. Chceme pracovat pouze s inicializovanými proměnnými, a proto z lokální proměnné můžeme číst hodnotu teprve poté, co byla tato proměnná buďto explicitně zinicializována při deklaraci, a nebo byla do ní nějaká hodnota přiřazena předchozími příkazy. Toto pravidlo se dá zjednodušit. Pokud se proměnná vyskytla v nějakém příkazu či deklaraci nalevo od operátoru =, smíme ji pak použít v následujících příkazech i napravo od operátoru =. Velmi často lidé mylně předpokládají, že lokální proměnné budou samy od sebe inicializovány hodnotou nula a s touto informací pracují. Při ladění programu se pak obvykle objevují zcela nesmyslná čísla (v případě proměnné typu int se jedná o čísla v řádu stovek milionů). Na slajdu č. 24 na příkladu v zeleném rámečku vidíme na posledním řádku výraz z+x. Proměnná z má hodnotu 0, zatímco proměnné x, zatím žádná hodnota nebyla přiřazena, a x tedy obsahuje náhodnou hodnotu. Výsledkem celého výrazu bude náhodná hodnota. Tento výraz následuje za return, proto celá funkce vrací náhodnou hodnotu. 1.3 Globální proměnné Globální proměnné se deklarují mimo těla funkcí. Pro lepší přehlednost se globální proměnné ve zdrojovém souboru obvykle deklarují úplně na jeho začátku. Globální proměnné jsou viditelné ( = můžeme s nimi pracovat) ve všech funkcích. Globální proměnné se inicializují při startu programu hodnotou nula, nebo inicializační hodnotou použitou při deklaraci globální proměnné. Globální proměnná zaniká pouze s koncem celého programu. Pokud hodnotu globální proměnné modifikujeme v těle nějaké funkce, je tato změna trvalá a nezmizí s koncem běhu funkce. Na slajdu č. 27 vidíme použití globální proměnné g. Zkuste si slajd prohlédnout a určit, co bude návratovou hodnotou funkce main. Až budete mít odpověď, můžete pokračovat dál ve čtení. Ve funkci fce do proměnné g přičítáme hodnotu z*x, kde z je lokální proměnná a x je parametr funkce. Funkce fce je dvakrát zavolána z funkce main. Po jejím prvním volání bude v g hodnota 5, po druhém volání 11. Hodnota proměnné g bude i návratovou hodnotou programu (opět 11). Na slajdu si můžete zopakovat i učivo minulého tématického bloku: formální a skutečný parametr funkce.
Globální proměnné by jsme měli používat pouze ve výjimečných případech, například pro sdílená data, se kterými pracuje většina funkcí programu. Dalším dobrým důvodem může být ladění programu, pokud do globální proměnné ukládáme ladící data (například počet průchodů vybranou funkcí či velikost celkově alokované paměti). Každý program lze napsat bez použití globálních proměnných, a to by měl být náš cíl. Jsou programovací jazyky (například Java), kde globální proměnné neexistují. 1.4. Předávání parametrů funkcí hodnotou V jazyku C se všechny parametry funkcí předávají hodnotou. Můžeme si představit, že formální parametry funkce se chovají jako lokální proměnné inicializované skutečnou hodnotou parametru, se kterou byla funkce zavolána. Skutečné parametry funkce se tedy pouze okopírují a my pracujeme s kopiemi místo původních hodnot. Hodnoty parametrů funkce můžeme tedy libovolně modifikovat, ale změny se projeví pouze uvnitř těla funkce. Ve chvíli kdy funkce skončí, všechny lokální proměnné zanikají a s nimi i tyto kopie skutečných parametrů, se kterými jsme pracovali. Na slajdu č. 26 vidíme neúspěšný pokus o napsání funkce swap, která by prohodila mezi sebou hodnoty svých dvou parametrů. Algoritmus použitý v těle funkce je v pořádku, provádí výměnu hodnot parametrů x a y s použitím jedné pomocné lokální proměnné pom. V čem je tedy problém? Zkusíme si tento kus kódu vytrasovat ( = projít v pořadí v jakém se vykonávají jednotlivé příkazy). Z funkce fce se volá funkce swap se skutečnými parametry a a b. Po zavolání funkce swap se hodnoty skutečných parametrů okopírují do lokálních proměnných, které vzniky z formálních parametrů. Do x se přiřadí hodnota proměnná a, tedy 1. Do y se přiřadí hodnota proměnná b, tedy 2. Nyní se vykoná tělo funkce swap a hodnoty v x a y se správně vymění. V x je hodnota 2, v y je hodnota 1. Funkce swap skončí a zaniknou její lokální proměnné, mezi nimi i x a y. Výměna skutečně proběhla, ale pouze na kopiích x a y, proměnné a a b zůstaly nezměněny. Hodnota, která se v dalším příkazu spočítá do proměnné c je tedy 4. nikoliv 5 jak by jsme na první pohled čekali. Může se nám zdát nelogické, že takto předávání parametrů funkcí funguje. Díky kopírování hodnot parametrů však můžeme funkci swap zavolat i přímo s parametry 1 a 2 (a ne jen přes proměnná a a b). Pokud by jsme chtěli, aby se modifikovaná hodnota parametru propagovala do volající funkce (= parametr předávaný odkazem), musíme použít mechanizmy ukazatelů nebo referencí, které budeme probírat v pozdějších tématických blocích. 2 Motivační příklad: Malá násobilka Na slajdu č. 25 vidíme už trochu složitější příklad. V klidu si ho prohlédněte a zkuste popsat, co program přesně dělá. Zkuste, aby váš popis byl jednoznačný a úplný. Všimněme si, že funkce main je zde uvedena bez svých parametrů, které slouží ke zpracování parametrů programu zadaných z příkazové řádky. Na slajdech budeme parametry funkce main vynechávat z důvodu úspory místa. Ve svých programech je nevynechávejte, protože se nám časem budou hodit. Ve funkci main máme deklarovanou lokální proměnou cislo, inicializovanou na hodnotu 7. Hodnota této proměnné je použita jako skutečný parametr funkce vynásob, se kterým je tato funkce zavolána. Funkce main končí vrácením hodnoty
0, tedy oznámením operačnímu systému, že vše v programu proběhlo v pořádku (dle našeho
názoru). Na prvním řádku vidíme použití direktivy #include, pro vložení hlavičkového souboru studio.h, v němž se nachází hlavička funkce printf. Hlavička funkce vynasob je uvedena na dalším řádku. Funkce vrací návratovou hodnotu typu celé číslo a má jeden formální parametr c také typu celé číslo. Ve funkci jsou definovány dvě lokální celočíselné proměnné i a v, proměnná i má inicializační hodnotu 1. Na dalších řádcích vidíme použity dvě nové syntaktické konstrukce (podmínku if a cyklus while), které si probereme velmi podrobně v dalších kapitolách tohoto textu. V podmínce následující po klíčovém slovu if testujeme, zda parametr c, není z intervalu [1,10]. K tomu jsme použili disjunkci ( = logické nebo) dvou podmínek c < 1 a c > 10. Všimněme si že podmínka uvedená za if musí být v závorce bez ohledu na její složitost. Pokud parametr c není z intervalu [1,10], podmínka je splněná a vykoná se její tělo, příkaz return -1;. Tímto příkazem funkce končí. Funkce vynásob odmítá zpracovávat hodnoty parametru c, které nejsou z intervalu [1,10]. Následuje cyklus while, jehož tělo se vykonává, dokud je platná podmínka uvedená v závorce, v našem případě hodnota proměnné i je menší nebo rovna 10. V těle cyklu jsou tři příkazy. Nejprve počítáme hodnotu proměnné v jako i*c. Poté tiskneme na standardní výstup (nejčastěji obrazovku) jeden řádek malé násobilky (například při prvním průchodu cyklem vytiskneme 1 * 7 = 7). Poslední řádek v těle cyklu zvyšuje hodnotu proměnní i o 1. Tento cyklus se vykoná celkem desetkrát a vypíše nám všechny násobky parametru c (v našem případě 7). Po skončení while cyklu následuje ukončení funkce vynasob s návratovou hodnotou 0. Zkusme se zamyslet, jak by jsme mohli program zpřehlednit či vylepšit jeho funkčnost . Zde jsou tři nápady. Možná nás překvapilo použití while cyklu, když jsme znali přesný počet iterací cyklu. Použití for cyklu by program zpřehlednilo. Ve funkci main by před return 0; mohla být zavolána funkce getch, abychom měli šanci si přečíst, co program vypsal. Ve funkci main by return místo hodnoty 0 mohl vracet návratovou hodnotu funkce vynasob, kterou teďka ignorujeme. Využili bychom toho ,že funkce vynasob oznamuje, zda proběhla v pořádku, či ne. 3 Výraz Podle normy jazyka C je výraz posloupnost operací a operátorů specifikující výpočet hodnoty. Někomu se může zdát přehlednější následující rekurzivní definice. A) Proměnná nebo hodnota jsou výraz. (příklady: 1, "ahoj") B) Posloupnost unární operátor a výraz je opět výraz (příklady: -1, -x, *p ) C) Posloupnost výraz, binární operátor a výraz je opět výraz (příklady: -1*a, 3-x, 7+4*a)
D) Volání funkce je výraz, pokud všechny její parametry jsou výrazy. (příklady: sin(x), printf("ahoj"), fce(2+sin(x)*-6,1,"baf"), fce2()) E) Pomocí závorek () můžeme ve výrazu upřesnit pořadí vykonávání operátorů (příklady: (2+3)*4, -(2+sin(x))). Každý výraz má svojí hodnotu a datový typ. V případě, že výrazem je volání funkce, je hodnotou výrazu návratová hodnota volané funkce. Na slajdu č. 28 vlevo v zeleném rámečku vidíme několik dalších příkladu výrazů. Všimněme si, že výraz může mít i nějaký vedlejší efekt. Například výraz printf("ahoj") má vedlejší efekt, že vytiskne na standardní výstup řetězec ahoj. Hodnotou tohoto výrazu je samozřejmě návratová hodnota volání funkce, tedy počet vytisknutých znaků, číslo 4. Přiřazovací výraz je také výrazem, jeho hodnotou je přiřazovaná hodnota. Je možné i vícenásobné použití operátoru přiřazení v rámci jednoho výrazu, například při inicializaci více lokálních proměnných na stejnou hodnotu (příklad a = b = 0). Toho, že přiřazovací výraz má svoji hodnotu, se velmi často využívá i v následující konstrukci: if (( x = fce() != 0) Na jednom řádku do proměnné x uložíme hodnotu volání funkce a tuto hodnotu otestujeme například na nerovnost od nuly. 4 Příkaz (Jednoduchý) příkaz je výraz ukončený středníkem. Složený příkaz vznikne pokud několik příkazů uzavřeme do složených závorek { a }. Na rozdíl od jazyka Pascal nemůžeme v jazyku C u posledního příkazu ve složeném příkazu vynechat středník. V jazyku C každý jednoduchý příkaz končí středníkem bez ohledu na jeho postavení ve zdrojovém kódu (tedy i pokud je posledním příkazem před else, nebo posledním příkazem ve složené závorce. Složený příkaz může být i prázdný {}, tedy neobsahovat žádný příkaz. Příklady výrazů vidíme na slajdu č. 29 vlevo, příklad na složený příkaz vidíme vpravo Příkazem jsou i programové konstrukce if, if-else, switch, while, do-while, for, break, continue, return, goto, které si probereme nyní podrobněji. 4.1 Podmíněný příkaz if Na slajdu č. 30 nahoře vidíme formální syntaktický zápis podmíněného příkazu. Po klíčovém slovu if následuje v kulatých závorkách výraz (ten označujeme jako podmínka). Pokud je podmínka splněna, pak se vykoná příkaz, který je za ní (za zavírací kulatou závorkou) uveden ( = kladná větev). Podmíněný příkaz může nepovinně pokračovat klíčovým slovem else následovaným příkazem, který se vykoná v případě, že podmínka splněna nebyla ( = else větev). Podmínka za klíčovým slovem if musí být povinně v kulaté závorce, i když je velmi jednoduchá. Podmínka je splněna, když hodnota jejího výrazu je různá od hodnoty nula. Pokud v kladné (či else) větvi máme více příkazů než jeden, je nutné je uzavřít do složených závorek a tak z nich udělat jeden složený příkaz.. Na slajdu č. 30 vlevo na zeleném podkladu vidíme dva podmíněné příkazy. První z nich vytiskne na standardní výstup text OK, pokud hodnota proměnná a je větší než 1. Druhý podmíněný příkaz je ještě doplněn o else větev, která se vykoná když podmínka splněna není, vytiskne se nee.
Na slajdu č. 30 vprostředku na červeném podkladu vidíme dvě chybná použití podmíněného příkazu. V obou případech jsme zapomněli uzavřít příkazy, které se mají vykonat při splnění podmínky, do složených závorek, aby se z nich stal jeden složený příkaz. Opravené verze příkladů vidíme na zeleném podkladu, úplně napravo na slajdu. V horním příkladě podmíněný příkaz neobsahuje else větev, tak se tato chyba projeví pouze ve špatném chování programu – text OK se bude vypisovat bez ohledu na výsledek vyhodnocení podmínky. Ve spodním příkladě s else větví nepůjde program ani zkompilovat. Kompilátor ohlásí chybu, že nalezl else, který nepatří k žádnému if. Velmi často se chybuje při přidávání příkazů do kladné nebo else větve již existujícího podmíněného příkazu, pokud původně v dané větvi byl pouze jeden příkaz – zapomíná se příkazy uzavřít do složených závorek. 4.2 Vnořování podmíněných příkazů Vnořený podmíněný příkaz je označení podmíněného příkazu, který se nachází v kladné větvi jiného podmíněného příkazu. Pokud právě jeden z těchto dvou podmíněných příkazů má else větev, stává se zdrojový kód nepřehledným. Existuje však možnost, jak přehlednost tohoto zdrojového kódu zvýšit. Kladnou větev vnějšího podmíněného příkazu celou uzavřeme do složených závorek, i když by v konkrétním případě uzavřena být nemusela. Na slajdu č. 31 vidíme několik příkladů. Vlevo na červeném podkladu vidíme podle odsazení zdrojového kódu, že else větev měla podle programátora patřit vnějšímu podmíněnému příkazu. V jazyce C platí pravidlo, že else větev patří vždy k nejvíce vnořenému podmíněnému příkazu, z těch co přicházejí v úvahu. Tedy v našem případě else větev patří vnitřnímu podmíněnému příkazu. Vpravo na zeleném podkladu vidíme dvě verze zdrojového kódu, které získáme různým umístěním složených závorek. První verze odpovídá tomu, co si přál programátor podle odsazení zdrojového kódu (nalevo na červeném podkladu). Druhá verze ukazuje, co ve skutečnosti napsal. 4.3 Vícenásobné větvení switch Pokud chceme vykonat různé akce na základě více než dvou možných hodnot (nula, různé od nuly) jednoho výrazu, můžeme použít několik zřetězených podmíněných příkazu if nebo vícenásobné větvení switch, které udělá zdrojový kód přehlednějším. Motivačním příkladem může být funkce, která ve své návratové hodnotě vrací číslo chyby, která při jejím běhu nastala. My chceme podle čísla dané chyby vypsat na obrazovku chybovou hlášku, případně provést další příkazy. Na slajdu č. 32 vlevo vidíme formální syntaktický zápis vícenásobného větvení. Za klíčovým slovem switch v kulaté závorce následuje výraz, podle jehož hodnoty se budeme rozhodovat. Tento výraz musí být celočíselného datového typu. Tělo příkazu switch je uzavřeno ve složených závorkách obsahuje posloupnost příkazů a case návěští. Case návěští je posloupností klíčového slovo case následovaného konkrétní hodnotou a dvojtečkou. Case návěští musí obsahovat právě jednu hodnotu, není povolen interval nebo výčet více hodnot. V těle příkazu switch se může nejvýše jednou vyskytovat defaultní návěští, ale za ním nesmí následovat žádné další case návěští. Default návěští je posloupnost klíčového slova default následovaného dvojtečkou.
Postup vyhodnocování výrazu switch je na první pohled poněkud překvapivý. Vyhodnotí se výraz uvedený v kulaté závorce za switch a najde se takové case návěští, jehož hodnotě (hodnotě uvedené za slovem case) se rovná. Pokud žádné takové case návěští nebylo nalezeno a je zde default návěští, bude nalezeným návěštím toto default návěští. Nyní se postupně provedou příkazy následující za nalezeným návěštím až do konce těla switch nebo do nalezení příkazu break. Nebere se ohled na přítomnost dalších návěští. Obvykle chceme, aby se vykonávání příkazů zastavilo před dalším návěštím, umístíme zde tedy příkaz break. Opačná situace, že chceme pokračovat i příkazy uvedenými za dalším návěštím, je tak vzácná, že chybějící příkaz break se vysvětluje komentářem v programu. Pokud chceme více možných hodnot výrazu uvedeného v závorce za switch ošetřit pomocí stejného zdrojového kódu, uvedeme několik case návěští za sebou, aniž by mezi nimi byly příkazy. Příkazy budu teprve za posledním case návěštím. Na slajdu č.32 veprostřed na zeleném podkladu je ukázka zpracování návratové hodnoty funkce, která v ní předává číslo chyby. Tato návratová hodnota je uložena v proměnné errc, jejíž hodnotu pomocí příkazu switch testujeme. Všimněme si důsledného uvádění příkazu break před každým novým návěštím s výjimkou případu -2 a -3, které chceme ošetřit společně. Na slajdu č. 32 vpravo nahoře na červeném podkladu je ukázka zapomenutého příkazu break. U hodnoty 0 se nám vypíše text jak OK tak i text spatne. Příklad napravo dole ukazuje marný pokus o zpracování intervalu. 5 Cykly Cyklus je tvořen klíčovým slovem určujícím jeho typ a podmínkou určující zda a jak dlouho se bude vykonávat tělo cyklu. Tělo cyklu je tvořeno jedním příkazem (obvykle složeným). Každé vykonání těla cyklu se nazývá iterace cyklu. Přestože máme tři syntaktické konstrukce jak cyklus zapsat (while, do-while a for), dají se všechny tyto zápisy navzájem mezi sebou převést. Cyklus, který neobsahuje podmínku, nebo obsahuje podmínku, která je vždy splněna je nekonečným cyklem. Jeho tělo se bude stále opakovat. Nejčastěji takový cyklus vznikne chybou programátora a běžící aplikaci obsahující tento nekonečný cyklus jde potom ukončit jen násilně prostředky operačního systému. Občas se ale nekonečné cykly vytvářejí úmyslně a předpokládá se jejich ukončení uvnitř cyklu pomocí příkazu break (viz 5.3). 5.1 Cyklus while Po klíčovém slovu while následuje v kulatých závorkách podmínka, při jejímž splnění se vykoná tělo cyklu tvořené jedním příkazem (obvykle složeným). Po vykonání těla cyklu se opět vyhodnotí podmínka a rozhodne se o dalším vykonání těla cyklu atd. Vyhodnocování podmínky se řídí stejnými pravidly, jako u podmíněného příkazu if. Podmínka je splněna, když hodnota jejího výrazu je různá od nuly. Podmínka musí být povinně v kulaté závorce bez ohledu na její složitost.
Existuje i obdoba repeat cyklu z Pascalu. Po klíčovém slovu do následuje tělo cyklu, po kterém následuje klíčové slovo while a podmínka v kulatých závorkách. V tomto případě se nejdříve vykoná tělo a teprve poté se testuje podmínka, zda budeme pokračovat další iterací. Na slajdu č. 33 vidíme tři ukázky zdrojového kódu. Nejprve se podíváme na příklad vlevo nahoře. Zkuste si rozmyslet, jaká bude hodnota proměnné a po skončení cyklu v závislostí na její počáteční hodnotě? Předpokládejme, že typ proměnné a je celočíselný. Pokud máte rozmyšlenou odpověď, můžete pokračovat dále ve čtení. Pokud je hodnota proměnné a nula nebo záporná, pak se cyklus nevykoná ani jednou a její hodnota zůstane nezměněna. Pokud je hodnota a například 15 965, tak se její hodnota v každé iteraci cyklu sníží na polovinu (celočíselné dělení dvěma). Tedy postupně a bude nabývat hodnot 7 982, 3 991, 1995, 997, 498, 249, 124, 62, 31, 15, 7, 3, 1. Všimněme si, že hodnota každého kladného čísla v cyklu postupně klesá a zastaví se na hodnotě jedna. Podívejme se na příklad vlevo dole. Předchozí příklad jsme obohatili o volání funkce fce se skutečným parametrem a uvnitř těla cyklu. Zkuste odpovědět na tu samou otázku jako v minulém příkladě. Jaká bude hodnota proměnné a na konci cyklu? Potřebujete ke své odpovědi vidět tělo funkce fce? Správná odpověď je stejná jako v minulém příkladě. Protože parametr a funkce fce byl předán hodnotou (viz kapitola 1.4), nemůže mít volání funkce fce vliv na hodnotu proměnné a v těle cyklu, bez ohledu na tělo funkce fce. Přiklad vpravo demonstruje použití do-while cyklu. Minulý příklad jsme modifikovali tak, že cyklus se vykoná alespoň jednou. Uhodnete, jak se změní odpověď na otázku, jaká bude hodnota proměnné a na konci cyklu? Pokud je hodnota proměnné a menší než 2, pak se celočíselně vydělí hodnotou dvě. V opačném případě se hodnota proměnné a po skončení cyklu změní na 1. 5.2 Cyklus for Na slajdu č. 34 vlevo nahoře vidíme syntaktický zápis for cyklu. Po klíčovém slovu for následuje kulatá závorka, ve které jsou tři výrazy (inicializace, podmínka a inkrement) navzájem oddělené středníky. Po kulaté závorce následuje příkaz (obvykle složený), který tvoří tělo cyklu. Velmi často lidé chybně mezi jednotlivými výrazy nahrazují operátor středníku operátorem čárky, případně zapomínají na kulatou závorku. Inicializace se provádí pouze jednou a to jako první příkaz před prvním průchodem těla cyklu. Obvykle se zde nastavuje hodnota řídící proměnné cyklu. Podmínka podobně jako ve while cyklu rozhoduje zda se vykoná další iterace těla cyklu. Obvykle se v podmínce porovnává hodnota řídící proměnné cyklu s nějakou konstantou. Po vykonání každé iterace cyklu se provede inkrement, obvykle změna (zvýšení) řídící proměnné cyklu. Následuje pak další test podmínky atd. V těle cyklu for není vhodné modifikovat řídící proměnnou cyklu, zdrojový kód se stává značně nepřehledným. Na slajdu č. 34 vlevo dále vidíme jak lze nahradit libovolný for cyklus while cyklem Vpravo nahoře na slajdu vidíme for cyklus, který postupně zavolá funkce fce se skutečnou hodnotou parametru 0 až 9. Vpravo dole je tento for cyklus nahrazen while cyklem, přesně podle vzoru. Uprostřed slajdu dole je ukázáno, že za inicializaci, podmínku a inkrement lze dosadit libovolný výraz.
Cyklem for lze také procházet spojový seznam nebo binární vyhledávací strom. Řídící proměnou cyklu je v těchto případech ukazatel na uzel dané datové struktury. 5.3 Ukončení cyklu break Občas nastane situace, kdy je potřeba cyklus náhle přerušit. Velmi často tak reagujeme na výsledek volání nějaké funkce, která může vrátit chybovou hodnotu. Například pokud v cyklu zapisujeme do souboru a dojde k zaplnění disku a je nám další zápis znemožněn. Toho můžeme dosáhnout modifikací podmínky cyklu, ale tím můžeme znepřehlednit zdrojový kód. Možným řešením je použití příkazu break, který na libovolném místě ukončí právě vykonávaný cyklus. Pokud je tento cyklus zanořen v jiném cyklu, ukončí se jen ten vnitřní cyklus, vnější cyklus pokračuje dál. Na slajdu č. 35 nalevo vidíme for cyklus který v kulatých závorkách obsahuje jen dva středníky. Není to syntaktická chyba, skutečně libovolný z výrazů inicializace, podmínka a inkrement může být prázdný, dokonce všechny tři na jednou. Jedná se o nekonečný cyklus, protože neobsahuje podmínku (je prázdná). V těle cyklus se volá funkci fce a testuje její návratovou hodnotu. Pokud je její návratová hodnota nezáporná tak se vykoná funkce jinafce a pokračuje se další iterací cyklu. Pokud je návratová hodnota funkce fce záporná (příznak, že při jejím volání došlo k chybě), ukončíme cyklus příkazem break. 5.4 Ukončení jedné iterace continue Někdy nastane situace, že nepotřebujeme ukončit cyklus celý, ale pouze právě vykonávanou iteraci. Příkaz continue přeskočí všechny nevykonané příkazy v aktuální iteraci cyklu a pokud je stále splněna podmínka cyklu, zahájí iteraci následující. Na slajdu č. 35 napravo vidíme příklad použití příkazu continue. Pokud návratová hodnota funkce get, ukončujeme aktuální iteraci cyklu příkazem continue (přeskakujeme příkaz. kde bychom tou nulou vynásobili proměnou n, která se chová jako řídící proměnná cyklu). V další iteraci cyklu získáme další návratovou hodnotu funkce get. Prohlédněte si znovu příklad na slajdu č. 35 napravo a rozhodněte za jakých podmínek je cyklus nekonečný. Nápověda: zajímají nás návratové hodnoty funkce get. Pokud máte rozmyšlenou odpověď, můžete pokračovat dále ve čtení. Cyklus skončí ve chvíli, kdy hodnota proměnné n dosáhne hodnoty nejméně 1000. V každé iteraci cyklu se hodnota proměnné n násobí číslem, které je různé od nuly, tedy její hodnota se na první pohled stále pomalu zvětšuje. Pokud funkce get bude vracet pouze hodnoty jedna a nula, tak ovšem hodnota n neporoste a máme nekonečný cyklus. Další problém nastane pokud funkce get vrátí jedno záporné číslo. Hodnota proměnné n se stane zápornou a bude se dále zmenšovat (v absolutní hodnotě sice poroste). Poslední problém představuje přetečení hodnoty proměnné, což v případě násobení je velmi snadné, pokud funkce get vrátí velkou hodnotu. 6 Příkaz skoku goto Příkaz goto slouží ke skoku na libovolné místo v kódu funkce. Příkaz goto je v jazyku C zejména z historických důvodů. Dokáže nahradit podmínku či cyklus. Jeho použití pro tyto účely výrazně znepřehlední zdrojový kód. Není nutné, abychom znali jeho syntax nebo tento příkaz používali..
Na slajdu č. 36 vidíte jeden z mála příkladů oprávněného použití příkazu goto k vyskočení ze dvou vnořených for cyklů. Klíčové pojmy Proměnná (název, typ a hodnota; deklarace a inicializace; lokální a globální) Výraz a jeho hodnota Příkaz, složený příkaz, Podmíněný příkaz if, (kladná a else větev, vnořený podmíněný příkaz) Vícenásobné větvení switch (návěští case a default) Cyklus (nekonečný, iterace, tělo, podmínka) While cyklus For cyklus (podmínka, inicializace, inkrement, řídící proměnná) Ukončení cyklu break a continue Příkaz skoku goto 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. Jaký je rozdíl mezi globální a lokální proměnnou? Co je vhodnější používat? Lze deklarovat více proměnných stejného typu najednou v jedné deklaraci? Jakou hodnotou je inicializována globální proměnná (rozeberte všechny případy)? Jakou hodnotou je inicializována lokální proměnná (rozeberte všechny případy)? Můžeme mít deklarovanou lokální proměnou stejného jména, jako je jméno nějaké globální proměnné? Pokud je to možné, co se stane? Můžeme mít deklarovanou lokální proměnnou shodného jména, jako se jmenuje formální parametr funkce? Pokud je to možné, co se stane? Můžeme mít deklarovanou lokální proměnnou shodného jména, jako se jmenuje skutečný parametr funkce? Pokud je to možné, co se stane? Můžou dvě různé funkce obsahovat lokální proměnou stejného jména? Pokud je to možné, co se stane? Popište předávání parametrů funkce. Co je to výraz, jak se liší od příkazu. V jakých situacích můžeme za příkazem vynechat středník? Může výraz obsahovat operátor =? Jak syntakticky vytvoříme složený příkaz? K čemu slouží? Jak syntakticky vytvoříme podmíněný příkaz? Rozeberte obě dvě varianty. Jaké jsou kladeny požadavky na podmínku v podmíněném výrazu? Co je to vnořování podmíněných příkazů, jaký problém přináší a jak se tento problém řeší? K čemu složí vícenásobné větvení, jak ho syntakticky vytvořím?
Jaké jsou kladeny podmínky na výraz, podle jehož hodnoty se rozhodujeme v příkazu switch ? Je nutné používat příkaz break uvnitř switch příkazu ? Dá se v příkazu switch definovat návěští pro čísla z daného intervalu? V jakých situacích je vyžadováno použití kulatých závorek? (vyjmenujte alespoň 5 případů). Je nutné používat středník u posledního příkazu ve složením příkazu? Je nutné používat středník před else v podmíněném příkazu? Jaké jsou typy cyklů v jazyku C? Jakým způsobem bychom převedli obecný do-while cyklus na while cyklus ? Jakým způsobem bychom převedli obecný while cyklus na do-while cyklus ? Popište for cyklus, zaměřte se na obsah kulaté závorky. Jakým způsobem bychom převedli obecný for cyklus na while cyklus? Jakými příkazy můžeme přerušit cyklus? Jak se tyty příkazy od sebe liší? Co je to nekonečný cyklus, dá se nějak prakticky využít ? Jaké příkazy můžeme příkazem goto nahradit? Je vhodnější použit goto nebo zmíněné příkazy? 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 program malá násobilka (slaj č. 25) pomocí for cyklu. 2. Napište program, který pro daný měsíc (stačí jen do září) v roce zadaný číslem vypíše jeho název. 3. Napište program, který ze standardního vstupu čte tak dlouho text, dokud není zadáno písmeno X. 4. Napište program, který vygeneruje všechny textové řetězce délky X=3 z písmen T G C A. 5. Napište program, který pro dané číslo určí všechny jeho dělitele. 6. Napište program, který určí všechna prvočísla menší než dané číslo. 7. Program z (4) upravte pro obecné X. 8. Napište funkci, která vypíše na obrazovku hodnoty svých parametrů (typu celé číslo) setříděné vzestupně. Začněte se třemi parametry, obtížnost úlohy jde zvýšit čtyřmi nebo dokonce pěti parametry. 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 příkazy if, switch,. while, for. Miroslav Virius: Pasti a propasti jazyka C++ (Brno, 2. vydání 2005) Kapitola 1.1 Příkazy