Doposud platila rovnost - co úloha, to jeden vývojový diagram. Dnešním dílem počínaje tato rovnost platit nebude, protože se naučíme využívat tzv. podprogramy, rozvětvíme si tak program na další celky.
V předchozích dílech jsme probrali příkazy, podmínky a cykly. Z těchto částí se skládá každý program. Jak jsme postupovali dále a dále, tak algoritmy a potažmo vývojové diagramy měly tendenci býti složitější a složitější a dokonce i pro jednoduché úlohy začínala být jedna A4 málo (obrazně řečeno).
Jak tedy postupovat v případě, že chceme řešit složitější úlohu? Musíme provést analýzu a pokusit se dekomponovat problém na jednotlivé jednodušší celky. Za touto vznešené vyhlížející větou není nic jiného než to, co dělá každý z nás dnes a denně - složitější úlohy rozdělíme na menší, snáze řešitelné, a ve výsledku dosáhneme kýženého.
Třeba takový týdenní velký nákup v supermarketu. To je komplexní úloha jako hrom. Pokusit si ji naplánovat do nejmenších detailů je značně náročné. Při rozdělení této úlohy na její jednotlivé části, tj. v tomto případě: cesta před supermarket, získání košíku, boj o potraviny v akci atd., se můžeme soustředit na jednotlivé detaily dílčích kroků. Vytvořili bychom si tak vývojové diagramy pro jednotlivé kousky, které ještě potřebujeme nějakým vhodným způsobem spojit.
U každé jednotlivé části víme, kdy nastane resp. víme, která část jakou předchází, a která následuje - známé jejich pořadí. Víme například, že nemá cenu snažit se získat košík, když ještě nejsme před supermarketem. Části - jednotlivé vývojové diagramy - potřebujeme vhodným způsobem "vyvolávat" a posloupnost tohoto vyvolávání nějak zapsat.
Posloupnost vyvolávání je vlastně náš program, to co budeme my nebo počítač vykonávat. Této základní posloupnosti se říká hlavní program. Doposud jsme řešili úlohy, které jsme zapsali do jednoho vývojového diagramu, a to byl vždy hlavní program té dané úlohy. V tomto díle začneme řešení úloh dělit do několika části, a každou takovou část zakreslíme do vývojového diagramu, takže ve výsledku budeme mít jeden vývojový diagram pro hlavní program a několik vývojových diagramů pro dílčí části.
Jednotlivé části, ze kterých se skládá hlavní program, se nazývají podprogramy. Pokud takový podprogram chceme spustit, tak říkáme, že voláme podprogram. Volání nemusí probíhat jen z hlavního programu, ale i z jakéhokoliv podprogramu. V jednom programu jich může být více, takže si každý z nich pojmenujeme a do značky pro volání zapíše jeho jméno (volaného podprogramu). Volání (námi vytvořeného) podprogramu se zapisuje značkou, kterou vidíte na obrázku.
Princip volání podprogramu je znázorněn na předchozím obrázku. Při vstupu do značky pro volání se "skočí" na jeho vývojový diagram (startovací značka nese název podprogramu). Vykoná se tělo podprogramu a s ukončovací značkou (Návrat) se z podprogramu vrací zpět do místa, odkud byl zavolán. Řekněme, že výřez na obrázku zobrazuje volání podprogramu Test z hlavního programu. Při jeho zavolání hlavní program čeká až se dokončí a pokračuje až ve chvíli, kdy je podprogram ukončen.
Podprogramy musí nějak interagovat s programem, který ho volá, jinak by jejich yužítí bylo značně omezené. Musíme mít možnost předat jim nějaké informace a následně potřebujeme, aby podprogram mohl nějaké informace předávat zpět (vracet). Předání informací do podprogramu provedeme jeho zavolání s parametry.
Parametr - informace, kterou předáváme do podprogramu.
Parametr nemusí být žádný a může jich být více jak jeden. Jejich počet není nijak omezen. Jako parametr lze předávat konstantní hodnotu (např. 10) nebo hodnotu proměnné (např. X). Parametry předávané do podprogramu se zapisují do závorky za jméno volaného podprogramu a jsou odděleny čárkou. Jejich počet musí odpovídat počtu parametrů, které podprogram vyžaduje. Jejich seznam se zapisuje, stejně jako při volání podprogramu, za jeho jméno do startovací značky (opět do závorek a odděleny čárkou). Podprogram bez parametrů se volá jen a zapisuje jen jeho jménem.
Na obrázku vidíte příklad podprogramu, který má 3 parametry (X, Y, Z). Při jeho volání mu předáváme hodnoty proměnných A a B a konstantní honodtu 10. Hodnoty A a B mohou být samozřejmě při každém spuštění programu jiné (podle toho jak je zadá uživatel). Všechny předávané parametry musí mít v podprogramu svoje označení, které je nezávislé na programu, který podprogram volal. Parametry podprogramu se stávají jeho proměnnými a můžeme podle toho s nimi nákladat, tj. i měnit jejich hodnotu, tato se změna se projeví pouze v podprogramu.
Pokud chceme předat nějakou informaci zpět, tak musíme využít návratové hodnoty. Ta je pouze a jenom jedna a podprogram ji nemusí využít. Vracet opět můžeme hodnotu nějaké proměnné nebo konstantu. Zápis návratové hodnoty provedeme do koncové značky (opět do závorky). Zpracování návratové hodnoty se provede jejím přiřazením do nějaké proměnné v části programu, kde jsme podprogram volali. Na obrázku si můžete prohlédnout vývojový diagram pro výpočet součtu 2 čísel za pomoci podprogramu.
Nejprve načteme do proměnných K a L hodnoty od uživatele, ty mohou být různé a pro náš jeden konkrétní případ mohou být např. 10 a 15. Následně se provede volání podprogramu Součet s hodnotami těchto proměnných, tj. v našem příkladě se provede volání Součet(10, 15)
. Provádění programu přejde do podprogramu, kde se naplní parametry předanými hodnotami tj. v tomto konkrétním případě A bude 10 a B bude 15. Provede se součet čísel a výsledek se uloží do proměnné C (10 + 15 = 25
). Provádění programu se vrací zpět do hlavního programu s návratovou hodnotuo C (25). Navrácená hodnota se uloží do proměnné M (25) a následně se vypíše.
Návratová hodnota - informace, kterou předává (vrací) podprogram zpět.
Rozklad úlohy na podprogramy není nijak omezen. Dekomponovat tak lze ve více krocích (ve více úrovních), nejprve hrubé rozdělení na několik základních částí a pak dekompozice těchto částí na menší kousky atd. V ideálním případě dostaneme program rozdělený do několika částí, kde každý takový díl programu lze zakreslit vývojovým diagramem jehož velikost o moc nepřesahuje nejsložitější úlohy, které jsme se zde zatím řešili. Tento typ návrhu se označuje shora-dolů, protože začínáme na nejvyšší úrovni a postupně řešíme větší a větší detaily (více a více se ponořujeme do problému).
Jiným způsobem, jak se můžeme dobrat vyřešení složitější úlohy je řešit dílčí části a postupně je spojovat do větších a větších celků. Nakonec je spojíme do hlavního programu. Tento způsob se označuje zdola-nahoru. Rozdíl mezi oběma způsoby je tedy zřejmý, v prvním případě začínáme hlavním programem, kdežto v druhém případě hlavním programem končíme. Druhý rozdíl už tolik zřejmý není, při návrhu shora-dolů jsou většinou dílčí řešení poplatné dané úloze. U řešení zdola-nahoru není problém tak ostře ohraničen (řešíme úlohu, u které nemusí být známa omezení), takže řešení bude více univerzální a tudíž znovupoužitelné.
Pro začátek je jednodušší návrh shora-dolů, protože vyžaduje méně představivosti a analytických zkušeností. I proto budeme vždy používat tento způsob rozkladu úlohy, nejprve si vytvoříme hlavní program, kde budeme volat podprogramy a následně tyto podprogramy vytvoříme.
Dalším polem působnosti podprogramů jsou opakující se části kódu. Řekněme, že děláme program na komunikaci s nějakým zařízením. Přenos použitým protokolem je chráněn kontrolním součtem (CRC). Tento kontrolní součet musíme spočítat než odesleme výzvu do zařízení a dále ho musíme spočítat u příchozí odpovědi, abychom zkontrolovali správnost došlých dat. Výpočet CRC je shodný pro obě zprávy, takže je zbytečné mít ho v programu 2x, ale stačí z něj udělat podprogram, který budeme volat ze dvou míst. Šetříme tím nejenom velikost výsledného programu, ale opět se takový program mnohem lépe udržuje (případná úprava výpočtu CRC se provede jen na jedno místě).
A poslední neméně důležitou oblastí jsou knihovní funkce. Každý programovací jazyk nabízí knihovní funkce bez nichž by bylo programování mnohonásobně složitější. Například výpočet goniometrických funkcí (sin, cos atd.) se jistě dá vypočítat aproximačním výpočtem, ale rozhodně je pohodlnější zavolat přepřipravenou funkci (podprogram), který provede výpočet.
Jinak se ve všech případech se jedná o stejné podprogramy. Rozdíl je pouze v tom, kdo a za jakým účelem je vytváří. Knihovní funkce netvoříme my, ale tvůrce dané knihovny - my je jen využíváme. Ve vývojovém diagramu budeme takové volání odlišovat jako obyčejný příkaz. Podprogramy, co rozdělují úlohu na menší části nebo jsou vytvořeny z důvodu opakujícího se kódu, mohou lehce splynout a nijak je dále rozlišovat nebudeme.
V tuto chvíli nastal čas na řešený příklad s podprogramy. Uděláme si jeden jednoduchý příklad s mocninou. Jednak si na něm ukážeme, jak udělat návrh shora-dolů. Dále bude demonstrovat to, co je jsem napsal výše - podprogram zjednodušuje a zpřehledňuje program. A také si samozřejmě ukážeme, jak zapsat vývojové diagramy.
XN
Vytvoříme si příklad, ve kterém budeme počítat XN nebo-li N-tou mocninu čísla X. Zadání by bylo následující: vytvořte algoritmus pro výpočet N-té mocniny čísla X. Vstupem od uživatele tedy budou 2 čísla: X a N, výstupem algortimu bude vypsaný výsledek vypočítané mocniny.
Hlavní program bude podobně jednoduchý jako v případě ukázky součtu 2 čísel. Od uživatele načteme 2 čísla, která předáme podprogramu na výpočet mocniny a vrácenou hodnotu vypíšeme. Jak je vidět na obrázku, vývojový diagram je jednoduchý a velice srozumitelný. Neřešíme zde žádné detaily, ale jsou v něm pouze základní body. Nyní můžeme přistoupit k vytvoření vývojového diagramu pro podprogram na výpočet mocniny.
Výpočet mocniny např. 5 na 3
je v podstatě výpočet 5x5x5
, nebo-li N-krát provedeme vynásobení čísla X. To vede na cyklus s daným počtem opakování. Výsledný algoritmus by byl podobný například výpočtu faktoriálu, který už jsme dělali - inicializace proměnné, ve které by byl výsledek, na 1 a následné násobení (N-krát) této proměnné hodnotu X. Nakonec výsledek jako použít návratovou hodnotu.
Tak jednoduché by to bylo pouze v případě, že za N můžeme zadat nenulové kladné číslo. A co v případě, že je N nulové nebo doknce záporné? Do našeho podprogramu ošetříme i tyto vstupy tj. pro Z = 0 (N = 0) bude výsledek 1 a pro zápornou hodnotu Z (N) se provede výpočet 1 / YZ
. Přidáme tedy do našeho vývojové diagramu ještě 2 podmínky, nejprve ošetří stav, kdy je Z = 0. Vzhledem k incializaci výsledku A na 1 nemusíme při Z = 0 nic dalšího dělat a tuto hodnotu můžeme vrátit.
Druhou podmínkou zjistíme, jestli je Z kladné nebo záporné. Pokud je kladné, tak bude výpočet stejný. Pro Z záporné budeme provádět místo násobení dělení. A samozřejmě musíme upravit rozmezí cyklu (od 1 do -Z
), aby například při zadání čísla -5 byl cyklus od 1 do -(-5)
nebo-li od 1 do 5
. Vypočítanou hodnotu opět vracíme do hlavního programu.
Výsledný vývojový diagram pro podprogram si můžete prohlédnout na obrázku. Jak vidíte, při zapsání do jednoho vývojového diagramu, by tento byl nepřehledný. Použití podprogramu celé řešení zpřehlednilo a ještě to má jednu výhodu: můžeme ho s výhodou použít do nějakého dalšího programu, tj. nemusíme opakovaně řešit stejnou věc.
Následuje tabulka pro zadané hodnoty 5 a 3. Stejně jako v předchozím případě jsou do modra laděné řádky kroky v hlavním programu a řádky laděné do červena jsou kroky v podprogramu. Z výpisu je mimo jiné vidět, že v podprogramu strávil program více času než v hlavním programu, což přesně odpovídá složitosti hlavního programu a podprogramu.
Nakonec jedna poznámka pro zvýdavější. Algoritmus podprogramu by samozřejmě bylo možné zjednodušit. Stávající řešení bylo zvoleno z ukázkových důvodů. Jednou z možný úprav by bylo redukce na jednu podmínku - část pro výpočet kladné mocniny dokáže správně vypočítat i Z = 0 (cyklus se neprovede ani jednou a výsledek je 1). To samozřejmě není poslední možnost, ale na hledání optimálních řešení je v tuto chvíli času dost.
V příštím díle budeme s podprogramy pokračovat.