príručka pre jazyk - vysoké učení technické v brněhlavicka/vyuka/priruckac_kubran.pdf · 7...

117
Príručka pre jazyk Marek Kubran

Upload: others

Post on 11-Jul-2020

2 views

Category:

Documents


0 download

TRANSCRIPT

Príručka pre jazyk

Marek Kubran

Obsah

Úvod............................................................................................................................................................................... 5

I. lekcia: Komponenty programu v jazyku C ............................................................................................................ 7 Prvý C program .......................................................................................................................................................... 7 Programové komponenty ........................................................................................................................................... 8 Zhrnutie .................................................................................................................................................................... 10 Cvičenia.................................................................................................................................................................... 10

II. lekcia: Funkcie a práca s pamäťou...................................................................................................................... 11 Hlavička funkcie ...................................................................................................................................................... 11 Deklarácia a definícia funkcie .................................................................................................................................. 11 Procedúry a dátový typ void..................................................................................................................................... 13 Parametre funkcií ..................................................................................................................................................... 13 Definície premenných .............................................................................................................................................. 14 Cvičenia.................................................................................................................................................................... 14

III. lekcia: Formátovaný vstup/výstup z/na terminál.............................................................................................. 15 Formátovaný výstup na obrazovku .......................................................................................................................... 15 Formátovaný vstup z klávesnice .............................................................................................................................. 16 Cvičenia.................................................................................................................................................................... 17

IV. lekcia: Riadiace štruktúry – príkaz vetvenia..................................................................................................... 19 Príkaz if .................................................................................................................................................................... 19 Ternárny operátor ..................................................................................................................................................... 20 Cvičenia.................................................................................................................................................................... 21

V. lekcia: Booleovské výrazy ..................................................................................................................................... 22 Logické a relačné operátory v jazyku C ................................................................................................................... 23 Skrátené vyhodnocovanie logických výrazov .......................................................................................................... 24 Cvičenia.................................................................................................................................................................... 24 Tipy a triky............................................................................................................................................................... 24

VI. lekcia: Riadiace štruktúry – iteračné príkazy ................................................................................................... 25 Operátor porovnania................................................................................................................................................. 26 Cyklus s podmienkou na začiatku ............................................................................................................................ 26 Príkazy break a continue .......................................................................................................................................... 27 Cvičenia.................................................................................................................................................................... 27

VII. lekcia: Riadiace štruktúry – iteračné príkazy.................................................................................................. 28 Cyklus s podmienkou na konci................................................................................................................................. 30 Cvičenia.................................................................................................................................................................... 31

VIII. lekcia: Riadiace štruktúry – iteračné príkazy ................................................................................................ 32 Typové modifikátory................................................................................................................................................ 33 Cyklus for................................................................................................................................................................. 33 Cvičenia.................................................................................................................................................................... 35

IX. lekcia: Iterácia pomocou rekurzie ...................................................................................................................... 36 Cvičenia.................................................................................................................................................................... 37

X. lekcia: Typová konverzia ...................................................................................................................................... 38 Cvičenia.................................................................................................................................................................... 39

XI. lekcia: Preprocesor jazyka C .............................................................................................................................. 40 Preprocesor jazyka C................................................................................................................................................ 41 Makrá bez parametrov.............................................................................................................................................. 42 Makrá s parametrami................................................................................................................................................ 42 Zrušenie definície makra .......................................................................................................................................... 43 Podmienený preklad riadený definíciou makra ........................................................................................................ 43 Cvičenia.................................................................................................................................................................... 44 Tipy a triky............................................................................................................................................................... 45

XII. lekcia: Unárne operátory ++ a ––; Reťazce ako jednorozmerné polia ......................................................... 46 Unárne operátory ++ a –– ........................................................................................................................................ 47 Jednorozmerné polia ................................................................................................................................................ 47 Reťazce..................................................................................................................................................................... 49

Práca s reťazcom .................................................................................................................................................. 49 Cvičenia.................................................................................................................................................................... 50

XIII. lekcia: Smerníky a volanie odkazom............................................................................................................... 51 Smerníky .................................................................................................................................................................. 52 Smerníky a jednoduché premenné............................................................................................................................ 53 Smerníky a typy premenných................................................................................................................................... 54 Smerníky a funkcie – volanie odkazom ................................................................................................................... 55 Tipy a triky............................................................................................................................................................... 58 Cvičenia.................................................................................................................................................................... 58

XIV. lekcia: Smerníky a polia, pointerová aritmetika, pole reťazcov ................................................................... 59 Smerníky a polia....................................................................................................................................................... 60 Pointerová aritmetika ............................................................................................................................................... 61 Vzťah medzi smerníkmi a poliami ........................................................................................................................... 61 Pole reťazcov............................................................................................................................................................ 62 Cvičenia.................................................................................................................................................................... 63

XV. lekcia: Parametre funkcie main() a príkaz switch ........................................................................................... 65 Parametre funkcie main() ......................................................................................................................................... 69 Príkaz switch ............................................................................................................................................................ 70 Cvičenia.................................................................................................................................................................... 72 Tipy a triky............................................................................................................................................................... 73

XVI. lekcia: Definícia nového typu, vymenovaný typ, operátor čiarky................................................................. 74 Definícia nového typu .............................................................................................................................................. 79 Vymenovaný typ ...................................................................................................................................................... 80 Operátor čiarky......................................................................................................................................................... 81 Cvičenia.................................................................................................................................................................... 82

XVII. lekcia: Práca so súbormi, štandardný vstup/výstup ..................................................................................... 84 Práca so súbormi ...................................................................................................................................................... 88 Otvorenie súboru ...................................................................................................................................................... 89 Uzavretie súboru ...................................................................................................................................................... 89

Štandardný vstup a výstup........................................................................................................................................ 90 Základné operácie s otvoreným súborom................................................................................................................. 90 Presmerovanie štandardného vstupu a výstupu ........................................................................................................ 93 Cvičenia.................................................................................................................................................................... 93

XVIII. lekcia: Zostavenie programu kalkulátor – I. časť ....................................................................................... 95 Funkcia exit ............................................................................................................................................................ 101 Syntaktický analyzátor s vyhodnocovaním výrazov .............................................................................................. 102

Analýza funkcie vyraz() ..................................................................................................................................... 103 Analýza funkcie term()....................................................................................................................................... 104 Analýza funkcie faktor() .................................................................................................................................... 104

Zhrnutie: ................................................................................................................................................................. 104 Cvičenia.................................................................................................................................................................. 105

XIX. lekcia: Zostavenie programu kalkulátor – II. časť ...................................................................................... 106 Doplnenie funkcie vyraz() o interpretáciu unárnych operátorov............................................................................ 109 Rozšírenie funkcie faktor() o syntaktickú analýzu a vyhodnotenie funkcií MAX a MIN...................................... 109 Cvičenia.................................................................................................................................................................. 110

Príloha ....................................................................................................................................................................... 114

Literatúra.................................................................................................................................................................. 115

Register...................................................................................................................................................................... 116

5

Úvod Príručka, ktorú práve držíte v ruke, je súčasťou záverečného projektu, ktorého cieľom bolo vytvoriť textovú

príručku pre cvičenie programovania v jazyku C. Hlavným obsahom príručky má byť postupnosť programovacích príkladov, ktorá je konzistentná, didakticky optimalizovaná a má modulárny charakter.

Príručka je teda venovaná študentom prvého ročníka FEI STU, ktorí majú zapísaný predmet Programovanie v C. Môže však poslúžiť aj ostatným čitateľom, ktorí nemajú skúsenosti z programovacieho jazyka C. Príručka nie je učebnicou programovania, ale učebnicou jazyka C, pretože sa v nej oboznamujete s príkazmi, funkciami a konštruk-ciami programovacieho jazyka C.

Cieľom projektu nebolo vytvoriť ďalšiu príručku k stovkám existujúcich ani vyčerpávajúcu encyklopédiu, ale skôr učebnicu, podľa ktorej by mohlo prebiehať cvičenie na princípe stavebnice LEGO a krátkych príkladov. V jednotlivých lekciách sa vytvoria jednoduché funkcie, na ktorých sa oboznámite so základnými prvkami a kon-štrukciami jazyka C, a tie potom poskladáte do jedného väčšieho programu.

Nosnou témou príručky je program kalkulátor, ktorý pozostáva z konzistentnej postupnosti programovacích príkladov. Celé vysvetľovanie problematiky jazyka C sa orientuje na túto tému. Cieľom preštudovania príručky nie je len získanie a osvojenie si nových vedomostí z jazyka C, ale aj vybudovanie programu kalkulátor. Na jeho vývoji sa budete priamo zúčastňovať aj vy, a to formou riešenia úloh zadaných v cvičeniach na konci každej lekcie. Okrem problematiky jazyka C sa v príručke dozviete čiastočne aj o prekladačoch, pretože kalkulátor bude v sebe imple-mentovať aj jednoduchý lexikálny a syntaktický analyzátor. Tým, že celá príručka je monotematicky ladená, t.j. nesie sa v duchu vytvorenia jediného uceleného programu, je to prvá publikácia svojho druhu zameraná na výučbu jazyka C.

Príručka je rozdelená do 19 lekcií, ktoré nasledujú za sebou v postupnosti, v akej sa program kalkulátor imple-mentuje. Na rozdiel od bežných učebníc jazyka C sa hneď na začiatku (II. lekcia) zoznámite s funkciami, ktoré sú základným stavebným kameňom jazyka C, a ktorých poznanie vám dovolí od počiatku vytvárať program kalkulátor ako program skladajúci sa z postupnosti programovacích príkladov.

V ďalších lekciách sa oboznámite s riadiacimi konštrukciami jazyka C, ako sú napr. príkaz vetvenia a s ním súvisiace Booleovské výrazy, iteračné príkazy, t.j. cykly a rekurzívne funkcie. Po zoznámení sa s riadiacimi príkazmi jazyka C sa naučíte používať zložitejšie údajové štruktúry, ako sú napr. polia alebo reťazce, a pokročilejšie techniky programovania, ako sú napr. smerníky a ich využitie pri volaní odkazom, vzťah medzi smerníkmi a poliami, direktí-vy preprocesora a pod. Pri vytváraní lexikálneho a syntaktického analyzátora s vyhodnocovaním matematických výrazov sa oboznámite s vymenovaným typom v jazyku C, ktorý vám umožní reprezentovať matematické výrazy pomocou symbolov. Nakoniec sa naučíte pracovať aj so súbormi, ktoré poslúžia používateľovi programu kalkulátor na to, aby mohol zadávať vstupné matematické výrazy priamo zo súboru.

Samotný plne funkčný program zostavíte až v posledných dvoch lekciách. Dovtedy budete vytvárať väčšinou univerzálne použiteľné funkcie, ktoré sa dajú použiť aj v inom programe ako je kalkulátor. Cieľový program kalkulátor by mal byť pre vás veľkou motiváciou, pretože bude pre mnohých z vás prvým väčším programom, ktorý bude zmysluplný a reálne použiteľný. Kalkulátor by mal vedieť počítať aj také matematické výrazy, ktoré obsahujú neštandardné funkcie, ako sú napr. najväčší spoločný deliteľ, funkcie na zistenie väčšieho/menšieho čísla z dvoch argumentov, rôzne typy zaokrúhľovania, …

Všetky lekcie majú nasledujúcu štruktúru: • Názov lekcie • Úvod • Výpis programu • Vysvetlivky • Text • Tipy a triky • Cvičenia

Úvod vás oboznamuje s tým, čo sa v lekcii naučíte z problematiky jazyka C, a čo bude výstupom lekcie pre program kalkulátor.

6

Po krátkom úvode nasleduje výpis programu, na ktorom sa vysvetľuje jazyk C. Riadky programov uvedených vo výpisoch sú očíslované kvôli identifikácii vysvetľovaných častí programu. Čísla riadkov zodpovedajú fyzickým číslam riadkov zdrojových kódov programov.

Vo vysvetlivkách sú uvedené nové výrazy/slovíčka nachádzajúce sa vo výpise programu. Vysvetlivky majú charakter slovníka a sú veľmi dôležitou súčasťou lekcie, pretože sa nimi zavádzajú nové pojmy, t.j. oboznamujú vás s tým, ako sa čo nazýva.

Po vysvetlivkách nasleduje samotný text, v ktorom sa vysvetľujú nové pojmy, vysvetľuje sa problematika jazyka C a zároveň problematika funkcií kalkulátora. Časť tipy a triky nie je súčasťou každej lekcie a niekedy sa nachádza až za cvičeniami. V cvičeniach sú zadané úlohy, ktoré máte riešiť. Cieľom úloh je precvičenie a osvojenie si novonadobudnutých vedomostí z problematiky jazyka C. Väčšina úloh nadväzuje na seba, tzn. ak budete riešiť všetky úlohy v uvedenej postupnosti, v poslednej lekcii by ste mali dospieť ku konečnému výsledku, ktorým je cie-ľový program kalkulátor.

Za poslednou lekciou sa nachádza príloha so zoznamom neprebraných alebo len čiastočne vysvetlených tém z jazyka C, ktoré zostávajú na samoštúdium. V závere príručky nájdete register, v ktorom sú uvedené všetky kľúčové slová a odkazy na strany, kde sa príslušné slovo nachádza. Register vám umožní rýchlo vyhľadať informácie vysvet-ľujúce dané kľúčové slovo, funkciu alebo príkaz.

Príručka má celkovo 117 strán, pričom značnú časť zaberajú výpisy zdrojových textov programov, na ktorých sa vysvetľuje preberaná problematika. Príručka je ilustrovaná 25 inštruktívnymi obrázkami, obsahuje niekoľko tabuliek, asi desiatku tipov a trikov, ktoré je dobré vedieť, a po ktorých osvojení si získate prinajmenšom zručnosť pri vytvára-ní programov. Príručka obsahuje celkovo 105 úloh, z ktorých 94 je vyriešených. Riešené úlohy sa nachádzajú v adre-sári CVICENIA\, ktorý je súčasťou elektronického média dodávaného k tejto príručke. Disketa taktiež obsahuje adresár VYKLAD\, v ktorom sa nachádzajú zdrojové texty všetkých programov uvedených vo výpisoch príručky.

Pre štúdium príručky predpokladáme u čitateľa nižšie uvedené znalosti. V príručke nie sú uvedené kvôli tomu, že ide zväčša o referenčné informácie, ktoré môžete získať z ľubovoľnej dostupnej literatúry k jazyku C alebo z ma-nuálov k prekladaču jazyka C.

- spôsob tvorby programu: Editor à Preprocesor à Prekladač à Linker à Debugger - čo je ASCII tabuľka - zápis identifikátorov a kľúčové slová (napr. identifikátor musí začínať písmenom a je ”case sensitive“,

identifikátorom nemôže byť kľúčové slovo, …) - zápis konštánt (pre celé čísla aj v rôznych sústavách) a literálov - údajové typy v jazyku C a ich rozsahy

7

I. lekcia: Komponenty programu v jazyku C Každý program v jazyku C sa skladá z niekoľkých komponentov, kombinovaných určitým spôsobom. Väčšia

časť tejto príručky je venovaná vysvetľovaniu rôznych programových častí a ako ich máme používať. Na celkovú predstavu by sme mali začať kompletným (hoci malým) programom napísaným v jazyku C s identifikáciou jednotlivých častí. V tejto lekcii sa naučíme:

• o jednotlivých častiach programu v jazyku C • o význame jednotlivých programových častí

Prvý C program Dole uvedený výpis programu predstavuje zdrojový kód programu P01.CPP. Program načíta z klávesnice dve

čísla, zistí a vypíše maximum z obidvoch čísel. V tomto štádiu sa neznepokojujte tým, ako program pracuje a čo jednotlivé príkazy znamenajú. Ide tu o pochopenie jednotlivých častí (komponentov) programu, aby sme lepšie poro-zumeli výpisom programov v ďalších lekciách tejto príručky.

Predtým, než začneme rozoberať jednotlivé časti, musíme vedieť, čo je to funkcia, pretože funkcie sú základnou stavebnicou programovania v jazyku C. Funkcia je nezávislá časť programového kódu, ktorá vykonáva určitú úlohu, a ktorá má pridelené meno. Použitím mena funkcie v programe sa vykoná kód tela funkcie. Program môže tiež posielať funkcii informácie, nazývané argumenty, a funkcia môže vrátiť informáciu časti programu, odkiaľ bola volaná. V jazyku C existujú dva typy funkcií: knižničné funkcie, ktoré sú súčasťou prekladača jazyka C a užívateľom definované funkcie (užívateľské funkcie), ktoré programátor vytvára. V tejto príručke sa naučíte o obidvoch typoch funkcií. Poznámka: Čísla riadkov vo výpisoch programov nie sú súčasťou zdrojového kódu programu. V programe sú uvádzané iba kvôli identifikácii, takže v zdrojových programoch ich nepíšte!

Výpis programu P01.CPP 1: #include <stdio.h> 2: 3: int c; 4: 5: int max(int x, int y); 6: 7: main() 8: { 9: int a, b; 10: 11: /* nacitanie prveho cisla */ 12: printf("Zadaj 1. cislo: "); 13: scanf("%d", &a); 14: 15: /* nacitanie druheho cisla */ 16: printf("Zadaj 2. cislo: "); 17: scanf("%d", &b); 18: 19: /* najdenie vacsieho cisla a zobrazenie vysledku */ 20: c = max(a, b); 21: printf("Maximum z %d a %d je %d\n", a, b, c); 22: 23: return (0); 24: } 25: 26: /* Funkcia vrati maximum z dvoch cisel - argumentov funkcie */ 27: int max(int x, int y) 28: { 29: return (x > y ? x : y); 30: }

8

Programové komponenty Nasledujúce odseky opisujú rôzne časti predchádzajúceho ukážkového programu. Riadky sú očíslované, takže

môžete jednoducho identifikovať programové časti, ktoré sa práve rozoberajú. Funkcia main() (riadky 7 až 24)

Jediná časť, ktorá je povinná v každom C programe, je funkcia main(). V najjednoduchšej forme funkcia main() pozostáva z mena main nasledovaného párom prázdnych zátvoriek (()) a párom zložených zátvoriek ({}). Vo vnútri zložených zátvoriek sú príkazy, ktoré vytvárajú telo programu. Za normálnych okolností, vykonávanie programu začína na prvom príkaze vo funkcii main() a končí posledným príkazom v main(). Direktíva #include (riadok 1)

Direktíva #include nariaďuje prekladaču C, aby vložil obsah include súboru, uvedeného bezprostredne za týmto príkazom, do programu v čase prekladu. Include súbor je samostatný súbor, ktorý obsahuje informácie potrebné pre náš program alebo prekladač. Väčšina týchto súborov (niekedy nazývaných aj hlavičkové súbory) je súčasťou prekladača. Obsah týchto súborov nie je potrebné nikdy meniť a preto sú udržiavané separátne od našich zdrojových kódov. Všetky vkladané súbory by mali mať príponu .H (napr. STDIO.H). V našom ukážkovom programe direktíva #include nariaďuje prekladaču „vlož obsah súboru STDIO.H do programu P01.CPP na miesto, kde sa nachádza direktíva #include“. Väčšina C programov vkladá jeden alebo viac hlavičkových súborov. Viac informácií o vkladaní súborov nájdete v lekcii XI, „Preprocesor jazyka C“. Definícia premennej (riadky 3 a 9)

Premenná je meno, ktoré je priradené určitej pamäťovej adrese. Programy používajú premenné na ukladanie rozličných druhov dát počas behu programu. V jazyku C musí byť premenná definovaná pred jej prvým použitím! Definícia premennej informuje prekladač o mene premennej a type dát, ktorý táto premenná uchováva. V našom ukážkovom programe definície premenných v riadkoch 3 a 9: int c; int a, b; definujú tri premenné pomenované c, a, b, z ktorých každá uchováva celočíselnú hodnotu. Viac informácií o premenných a ich definovaní získate v lekcii II „Funkcie a práca s pamäťou“. Funkčný prototyp (riadok 5)

Funkčný prototyp poskytuje prekladaču informácie (meno a argumenty) o funkcii, ktorú program obsahuje. Funkčný prototyp musí byť definovaný ešte predtým, než je funkcia prvýkrát použitá, pokiaľ ešte nebola definovaná. Prototyp funkcie je odlišný od definície funkcie, ktorá obsahuje konkrétne príkazy, ktoré tvoria funkciu. (Definícia funkcií je preberaná v ďalšej lekcii tejto príručky.) Príkazy programu (riadky 12, 13, 16, 17, 20, 21, 23 a 29)

Skutočná práca programu v jazyku C je určená jeho príkazmi. C príkazy zobrazujú informácie na obrazovke, čítajú vstup z klávesnice, vykonávajú matematické operácie, volajú funkcie, čítajú súbory z disku a realizujú všetky ostatné operácie, ktoré potrebuje program vykonať. Veľká časť tejto príručky je venovaná práve príkazom jazyka C. Nateraz je pre nás podstatné iba to, že v zdrojových kódoch programov je každý príkaz zapísaný na samostatnom riadku a končí sa bodkočiarkou (;). Príkazy programu P01.CPP sú v krátkosti vysvetlené v nasledovných odstavcoch. printf()

Príkaz printf() (riadky 12, 16 a 21) je knižničná funkcia, ktorá zobrazuje informáciu na obrazovke. printf() môže zobraziť jednoduchú textovú správu (ako napr. v riadku 12 a 16) alebo správu s hodnotou jednej alebo viacerých premenných (ako v riadku 21). scanf()

Príkaz scanf() (riadky 13 a 17) je iná knižničná funkcia, ktorá číta údaje z klávesnice a priradí tieto údaje jednej alebo viacerým programovým premenným. Príkaz programu v riadku 20 volá funkciu pomenovanú max. Inými slovami, vykoná príkazy, ktoré obsahuje funkcia max(). Zároveň tento príkaz pošle funkcii argumenty a a b. Po dokončení vykonávania príkazov vo funkcii max(), funkcia max() vráti hodnotu programu (riadenie sa vráti späť na miesto, odkiaľ bola funkcia volaná) a uloží ju do premennej c. return

Riadky 23 a 29 obsahujú príkaz return. V príkaze return (riadok 29), ktorý je súčasťou funkcie max(), je zapísaný výraz, ktorý zistí väčšiu hodnotu z dvoch premenných x a y a výsledok vráti na miesto volania funkcie max(). Príkaz return v riadku 23 vráti nulu operačnému systému, tesne pred ukončením vykonávania programu.

9

Definícia funkcie (riadky 27 až 30) Funkcia je nezávislá časť programu, ktorá obsahuje vlastný kód napísaný tak, aby vykonával určitú úlohu.

Každá funkcia má meno a kód. Kód, ktorý funkcia obsahuje, sa vykoná zapísaním mena funkcie v príkaze programu. Toto sa nazýva volanie funkcie.

Funkcia max() (riadky 27 až 29), je užívateľom definovaná funkcia. Ako už názov napovedá, užívateľom definované funkcie sú napísané programátorom počas vývoja programu. Funkcia max() je jednoduchá a všetko čo robí, je, že porovná dve hodnoty (x a y) a vráti väčšiu z nich programu, ktorý funkciu zavolal. (Príkaz podmienka ? výraz1 : výraz2 vyhodnotí výraz1 v prípade platnosti podmienky, ináč vyhodnotí výraz2). V nasledujúcej lekcii „Funkcie a práca s pamäťou“ sa naučíme správnemu používaniu funkcií.

Poznamenajme, že v reálnych programoch by sme asi nepoužili funkciu s takou jednoduchou úlohou, ako je vrátenie väčšej hodnoty z dvoch vstupných hodnôt. V našom ukážkovom programe sme tak spravili z demonštrač-ných dôvodov.

C-čko obsahuje taktiež knižničné funkcie, ktoré sú súčasťou balíka prekladača jazyka C. Knižničné funkcie vykonávajú väčšinou bežné úlohy (ako je vstup/výstup na obrazovku, z klávesnice a disku), ktoré programy zväčša potrebujú. V našom príklade sú takými funkciami printf() a scanf(), ktoré sú obsiahnuté v knižnici STDIO (STandarD Input/Output). Komentáre (riadky 11, 15, 19 a 26)

Ľubovoľná časť programu, ktorá začína dvojicou znakov /* a končí */, sa nazýva komentár. Prekladač všetky komentáre ignoruje, takže komentáre nemajú žiadny vplyv na vykonateľný program. Do komentára môžeme zapísať hocičo a pritom spôsob práce programu sa nezmení. Komentáre slúžia najmä pre programátorov, pretože sprehľad-ňujú niekedy na prvý pohľad nepochopiteľný program. Komentáre síce robia zdrojové kódy programov väčšími, ale obyčajne tvoria iba malý podiel.

Komentáre by ste mali do programu písať už pri vytváraní zdrojového súboru a nie až po odladení („až na to niekedy zostane čas“).

Komentár môže obsiahnuť časť riadku, celý riadok alebo niekoľko riadkov. ANSI norma jazyka C nedovoľuje používať vnorené komentáre (nested comments), napr.:

/* toto je komentár /* a toto je vnorený komentár */ */.

Väčšina začínajúcich programátorov považuje komentáre za neužitočnú a čas plytvajúcu vec. Toto je omyl! Funkcia vášho programu môže byť vcelku jasná, kým program píšete, alebo píšete veľmi jednoduchý program. Ale akonáhle začína byť program väčší a zložitejší, alebo keď potrebujete modifikovať program, ktorý ste napísali pred šiestimi mesiacmi, zistíte, že komentáre sú neoceniteľnou súčasťou programu.

Komentujte váš zdrojový program, špeciálne príkazy alebo funkcie, ktoré by mohli byť nejasné vám alebo niekomu inému, kto bude modifikovať program neskôr. Uvádzajte komentár pred každou logicky ucelenou časťou kódu. Nekomentujte úplne jasné a samozrejmé príkazy, pretože prílišným komentovaním zneprehľadňujete program, čo sa týka čitateľnosti. Napr.: i = i + 1; /* zvysenie hodnoty premennej i o jedna */ Komentár má obsahovať iba užitočnú informáciu!

Zložené zátvorky (riadky 8, 24, 28 a 30) Zložené zátvorky ({}) používame na uzavretie riadkov, ktoré vytvárajú každú funkciu v jazyku C – vrátane

funkcie main(). Skupina jedného alebo viacerých príkazov uzavretých v zložených zátvorkách sa nazýva blok. Blok môže byť použitý vo väčšine prípadov tam, kde sa používa príkaz samostatne. Ako uvidíme v ďalších lekciách, jazyk C používa bloky na rôzne účely.

10

Zhrnutie Táto lekcia bola krátka, ale dosť dôležitá, pretože vás uviedla do problematiky komponentov programu v jazyku

C. Naučili ste sa, že jediná povinná časť každého C programu je funkcia main(). Tiež ste sa naučili, že skutočné vykonávanie programu je realizované pomocou príkazov, ktoré nariaďujú počítaču, čo má vykonať. Táto lekcia vám ďalej vysvetlila pojmy premenná a definícia premennej, a ukázala vám, ako používať komentáre vo vašich zdrojových kódoch.

Okrem funkcie main() môže C program používať dva typy funkcií: knižničné funkcie, dodávané ako súčasť balíka k prekladaču, a užívateľské (resp. užívateľom definované) funkcie, vytvorené programátorom.

Cvičenia 1. Napíšte čo najmenší program.

Riešenie: program c01-1.cpp

2. Napíšte príklad komentára. Riešenie: súbor c01-2.cpp

3. Uvažujte nasledovný program: 1: /* c01-3.cpp */ 2: #include <stdio.h> 3: 4: void zobraz_ascii(void); 5: 6: main() 7: { 8: printf("ASCII tabulka viditelnych znakov (33 - 127)\n"); 9: zobraz_ascii(); 10: 11: return (0); 12: } 13: 14: /* vypise tabulku znakov */ 15: void zobraz_ascii(void) 16: { 17: int c; 18: 19: for (c = 33; c < 128; c++) 20: printf("%d\t%X\t%c\n", c, c, c); 21: } 22: /* koniec programu */

a. Ktoré riadky obsahujú príkazy? b. Ktoré riadky obsahujú definície premenných? c. Ktoré riadky obsahujú funkčné prototypy? d. Ktoré riadky obsahujú definície funkcií? e. Ktoré riadky obsahujú komentáre?

Riešenie: súbor c01-3.txt

4. Čo vykonáva program z predchádzajúceho cvičenia? (preložte a spustite ho) Riešenie: súbor c01-4.txt

11

II. lekcia: Funkcie a práca s pamäťou V tejto lekcii sa zoznámime s funkciami ako so základným staveným kameňom každého programu v jazyku C.

Naučíte sa, čo sú to funkcie, argumenty resp. parametre funkcie a ako sa do funkcie prenášajú, čo je to návratová hodnota funkcie a akým spôsobom sa odovzdáva volajúcej funkcii, čo je to hlavička funkcie a funkčný prototyp. Ďalej sa naučíte, ako sa funkcie deklarujú a definujú, aké problémy môžu nastať pri definícii funkcie a ako sú v jazy-ku C implementované procedúry. Nakoniec sa oboznámite s pojmami globálna, lokálna a lokálna statická premenná, naučíte sa o viditeľnosti a rozsahu platnosti premennej. Lekcia je teda venovaná funkciám a všetkému, čo s nimi súvisí. V tejto lekcii nie je uvedený výpis programu, pretože celá problematika sa demonštruje na výpise programu P01.CPP z predchádzajúcej lekcie.

Jazyk C je založený na funkciách a teda každý program obsahuje jednu alebo viac funkcií. Ako už vieme, každý program v C musí obsahovať minimálne jednu funkciu, a tou je main(). Spracovávanie programu začína volaním funkcie main() a zväčša aj končí opustením tejto funkcie.

Funkcia je nezávislá časť programového kódu, ktorá vykonáva určitú úlohu, a ktorá má pridelené meno. Použitím mena funkcie v programe sa vykoná kód tela funkcie. Program môže tiež posielať funkcii informácie, nazývané argumenty alebo parametre, a funkcia môže vrátiť informáciu, nazývanú návratová hodnota, časti programu, odkiaľ bola funkcia volaná. Každá funkcia má štandardne iba jeden výstup (návratová hodnota), no môže vrátiť i viacero hodnôt – pomocou smerníkov resp. globálnych premenných (viď. lekciu XIII – „Smerníky a volanie odkazom“).

Funkcie v jazyku C nemôžu byť vnorené, t.j. jedna funkcia nemôže obsahovať v svojom tele definíciu druhej funkcie. Z toho vyplýva, že formálne parametre a lokálne premenné sú prístupné iba vo funkcii, v ktorej boli definované, a sú skryté zvonka tejto funkcie. Všetky funkcie v C vracajú hodnotu, no dajú sa použiť aj ako procedúry – možnosti viď. ďalej „Procedúry a dátový typ void“.

Hlavička funkcie Hlavička funkcie poskytuje interfejs pre funkciu. Hlavička funkcie alebo interfejs je špecifikácia, ktorá vytvára

spoločné rozhranie pre komunikáciu medzi funkciou a volajúcim programom.

Obr. 2-1 Hlavička funkcie vytvára rozhranie medzi funkciou a volajúcim programom.

Hlavička funkcie informuje prekladač, aké údaje (počet a typ parametrov) funkcia prijíma z volajúceho programu a aký údaj (typ návratovej hodnoty) funkcia vráti volajúcemu programu. Hlavička funkcie nie je ukončená bodkočiarkou ‘;’.

Hlavička funkcie má tvar: typ_funkcie meno_funkcie(deklarácia_parametrov), kde typ_funkcie určuje typ návratovej hodnoty a deklarácia parametrov je zoznam dvojíc typ parameter, oddelený čiarkami. Parametre uvedené v zozname parametrov nazývame formálne parametre a pri volaní funkcie sú nahradené skutočnými hodnotami nazývanými aktuálne parametre. Napr. funkcia max() z príkladu P01.CPP má formálne parametre x a y (obidva sú typu int), a pri volaní funkcie v tele funkcie main() sú nahradené aktuálnymi parametrami a a b. Hlavička tejto funkcie je: int max(int x, int y)

Deklarácia a definícia funkcie Definícia funkcie určuje ako hlavičku, tak aj jej telo, zatiaľ čo deklarácia funkcie špecifikuje iba hlavičku

funkcie, t.j. meno funkcie, typ návratovej hodnoty a prípadne i typ a počet jej parametrov. Pokiaľ typ funkcie (typ návratovej hodnoty funkcie) neuvedieme, implicitne je funkcia typu int, napr. funkcia main() programu P01.CPP.

parametre

návratová hodnota

Volajúci program

Hlavička funkcie

(interfejs)

Telo funkcie

12

Keďže každá funkcia vracia nejakú hodnotu vrátane funkcie main() – má táto funkcia na poslednom riadku (riadok 23) príkaz return číslo; (funkcia je implicitne typu int). Podobne hlavička funkcie max() môže mať tvar:

max(int x, int y) Telo funkcie je uzavreté do zložených zátvoriek „{“ a „}“, a môže obsahovať príkazy aj definície premenných.

Návratová hodnota sa volajúcej funkcii odovzdáva pomocou príkazu: return (vyraz); alebo return vyraz;

ktorý vypočíta hodnotu výrazu vyraz, vráti ju volajúcej funkcii a túto funkciu ukončí. Príkaz return môžeme špecifikovať nasledovne: Ak vykonávanie programu príde na príkaz return, ukončí sa

vykonávanie funkcie, ktorá tento príkaz obsahuje. Vo funkcii main() príkaz return ukončí celý program. Ak sa po-mocou return vracia nejaká hodnota, jej typ záleží od typu funkcie (pozri hlavičku funkcie).

Funkcia, ktorá nemá žiadne parametre, musí byť definovaná aj volaná včítane oboch okrúhlych zátvoriek! Ako už vieme, funkcia nemôže byť definovaná vo vnútri inej funkcie. Samozrejme, že funkcia môže byť vo

vnútri inej funkcie volaná. Problém môže nastať, keď je funkcia definovaná až za definíciou funkcie, ktorá túto funkciu volá. Volajúca funkcia v tomto prípade nemá doposiaľ žiadne informácie o volanej funkcii (o návratovej hodnote, počte a typoch parametrov). Teda prekladaču je nutné oznámiť aspoň návratový typ a meno volanej funkcie pred jej volaním (počet a typ parametrov volanej funkcie nie je dôležitý). Pre umožnenie kontroly počtu a typov skutočných parametrov volanej funkcie je potrebné zadeklarovať volanú funkciu pomocou funkčného prototypu ešte pred samotným volaním funkcie.

Funkčný prototyp poskytuje prekladaču informácie (meno a argumenty) o funkcii, ktorú program obsahuje. Funkčný prototyp musí byť definovaný ešte predtým, než je funkcia prvýkrát volaná, pokiaľ ešte nie je zadefinovaná. Typický problém s umiestnením definície funkcie vyvstáva, keď potrebujeme vo funkcii a() volať funkciu b(), ktorá rekurzívne (tzn. opäť) volá funkciu a(). Ako uvidíme neskôr, v našom programe kalkulátor sa tento problém vyskytne pri definícii funkcie faktor(), ktorá rekurzívne volá funkciu vyraz(). Funkcia vyraz() volá funkciu term() a funkcia term() zase volá funkciu faktor(). Situácia je znázornená na nasledujúcom obrázku:

Obr. 2-2 Rekurzívne volanie funkcie vyraz() z funkcie faktor().

Ak sa rozhodneme napísať definície týchto troch funkcií v postupnosti ich volaní, narazíme na problém definovania funkcie faktor(). Pripomíname, že funkciu môžeme volať až po jej definícii. Postupnosť definovania funkcií by bola nasledovná. Funkcia main() volá funkciu vyraz(), teda funkciu vyraz() musíme zadefinovať ešte pred funkciou main(). Keďže funkcia vyraz() používa funkciu term(), definíciu funkcie term() umiestnime pred definíciu funkcie vyraz(). Vo vnútri funkcie term() sa však volá funkcia faktor(), a teda definícia funkcie faktor() musí byť umiestnená v programe ešte pred definíciou funkcie term(). Funkcia faktor() ale volá funkciu vyraz(), ktorá je definovaná v zdrojovom súbore až za ňou!

Na riešenie tohto problému využijeme funkčný prototyp, ktorým ešte pred samotnou definíciou funkcie faktor() oznámime prekladaču kompletné informácie (typ návratovej hodnoty, typy a počet argumentov) o funkcii vyraz().

Úplný funkčný prototyp sa skladá z hlavičky funkcie ukončenej bodkočiarkou ‘;’. Napr. v programe P01.CPP máme v riadku 5 uvedený funkčný prototyp funkcie max(), ktorým sme funkciu iba zadeklarovali, ale jej definícia sa nachádza až v riadkoch 27 až 30. Na upresnenie uvádzame, že riadok 27 predstavuje hlavičku funkcie max().

faktor()

vyraz() term()

13

Procedúry a dátový typ void Formálne síce v C procedúry neexistujú, ale sú dva spôsoby, ako tento „nedostatok“ obísť: 1. Funkcia návratovú hodnotu síce vráti, ale nikto ju nechce. Typický príklad je čakanie na stlačenie klávesu

pomocou funkcie getchar() z knižnice STDIO.H, ktorá pri normálnom použití vráti stlačený znak, teda návra-tová hodnota funkcie je stlačený znak. Príklad volania funkcie ako procedúry:

getchar(); /* čakanie na stlačenie klávesu */ 2. Funkcia sa definuje ako funkcia vracajúca typ void (tzn., že nevracia žiadnu hodnotu), napr.:

void pouzitie() { printf("Help k programu calc\n––––––––\n"); ... }

Príkaz return v tomto prípade nie je nutný. Pokiaľ nie je uvedený, nahrádza ho uzatvárajúca zložená zátvorka funkcie „}“. Príkaz return sa potom používa iba pre nútené ukončenie funkcie pred dosiahnutím jej konca, napr. po nejakej podmienke, a má tvar return;. Typ void sa používa aj v prípade, že funkcia nemá žiadne formálne parametre, aby bol prekladač o tom uistený. Napr. ak hlavná funkcia nemá žiadne vstupné parametre a ani nevracia operačnému systému žiadnu návratovú hodnotu, potom hlavička funkcie vyzerá nasledovne:

void main(void)

Parametre funkcií Jazyk C umožňuje iba jeden spôsob dodávania parametrov funkcii, a to hodnotou. Pri tomto spôsobe prenášania

parametrov sa hodnoty aktuálnych (skutočných) parametrov kopírujú cez zásobník do formálnych parametrov funk-cie. Obr. 2-3 Volanie hodnotou.

Pri prenášaní parametrov hodnotou skutočné parametre nemôžu byť vo funkcii zmenené, pretože vo funkcii sa pracuje s kópiou hodnôt skutočných parametrov. Teda akákoľvek zmena parametrov vo vnútri funkcie je iba dočasná a po opustení funkcie sa stráca.

Ak potrebujeme vo funkcii zmeniť hodnoty skutočných parametrov – urobiť trvalú zmenu, musíme funkcii dodať namiesto hodnoty parametra jeho adresu (adresu v pamäti, kde sa argument nachádza). Potom už funkcia môže zmeniť hodnotu parametra, pretože bude meniť obsah na dodanej adrese v pamäti. Tento spôsob riešenia sa nazýva volanie odkazom. Na prenos adresy sa využívajú smerníky. Obr. 2-4 Volanie odkazom.

S realizáciou volania odkazom sa bližšie oboznámime v lekcii XIII „Smerníky a funkcie – volanie odkazom“.

Kopírovanie Skutočné parametre

Formálne parametre

Volajúci program Funkcia

Skutočné parametre Adresa

Formálne parametre

Volajúci program Funkcia

14

Definície premenných Definícia premennej sa môže vyskytovať buď vonku/mimo funkcie, vtedy hovoríme o globálnej premennej,

alebo vo vnútri funkcie, a vtedy takúto premennú nazývame lokálna premenná. Napríklad premenná c v riadku 3 programu P01.CPP je globálna premenná, ale premenné a a b uvedené v riadku 9 sú lokálne.

Globálne premenné Definície globálnych premenných sa vyskytujú mimo definícií funkcií. Definícia globálnej premennej definuje

premennú, ktorej rozsah platnosti je od miesta definície až do konca súboru – nie programu! (program môže pozostávať z viacerých súborov.) Prístup k tejto premennej má teda každá funkcia, ktorá je definovaná za definíciou globálnej premennej. Keďže globálnu premennú môže meniť ľubovoľná funkcia, snažíme sa vyhýbať používaniu týchto premenných, pretože pri nejednoznačnom toku (spracovávaní programu) nie je jednoduché zistiť, ktorá funkcia zmenila obsah tejto premennej. Používaním globálnych premenných sa zároveň znižuje modularita programu.

Globálne premenné obsadzujú miesto v dátovej oblasti pamäti. Globálne premenné využívame najčastejšie vtedy, keď potrebujeme k premennej pristupovať v každej funkcii ⇒ hodnotu premennej nemusíme prenášať cez parameter; alebo ak by sa prenášaním hodnoty pomocou parametra a návratovej hodnoty funkcie príliš zneprehľadnil kód funkcie – viď. použitie globálnych premenných sym a cislo v rekurzívnej funkcii faktor(), term(), vyraz() a funkcii getSymbol() v príkladoch od lekcie XVI.

Lokálne premenné Lokálnou premennou je každá premenná definovaná vo vnútri funkcie resp. bloku (pripomíname, že blok je

ľubovoľná časť programu uzavretá v zložených zátvorkách { a }). Lokálna premenná vymedzuje miesto v zásobníku a je viditeľná iba zvnútra funkcie alebo bloku, v ktorom je definovaná. Rozsah platnosti lokálnej premennej je od miesta jej definície až po koniec funkcie/bloku, v ktorej bola premenná definovaná. S ukončením spracovávania funkcie/bloku lokálna premenná zaniká (zruší sa miesto vymedzené v zásobníku) a pri opätovnom vstupe do funkcie je jej hodnota nedefinovaná! Tip: Ak potrebujete zabezpečiť, aby sa hodnota lokálnej premennej medzi volaniami funkcie zachovala, napíšte pred definíciu premennej kľúčové slovo static.

Ak vo funkcii zadefinujeme premennú s rovnakým názvom ako je názov globálnej premennej, prekryjeme globálnu premennú definíciou lokálnej premennej, a teda v tejto funkcii už nemáme prístup ku globálnej premennej (viď. príklad premennej cislo vo funkcii root cieľového programu kalkulátor).

Cvičenia 1. Hodnota resp. výraz, ktorý pomocou príkazu return vraciame volajúcej funkcii, sa z konvencie píše do zátvo-

riek, i keď sa nemusí. Vyskúšajte na príklade programu P01.CPP prepísať vo funkcii main() príkaz return bez zátvoriek a presvedčte sa o funkčnosti.

Riešenie: program c02-1.cpp

2. V programe P01.CPP definujte funkciu main() ako procedúru, tzn. nech funkcia nevracia žiadnu hodnotu. Aký význam má v tomto prípade príkaz return?

Riešenie: program c02-2.cpp

15

III. lekcia: Formátovaný vstup/výstup z/na terminál Cieľom tejto lekcie je oboznámiť vás s príkazmi pre formátovaný vstup/výstup z/na terminál. Pod pojmom vstup

z terminálu máme na mysli vstup z klávesnice a pod pojmom výstup na terminál máme na mysli výstup na obrazovku. S funkciami pre vstup/výstup zo/do súboru sa oboznámite v lekcii XVII – „Práca so súbormi, štandardný vstup/výstup“. Slovom formátovaný vyjadrujeme skutočnosť, že napr. vypísanie hodnoty čísla na obrazovku (prípadne jeho vstup z klávesnice) môžeme vykonať v rôznych formátoch (napr. dekadický, hexadecimálny alebo znakový tvar celého čísla, resp. rôzny počet desatinných miest pre zobrazenie reálnych čísel, zarovnanie hodnôt k pravému alebo ľavému okraju a pod.). Výpis programu P03.CPP 1: #include <stdio.h> 2: 3: main() 4: { 5: int cislo; 6: 7: printf("Zadaj cele cislo: "); 8: scanf("%d", &cislo); 9: 10: printf("Zadal si cislo %d\n\n", cislo); 11: 12: return(0); 13: }

Vysvetlivky: printf – príkaz na formátovaný výstup na obrazovku scanf – príkaz na formátovaný vstup z klávesnice "%d" – riadiaci reťazec formátu \n – znak nového riadku

Formátovaný výstup na obrazovku Funkcia printf() slúži na formátovaný výstup na obrazovku. Jazyk C nedefinuje žiadnu I/O (vstupno/výstupnú –

z angl. Input/Output) operáciu ako časť jazyka, ale na prácu s nimi poskytuje príkazy, ktoré sa nachádzajú v štandardnej knižnici STDIO. Preto pre prácu s funkciami pre vstup a výstup je potrebné na začiatku programu vložiť pomocou direktívy #include hlavičkový súbor STDIO.H (riadok 1), v ktorom sa nachádzajú prototypy týchto funkcií.

Prvým argumentom funkcie printf() je tzv. riadiaci reťazec formátu. Riadiaci reťazec formátu je obyčajný reťazec znakov uzavretý v úvodzovkách, ktorý môže obsahovať:

• formátové špecifikácie – postupnosť začínajúca znakom „%“, ktorá určuje formát výpisu hodnoty premennej • znakové postupnosti – postupnosť znakov, ktorá nezačína znakom „%“ a vypíše sa tak, ako je zapísaná Počet argumentov funkcie printf() je premenlivý a závisí od počtu formátových špecifikácií takto: koľko

znakov % sa nachádza v riadiacom reťazci formátu, toľko parametrov oddelených čiarkami musí nasledovať za riadiacim reťazcom. Napr. v našom programe (riadok 10) sme v riadiacom reťazci formátu použili jednu formátovú špecifikáciu (jeden znak %), a teda za reťazcom nasleduje jeden parameter, kým v riadku 7 sme nepoužili žiadnu formátovú špecifikáciu, a teda za riadiacim reťazcom formátu nenasleduje žiadny parameter. Parametrom pre funkciu printf() môže byť premenná alebo konštanta. Napr. v riadku 10 môžeme namiesto premennej cislo priamo napísať číselnú konštantu 458.

Formátovou špecifikáciou vo funkcii printf() určujeme, ako sa vypíše hodnota premennej alebo konštanty, ktorá nasleduje za riadiacim reťazcom formátu a v danom poradí prislúcha k formátovej špecifikácii. Napr. v našom programe sme definovali premennú cislo typu int (riadok 5), ktorej hodnotu vypisujeme v riadku 10. Keďže premenná je typu int, formátovou špecifikáciou %d uvádzame, že na danom mieste (kde sa nachádza v riadiacom reťazci formátu) sa má vypísať hodnota celého čísla typu int v desiatkovom tvare. Vo funkcii printf() za riadiacim reťazcom formátu uvádzame premenné, ktorých hodnoty chceme vypísať.

16

Pre typ int je teda formátová špecifikácia %d resp. %i pre zobrazenie čísla v desiatkovej sústave, %o pre zobrazenie čísla v osmičkovej sústave, %x resp. %X pre zobrazenie čísla v šestnástkovej sústave. Pre reálne čísla s dvojnásobnou presnosťou (typ double) má funkcia printf() k dispozícii formátové špecifikácie %lf, %le, %lg, %lE a %lG. Medzi znakom % a znakmi určujúcimi typ premennej vo formátovej špecifikácii môže byť uvedené číslo v tvare [m][.n] (obidva parametre sú voliteľné) udávajúce minimálnu šírku a presnosť, v ktorej sa vypíše reálne číslo (typ float, double, resp. long double), tzn. že číslo sa vypíše minimálne na m miest, z toho n desatinných miest včítane desatinnej bodky.

Pre zobrazenie znaku sa používa formátová špecifikácia %c a pre reťazec %s. Medzi úvodným znakom % a koncovým znakom formátovej špecifikácie môžu byť uvedené ešte ďalšie symboly (napr. zarovnanie čísla doprava/doľava, zobrazenie čísla so znamienkom, doplnenie čísla nulami zľava a pod.), ktorých výklad nie je cieľom tejto príručky – je ich možné nájsť v referenčnej príručke jazyka C. Niektoré horeuvedené formátové špecifikácie si vyskúšate v cvičení č. 4 tejto lekcie.

V riadiacom reťazci formátu je možné použiť aj tzv. escape sekvencie, čo sú znaky resp. čísla (buď v osmičko-vej alebo šestnástkovej sústave) uvedené za znakom „\“, ktoré majú špeciálny význam. Nasleduje výpis tých escape sekvencií aj s významom, ktoré použijeme v našom programe kalkulátor:

\n – znak nového riadku (new line character); pri výstupe spôsobí odriadkovanie \t – znak tabelátora; pri výstupe spôsobí posunutie vpravo o určitý počet znakov \0 – nulový znak (null character); ukončuje reťazec V programe P03.CPP sme v riadku 10 použili znak ‘\n’ dvakrát, čím sme docielili, že za zobrazeným číslom sa

odriadkuje a vypíše sa jeden prázdny riadok.

Poznámka: Keďže znaky ‘\’ a ‘%’ majú v riadiacom reťazci formátu špeciálny význam, na ich zobrazenie pomocou funkcie printf() musíme príslušný znak zdvojiť, teda napr. %% vypíše znak percenta alebo \\ vypíše znak opačnej lomky. Ak potrebujeme zobraziť znak úvodzovky, musíme pred ňou uviesť znak ‘\’.

Formátovaný vstup z klávesnice Štandardná knižnica STDIO nám na formátovaný vstup z klávesnice poskytuje funkciu scanf(). Funkcia má

podobne ako printf() premenlivý počet parametrov, pričom prvým parametrom funkcie je opäť riadiaci reťazec formátu a zvyšný počet argumentov funkcie závisí od počtu formátových špecifikácii. V riadiacom reťazci formátu sa na rozdiel od funkcie printf() používajú iba formátové špecifikácie, oddeľovače (napr. znak medzera) a niektoré escape sekvencie, ktoré však majú pri vstupe z terminálu odlišný význam.

Všetky formátové špecifikácie, ako sú napr. %d, %lf, %x, %s, %c a i., majú ekvivalentný význam ako vo funkcii printf(), tzn. sú zviazané s rovnakými údajovými typmi. Šírka [m] udáva maximálny počet znakov, ktoré sa načítajú z klávesnice a má význam iba pri reťazcoch. Pri reálnych číslach môžeme pri zadávaní čísla použiť aj exponenciálnu notáciu, t.j. tam, kde sa očakáva reálne číslo typu float, double resp. long double, môžeme zadať z klávesnice číslo v tvare 1234.56 rovnako ako 1.23456E4 alebo 12.3456e-2 a pod.

Zásadný rozdiel vo volaní funkcie scanf() oproti funkcii printf() nastáva pri argumentoch uvedených za riadia-cim reťazcom formátu, pretože pred všetkými premennými jednoduchých typov (celé a reálne čísla – nie polia a reťazce) je potrebné uvádzať znak & (ampersand), ktorým označujeme adresu premennej, kam sa má uložiť načíta-ná hodnota. V predchádzajúcej lekcii sme sa dozvedeli, že funkcia dokáže vrátiť štandardne iba jednu hodnotu, a tou je pri funkcii scanf() počet úspešne načítaných položiek (čísel, znakov, reťazcov, ...). Ďalej sme sa dozvedeli, že parametre sa do funkcií prenášajú hodnotou (tzn. funkcia nedokáže meniť hodnotu skutočného parametra). Ak má teda prekladač zabezpečiť načítanie viacerých hodnôt a navyše funkcia má vrátiť počet načítaných hodnôt – všetko sú to výstupy funkcie („návratové hodnoty“) – musíme funkcii predať adresy premenných, kam sa uložia tieto hodnoty. Toto sa realizuje pridaním znaku & pred meno premennej a nazývame to volanie odkazom (viď. lekciu XIII „Smerníky a volanie odkazom“).

Pozor! Ak zabudneme vo funkcii scanf() pred premennou uviesť adresný operátor (znak &), prekladač nevy-hlási žiadnu chybu ani varovanie a program pracuje nekorektne. Prekladač totiž predpokladá, že hodnota premennej predstavuje adresu, kam sa má uložiť výsledok (načítaná hodnota z klávesnice) a teda nepovažuje to za chybu.

17

V našom príklade chceme v riadku 8 načítať celé číslo typu int do premennej cislo. Ak používateľ zadá namiesto očakávaného čísla napr. reťazec „qkt48w“, funkcia scanf() vráti nulu a hodnota premennej cislo bude nedefinovaná, pretože premenná nie je inicializovaná a jej pamäťové miesto je vyhradené v zásobníku (viď. predchá-dzajúcu lekciu). Ak používateľ zadá číslo „4.135“, program načíta číslo 4, pretože podľa formátovej špecifikácie očakáva celé číslo a znak ‘.’ už nie je znakom celého čísla a reťazec „.135“ zostane v bufferi klávesnice.

Funkcia scanf() úvodné biele znaky (medzery, tabulátory, odriadkovania) ignoruje, a je teda jedno, koľkokrát stlačíme napríklad kláves Enter pred zadaním čísla. Znak medzery v riadiacom reťazci formátu pred formátovou špecifikáciou označuje, že sa majú ignorovať biele znaky.

Upozornenie – platí pre printf() aj scanf():

Prekladač jazyka C nekontroluje, či typ premennej, ktorú vypisujeme alebo čítame, zodpovedá typu uvedenému vo formátovej špecifikácii riadiaceho reťazca a nehlási žiadnu chybu ani varovanie! Taktiež nekontroluje či počet argumentov za riadiacim reťazcom formátu zodpovedá počtu formátových špecifikácií (znakov %). Preto treba dávať veľký pozor pri používaní uvedených funkcií, aby počet a typ argumentov presne zodpovedal špecifikácii uvedenej v riadiacom reťazci formátu.

Cvičenia 1. Napíšte procedúru pouzitie (bez parametrov), ktorá pomocou príkazov printf() vypíše návod na použitie

programu kalkulátor s nasledovným textom: 1. riadok 2. riadok Help k programu calc 3. riadok -------------------- 4. riadok Spustenie programu: calc.exe [subor1] [subor2] 5. riadok bez parametrov - spusti kakulator v interaktivnom mode 6. riadok subor1 - vstupny subor, v ktorom su ulozene vyrazy 7. riadok subor2 - volitelne meno vystupneho suboru, v ktorom budu vypocitane vyrazy 8. riadok ak nie je zadane, vysledky sa vypisu na obrazovku 9. riadok

Riešenie: súbor c03-1.cpp

2. a) Odstráňte znak & pred premennou cislo vo funkcii scanf() programu P03.CPP, program skompilujte (preložte) a vyskúšajte jeho funkčnosť. Všimnite si počet chýb a varovaní pri preklade.

Riešenie: program c03-2a.cpp

b) Zameňte formátovú špecifikáciu %d v riadku 10 programu P03.CPP za %lf alebo ľubovolnú inú, ktorá sa používa pre reálne čísla, a vyskúšajte funkčnosť programu. Všimnite si počet chýb a varovaní pri preklade.

Riešenie: program c03-2b.cpp

c) Ako už vieme, vo funkcii printf() neuvádzame pred premennou znak &, pretože na mieste formátovej špecifikácie (napr. %d) sa očakáva hodnota a nie adresa. Znak & v tomto prípade spôsobí, že sa hodnota prečíta z adresy určenej obsahom premennej, t.j. jej hodnotou. Doplňte znak adresného operátora (znak &) pred premennú cislo v riadku 10 programu P03.CPP a vyskúšajte funkčnosť programu. Opäť si všimnite počet chýb a varovaní pri preklade.

Riešenie: program c03-2c.cpp

18

3. Prepíšte program P03.CPP tak, aby načítal reálne číslo typu double a vypísal ho. Po spustení programu skúste zadať ako vstup číslo v exponenciálnom tvare (napr. číslo 4.87e-2).

Riešenie: program c03-3.cpp

4. Na programe z predchádzajúceho cvičenia v príkaze printf(), ktorým vypisujete zadanú hodnotu, vyskúšajte postupne formáty čísel: %5.2lf, %.3lf, %010.3lf, %lG, %+lG a %.15lG prípadne iba %lf a zistite, ako sa vypíšu čísla: 3, 4.12, -15.2e-3, 12.34567, atď.

Riešenie: program c03-4.cpp

19

IV. lekcia: Riadiace štruktúry – príkaz vetvenia Príkaz if a ternárny operátor

Cieľom tejto lekcie je oboznámiť vás s príkazom vetvenia, pomocou ktorého dosahujeme podmienené vykoná-vanie programu. Tzn., že na základe určitej podmienky sa vykoná jedna časť programu, zatiaľ čo pri jej nesplnení sa vykoná iná časť programu. V tejto lekcii sa tiež oboznámite s podmieneným výrazom, ktorého funkcionalita je podobná príkazu vetvenia a v jazyku C sa často používa, pretože skracuje zápis zdrojového kódu programu. Výpis programu P04.CPP 1: #include <stdio.h> 2: 3: /* prototyp funkcie */ 4: double abs(double cislo); 5: 6: void main() 7: { 8: double cislo; 9: 10: printf("Zadaj realne cislo: "); 11: scanf("%lf", &cislo); 12: 13: printf("Absolutna hodnota z %+lG = %lG\n\n", cislo, abs(cislo)); 14: } 15: 16: /* funkcia vrati absolutnu hodnotu cisla */ 17: double abs(double cislo) 18: { 19: if (cislo < 0) 20: return(-cislo); 21: else 22: return(cislo); 23: }

Vysvetlivky: if ... else ... – príkaz vetvenia programu < – relačný operátor „menší ako“

Príkaz if Podmienené vykonávanie programu docielime pomocou príkazu if. Príkaz if má jeden z nasledujúcich tvarov:

alebo

Za príkazom if musí nasledovať výraz uzavretý v okrúhlych zátvorkách. Výraz sa vyhodnotí a jeho hodnota sa využije ako podmienka na vykonanie príkazu. Podmienkou býva najčastejšie Booleovský výraz, pričom logická hodnota FALSE (nepravda) zodpovedá hodnote 0, a logická hodnota TRUE (pravda) zodpovedá ľubovoľnej inej hodnote ako 0 (najčastejšie 1, ale nie je to podmienkou).

Poznámka: Jazyk C priamo neobsahuje typ Boolean. Logické hodnoty sú reprezentované pomocou celočísel-ných hodnôt (typ int), ktoré sú interpretované vyššie uvedeným spôsobom.

V konštrukcii if môžeme namiesto príkazu uviesť aj blok príkazov, t.j. niekoľko príkazov uzavretých do zlože-ných zátvoriek { a }. Zo syntaxe príkazu if vidíme, že vetva else nie je povinná (tvar príkazu if uvedený vľavo).

if (výraz) príkaz;

if (výraz) príkaz1; else príkaz2;

20

Sémantika príkazu if je jednoduchá. Najskôr sa vyhodnotí výraz uvedený v okrúhlych zátvorkách. Ak je výsledkom logická hodnota TRUE, vykoná sa príkaz1, ináč (logická hodnota FALSE) sa vykoná príkaz2. Pre tvar príkazu if, ktorý je uvedený vľavo, platí, že príkaz sa vyhodnotí iba v prípade, že podmienka (výraz) je splne-ná, t.j. výsledkom výrazu je logická hodnota TRUE. Celú sémantiku vyjadruje obrázok 4-1, na ktorom je uvedený vývojový diagram konštrukcie if–else.

Obr. 4-1 Vývojový diagram konštrukcie if–else.

V programe P04.CPP sme príkaz if použili vo funkcii pre výpočet absolútnej hodnoty (riadky 17 až 23). Ak je hodnota skutočného parametra cislo záporná (riadok 19), funkcia vráti negovanú hodnotu cisla (riadok 20), ináč vráti pôvodnú hodnotu cisla (riadok 22).

Ternárny operátor Jazyk C poskytuje aj tzv. podmienený výraz, ktorým je ternárny operátor. Syntax podmieneného výrazu je:

podmienka ? výraz1 : výraz2 a má ten význam, že podmienka sa vyhodnotí ako Booleovský výraz, a ak platí, vyhodnotí sa výraz1, ináč sa vyhodnotí výraz2. Ak za podmieneným výrazom uvedieme bodkočiarku ‘;’, stane sa z výrazu príkaz, ktorý je ekvivalentný s príkazom:

if (podmienka) výraz1 else výraz2;.

else

if výraz

príkaz1

príkaz2

true (nenulová hodnota)

false (nulová hodnota)

21

Cvičenia 1. Napíšte funkciu abs() bez vetvy else. Využite znalosť, že príkaz return ukončuje vykonávanie funkcie.

Riešenie: súbor c04-1.cpp

2. Prepíšte funkciu abs() tak, že bude obsahovať iba príkaz return a skrátenú formu príkazu if, t.j. podmienený výraz.. Pozor! Zložené zátvorky { a } musia nasledovať za hlavičkou funkcie abs(), aj keď funkcia obsahuje v tomto prípade iba jeden príkaz, pretože oznamujú prekladaču definíciu funkcie (viď. lekciu II – časť „Dekla-rácia a definícia funkcie“).

Riešenie: súbor c04-2.cpp

3. Napíšte funkciu s prototypom double max(double x, double y); ktorá vráti väčšie číslo zo svojich argumentov x a y. Funkciu implementujte pomocou príkazu if aj pomocou ternárneho operátora. Na porovnanie hodnôt x a y použite relačný operátor „väčší“, ktorý má v jazyku C syntax „>”.

Riešenie: súbor c04-3.cpp

4. Podobne, ako v cvičení 3, napíšte funkciu min(x, y), ktorá vráti menšie číslo z x a y. Riešenie: súbor c04-4.cpp

22

V. lekcia: Booleovské výrazy Cieľom tejto lekcie je oboznámiť vás bližšie s Booleovskými výrazmi, najmä s ich vyhodnocovaním v jazyku C.

Ako ste sa dozvedeli v predchádzajúcej lekcii, tak Booleovské výrazy najčastejšie používame ako podmienku pri podmienenom vykonávaní programu. V nasledujúcich lekciách uvidíme, že Booleovské výrazy sa používajú aj v ria-diacich častiach iteračných príkazov, ktorými sú napr. cykly, rekurzívne funkcie, atď. Keďže Booleovské výrazy sa v programoch veľmi často používajú, je im venovaná samostatná lekcia.

V tejto lekcii si v cvičeniach precvičíte príkaz if a ternárny operátor, s ktorými ste sa oboznámili v predchádzajú-cej lekcii.

Výpis programu P05.CPP 1: #include <stdio.h> 2: 3: /* prototypy funkcii */ 4: int isdigit (char x); 5: int islower (char x); 6: char toupper (char ch); 7: 8: void main() 9: { 10: char znak; 11: 12: printf("\nZadaj jeden znak: "); 13: znak = getchar(); 14: 15: if (isdigit(znak)) 16: printf("Znak '%c' je cislica.\n", znak); 17: else 18: if (islower(znak)) 19: printf("Znak '%c' je velke pismeno.\n", toupper(znak)); 20: else 21: printf("Znak '%c' nie je ani cislica ani male pismeno.\n", znak); 22: 23: } 24: 25: /* Funkcia vrati TRUE (1), ak znak x predstavuje cislicu, * 26: * inac vrati FALSE (0) */ 27: int isdigit(char x) 28: { 29: return(x >= '0' && x <= '9'); 30: } 31: 32: /* Funkcia vrati TRUE (1), ak znak x je pismeno malej abecedy, * 33: * inac vrati FALSE (0) */ 34: int islower (char x) 35: { 36: return(x >= 'a' && x <= 'z'); 37: } 38: 39: /* Funkcia vrati velke pismeno, ak vstupny parameter ch je male pismeno, * 40: * inac vrati povodny znak */ 41: char toupper(char ch) 42: { 43: return(islower(ch) ? ch - ('a' - 'A') : ch); 44: }

23

Vysvetlivky: = – operátor priradenia >= – relačný operátor "väčší alebo rovný" <= – relačný operátor "menší alebo rovný" && – logický súčin (AND) getchar() – funkcia, ktorá slúži na načítanie jedného znaku zo štandardného vstupu

Funkcia getchar() Funkcia getchar() načíta jeden znak zo štandardného vstupu (klávesnica). Prototyp funkcie sa nachádza v hla-

vičkovom súbore STDIO.H. Po volaní getchar() môžeme písať znaky na klávesnici tak dlho, kým nestlačíme kláves Enter. getchar() potom prečíta iba prvý z týchto zadaných znakov.

V riadku 13 programu P05.CPP voláme funkciu getchar(), aby sme od používateľa získali jeden znak. Namiesto getchar() sme mohli použiť príkaz scanf("%c", &znak); avšak tento spôsob je pre prekladač omnoho zložitejší, pretože navyše vyžaduje spracovanie riadiaceho reťazca formátu ⇒ má väčšiu réžiu (viď. lekciu III „Formátovaný vstup/výstup z/na terminál“).

Logické a relačné operátory v jazyku C V predchádzajúcej lekcii sme sa oboznámili s tým, ako je v C interpretovaný typ Boolean, teda že celočíselná

hodnota 0 znamená FALSE (nepravda) a nenulová hodnota (najčastejšie 1, ale nie je to podmienkou) znamená TRUE (pravda). Aby sme mohli vytvárať Booleovské výrazy, musíme sa oboznámiť s logickými a relačnými operátormi, ktoré nám jazyk C poskytuje (tabuľka 5-1 a 5-2).

Operátor Význam Použitie && Logický súčin (AND) riadky 29 a 36 programu P05.CPP || Logický súčet (OR) v nasledujúcej lekcii ! Negácia (NOT) cvičenie 2 lekcie 7

Tabuľka 5-1. Logické operátory jazyka C

Operátor Význam Použitie == Rovnosť v nasledujúcej lekcii != Nerovnosť v nasledujúcej lekcii < Menší v predchádzajúcej lekcii > Väčší cvičenie 3 predchádzajúcej lekcie >= Väčší alebo rovný riadky 29 a 36 programu P05.CPP <= Menší alebo rovný riadky 29 a 36 programu P05.CPP

Tabuľka 5-2. Relačné operátory jazyka C

24

Skrátené vyhodnocovanie logických výrazov Logický súčin a súčet sa v jazyku C vyhodnocujú v tzv. skrátenom vyhodnocovaní (angl. lazy evaluation).

Znamená to, že argumenty sú vyhodnocované zľava doprava a akonáhle je možné určiť konečný výsledok, vyhodno-covanie okamžite končí.

Napr. vo funkcii islower() (riadky 34 až 37) ak znak x predstavuje číslicu '9', tak už hneď podmienka v prvom podvýraze (x >= 'a') nie je splnená, a teda je už známy konečný výsledok, pretože podvýrazy sú spojené logickým súčinom a FALSE krát hocičo je v logike vždy FALSE.

Tento spôsob vyhodnocovania má v C praktické využitie. Napr. výraz: if (y != 0 && x / y < z) ...

je úplne správny a k deleniu nulou v tomto prípade nikdy nemôže prísť, pretože prvá časť logického výrazu (y != 0) ukončí vyhodnocovanie tohto výrazu pred delením.

Cvičenia 1. Presvedčte sa pomocou príkazu printf("%d", ...) a volania funkcie isdigit() raz s parametrom '5'

a druhý raz s parametrom 'B', že návratová hodnota funkcie isdigit() je 1 v prípade TRUE a 0 v prípade FALSE.

Riešenie: program c05-1.cpp

2. Prepíšte telo funkcie isdigit() pomocou príkazu if a presvedčte sa na volaní funkcie, že nenulová návratová hodnota (napr. –1), v prípade splnenej podmienky, znamená TRUE.

Riešenie: program c05-2.cpp

3. Prepíšte ternárny operátor vo funkcii toupper() programu P05.CPP pomocou príkazu if, a naopak prepíšte konštrukciu if–else–if–else vo funkcii main() pomocou jediného príkazu printf() a ternárnych operátorov. Pomôcka: Ak potrebujete pomocou príkazu printf() vypísať reťazec, použite formátovú špecifikáciu %s

v riadiacom reťazci formátu (pozri lekciu III).

Riešenie: program c05-3.cpp

Tipy a triky Tip 1: Podobne, ako funkcia getchar(), ktorá slúži na načítanie znaku zo štandardného vstupu, existuje aj funkcia

putchar() s prototypom int putchar(int c), ktorá vypíše znak c na štandardný výstup (obrazovka). Tip 2: Ak potrebujete načítať z klávesnice práve jeden znak bez nutnosti stlačenia klávesu Enter, použite funkciu

getch() resp. getche() z knižnice ConIO, ktorá však nie je súčasťou jazyka C podľa ANSI štandardu, tzn. nemusí ju obsahovať každý prekladač jazyka C. Prekladač od fy. Borland však tieto funkcie poskytuje. Predtým, než môžete použiť aspoň jednu z uvedených funkcií, musíte na začiatku programu vložiť hlavičkový súbor tejto knižnice pomocou direktívy #include <conio.h>. Rozdiel medzi getch() a getche() je v tom, že v prípade getche() sa znak načítaný z klávesnice vypíše aj na obrazovku. Vyskúšajte vo funkcii main() programu P05.CPP vymeniť riadok 13 za nasledovný a odskúšajte správanie sa programu:

znak = getche();

25

VI. lekcia: Riadiace štruktúry – iteračné príkazy Príkazy while, break a continue

Cieľom tejto lekcie je oboznámiť vás s príkazom cyklu s podmienkou na začiatku, pomocou ktorého dosahu-jeme opakované vykonávanie programu na základe určitej podmienky. Ďalej sa naučíme, ako môžeme ovplyvniť priebeh cyklu pomocou príkazov na riadenie cyklu. Nakoniec sa zoznámime so zloženým priraďovacím príkazom (príkaz priradenia s operátorom). Výstupom tejto lekcie bude funkcia pre výpočet najväčšieho spoločného deliteľa, ktorú využijeme v našom cieľovom programe kalkulátor. Výpis programu P06.CPP 1: #include <stdio.h> 2: 3: /* prototypy funkcii */ 4: double abs(double cislo); 5: int NSD(int a, int b); 6: 7: void main() 8: { 9: int x, y; 10: int vysledok; 11: 12: printf("Zadaj dve cele cisla oddelene medzerou: "); 13: scanf("%d %d", &x, &y); 14: 15: vysledok = NSD(abs(x), abs(y)); 16: printf("Najvacsi spolocny delitel z %d a %d je %d\n\n", x, y, vysledok); 17: 18: } 19: 20: /* Dijkstrov algoritmus pre vypocet najvacsieho spolocneho delitela */ 21: int NSD(int a, int b) 22: { 23: if (a == 0 || b == 0) 24: return(1); 25: 26: while(a != b) 27: { 28: if (a > b) 29: a=(a-b); 30: else 31: b=(b-a); 32: } 33: 34: return (a); 35: } 36: 37: 38: /* funkcia vrati absolutnu hodnotu cisla */ 39: double abs(double cislo) 40: { 41: return (cislo < 0 ? -cislo : cislo); 42: }

Vysvetlivky: == – operátor porovnania != – operátor nerovnosti || – logický súčet (OR) while – príkaz cyklu s podmienkou na začiatku

26

Operátor porovnania Pozor na rozdiel medzi porovnaním (operátor ==) a priradením (operátor =). a = 0 je celočíselný výraz s hodnotou 0, ktorú priraďuje premennej a – zmení jej pôvodnú hodnotu. a == 0 je celočíselný výraz poskytujúci 1 (TRUE), ak má premenná a hodnotu 0, alebo poskytujúci

0 (FALSE), ak má a hodnotu inú než 0. Hodnota premennej a sa nemení! (viď. riadok 23)

Cyklus s podmienkou na začiatku Syntax príkazu while:

while (podmienka) príkaz;

Poznámky: • V I. lekcii sme sa naučili, že tam, kde sa očakáva príkaz, môže byť väčšinou použitý aj blok, t.j. niekoľko

príkazov uzavretých do zložených zátvoriek { a }, a preto namiesto príkazu môže byť uvedený blok príka-zov.

• Zátvorky okolo podmienky sú povinné – podobne ako v príkaze if. • podmienka je Booleovský výraz, ktorý sa vyhodnotí a jeho výsledok (0 = FALSE; iné ako 0 = TRUE)

sa použije ako podmienka na vykonanie tela cyklu (príkazu).

Obr. 6-1 Vývojový diagram príkazu while.

V našom príklade sme cyklus while použili na implementáciu funkcie NSD, ktorej cieľom je nájsť najväčší spo-ločný deliteľ dvoch čísiel (argumenty funkcie) pomocou Dijkstrovho algoritmu. Telo cyklu while (riadky 27 až 32) sa vykonáva dovtedy, kým je splnená podmienka vykonávania cyklu (riadok 26), t.j. kým hodnoty obidvoch premen-ných (formálne parametre a a b – pozri hlavičku funkcie v riadku 21) nie sú rovnaké.

while podmienka

príkaz(y) cyklu

false

true

27

Príkazy break a continue Vo všetkých typoch cyklov – while, do...while a for (viď. nasledujúce lekcie) – možno použiť príkazy break

a continue, ktoré menia normálny priebeh cyklu a to nasledovne: • break – ukončuje najvnútornejšiu (pri vnorených cykloch) neuzavretú slučku, teda okamžite opúšťa cyklus. • continue – skáče na koniec najvnútornejšej neuzavretej slučky a tým vynúti ďalšiu iteráciu (ďalší cyklus).

continue cyklus neopúšťa!

Obr. 6-2 Funkcia príkazov break a continue.

Cvičenia 1. S využitím znalosti rozšírenej formy priraďovacieho príkazu

premenná = premenná operátor výraz; → premenná operátor= výraz; (napr. i = i + 5; → i += 5;) prepíšte telo funkcie NSD, t.j. vetvy príkazu if.

Riešenie: program c06-1.cpp

2. Prepíšte funkciu NSD pomocou nekonečného cyklu while(1) {...}, operátora porovnania a príkazov break a continue.

Riešenie: program c06-2.cpp

3. Telom cyklu while môže byť prázdny príkaz (;). Napíšte pomocou príkazu while najjednoduchší príklad "zacyklenia", t.j. nekonečného cyklu.

Riešenie: program c06-3.cpp

4. Napíšte pomocou príkazu while funkciu n-tej mocniny s prototypom double pow(double x, unsigned n); ktorá vráti hodnotu n-tej mocniny z x (n je prirodzené číslo včítane 0). unsgined znamená typový modifikátor, bližšie informácie nájdete v lekcii XIII.

Riešenie: súbor c06-4.cpp

5. Prepíšte funkciu pow() z predchádzajúceho cvičenia na funkciu s prototypom double pow(double x, int y); ktorá vráti y-tú mocninu z x (x je reálne číslo, y je celé číslo – teda aj záporné!)

Riešenie: súbor c06-5.cpp

6. Zväčšite rozsah čísel pre funkciu NSD() tak, že použijete namiesto typu int typ unsigned long. Bližšie informácie o typových modifikátoroch unsigned a long nájdete v lekcii XIII.

Riešenie: súbor c06-6.cpp

28

VII. lekcia: Riadiace štruktúry – iteračné príkazy Príkaz do–while

Cieľom tejto lekcie je oboznámiť vás s ďalším zo série iteračných príkazov, ktorým je príkaz do–while. Príkaz realizuje cyklus s podmienkou na konci. Pomocou príkazu budeme implementovať matematickú funkciu pre výpočet n-tej odmocniny z reálneho čísla. Funkcia má prototyp double root(double cislo, int n); a jej úlohou je vrátiť n-tú odmocninu z cislo. V prípade záporného čísla pod odmocninou vypíše chybovú správu a vráti 0 bez ohľadu na to, či je exponent n párne alebo nepárne číslo (napr. ). Podobne, funkcia bude ošetrovať prípad, keď n nie je prirodzené číslo.

Je dosť pravdepodobné, že vás teraz asi zaujíma, ako chceme funkciu root() implementovať, keď programovo realizovaný výpočet odmocniny vlastne ani nepoznáme. Preto predtým, než uvedieme výpis programu, v ktorom je funkcia root() už implementovaná, uvádzame odvodenie vzorca pre výpočet odmocniny pomocou Newtonovej ite-račnej metódy. (Pozn.: Týmto spôsobom počítajú n-tú odmocninu v súčasnosti takmer všetky kalkulačky.)

Odvodenie vzorca pre výpočet odmocniny

Vzorec pre Newtonovu iteračnú metódu:

Posledný vzťah udáva výsledný vzorec pre výpočet n-tej odmocniny z čísla A, pričom x k je hodnota odmocniny v k-tej iterácii a x k+1 je hodnota v nasledujúcom kroku. Iteráciu vykonávame dovtedy, kým rozdiel medzi hodnotou v (k+1). kroku a v k-tom (predchádzajúcom) kroku nie je menší než daná presnosť (epsilon). A teraz nasleduje im-plementácia tohto vzťahu pomocou cyklu do-while.

3273 −=−

1n

n

n

n

n)(0A)(

Aneznáma - známe; -n A,A

−⋅=′

=−=

=

=

xxfxxf

xxx

( ) nA1-n

An1

n1-n

An1

nn

nA

:dosadení po a)()(

1n1

1n1

1n1n

n

1

1n

n

1

1

+⋅=

⋅+=

−⋅−=

−−=

′−=

−+

−+

−−+

−+

+

kkk

kkk

kk

kkk

k

kkk

k

kkk

xxx

xxx

xxx

xx

xx

xx

xfxf

xx

29

Výpis programu P07.CPP 1: #include <stdio.h> 2: 3: /* prototypy funkcii */ 4: double abs(double cislo); 5: double root(double cislo, int n); 6: double pow(double x, int y); 7: 8: void main() 9: { 10: double x; 11: int n; 12: 13: printf("Zadaj cislo, ktore chces odmocnit: "); 14: scanf("%lf", &x); 15: printf("Zadaj n (cele cislo) urcujuce n-tu odmocninu: "); 16: scanf("%d", &n); 17: 18: printf("%d. odmocnina z %lG je %.15lG\n\n", n, x, root(x,n)); 19: 20: } 21: 22: double root(double cislo, int n) 23: { 24: const double EPSILON = 1E-9; /* presnost pre vypocet odmocniny */ 25: double x_old = 1, /* x v kroku k */ 26: x_new, /* x v kroku k+1 */ 27: tmp; /* pomocna premenna na uschovanie povodnej hodnoty * 28: * x_old, t.j. este pred jej aktualizaciou */ 29: 30: if (cislo < 0) 31: { 32: printf("MATH Error: zaporne cislo pod odmocninou\n"); 33: return(0); 34: } 35: 36: if (n <= 0) 37: { 38: printf("MATH Error: nemozem vyratat n-tu odmocninu pre n <= 0\nn = %d\n", n); 39: return(0); 40: } 41: 42: do { 43: x_new = ((n - 1) * x_old + cislo/pow(x_old, n - 1))/n; 44: tmp = x_old; 45: x_old = x_new; 46: } while (abs(x_new - tmp) > EPSILON); 47: 48: return(x_new); 49: } 50: 51: 52: /* funkcia vrati absolutnu hodnotu cisla */ 53: double abs(double cislo) 54: { 55: return (cislo < 0 ? -cislo : cislo); 56: } 57: 58: 59: double pow(double x, int y) 60: { 61: double vysledok=1;

30

62: 63: if (y == 0) return(1); 64: 65: while(y) 66: { 67: vysledok *= (y < 0 ? 1/x : x); 68: y < 0 ? y++ : y--; 69: } 70: 71: return(vysledok); 72: }

Vysvetlivky: const double EPSILON = 1E-9; – definícia konštanty EPSILON. Konštantná premenná musí byť

zároveň inicializovaná! do { ... } while – príkaz cyklu s podmienkou na začiatku *= – operátor priradenia s násobením (E1 *= E2 ⇔ E1 = E1 * E2) ++ – operátor inkrementácie (y++ ⇔ y = y + 1) –- – operátor dekrementácie (y–– ⇔ y = y - 1)

Cyklus s podmienkou na konci Syntax príkazu do-while:

do príkaz; while (podmienka);

Poznámky: • Namiesto príkazu môže byť uvedený blok príkazov (uzavretý v zložených zátvorkách { a }). • Zátvorky okolo podmienky sú povinné – podobne ako v príkaze if. • podmienka je Booleovský výraz, ktorý sa vyhodnotí a jeho výsledok (0 = FALSE; iné ako 0 = TRUE)

sa použije ako podmienka na vykonanie tela cyklu (príkazu).

Obr. 7-1 Vývojový diagram príkazu do-while.

V tomto cykle sa na rozdiel od cyklu while testuje podmienka až na konci (po prechode telom cyklu) ⇒ cyklus sa vykoná aspoň raz. Cyklus do-while pracuje tak dlho, kým je podmienka splnená, t.j. kým má hodnotu TRUE (nenulovú hodnotu).

false …

do

podmienka

príkaz(y) cyklu

true

while

break

continue

31

V našom príklade sa vykonávajú príkazy tela cyklu (riadky 43 až 45), kým nie je splnené, že rozdiel novej a starej hodnoty vypočítanej odmocniny je menší alebo rovný ako daná presnosť EPSILON (riadok 46). Riadok 43 realizuje vlastný výpočet odmocniny z hodnoty v predchádzajúcom kroku Newtonovej iteračnej metódy. Počiatočné x 0 bolo nastavené na východziu hodnotu 1 (riadok 25). Riadok 44 iba odpamätáva hodnotu x v kroku (k – 1), aby sa mohol vykonať test rozdielu hodnôt x k a x k–1 (riadok 46). Riadok 45 realizuje vlastnú iteráciu, t.j. vykonáva inkre-mentáciu indexu k. Poznámka: V cykle do-while je taktiež možné použiť príkazy break a continue na riadenie cyklu (viď. predchádza-

júcu lekciu).

Cvičenia 1. Prepíšte cyklus do-while vo funkcii root() pomocou nekonečného cyklu do {...} while(1); a príkazu

break prípadne continue. Riešenie: program c07-1.cpp

2. Znak ‘!’ pred výrazom znamená negáciu (NOT). Na príklade funkcie mocniny overte, že podmienka if (y == 0) ... je ekvivalentná s podmienkou if (!y) ...

Riešenie: program c07-2.cpp

3. Prepíšte funkciu root() tak, že nebude vypisovať matematické chyby, ale: • v prípade záporného čísla pod odmocninou vráti -1 a • v prípade n-tej odmocniny pre n <= 0 vráti –2. Pri pohľade na výpočet funkcie root() si môžete všimnúť, že pri n-tej odmocnine z nuly, kedy sa pre výpočet odmocniny použije vzorec

a pri danom EPSILON sa výsledok 0 nikdy nedosiahne. Preto ošetrite ďalej funkciu root() tak, že v prípade nuly pod odmocninou explicitne vráti výsledok 0.

Riešenie: súbor c07-3.cpp

kk xx ⋅−

=+ n1n

1

32

VIII. lekcia: Riadiace štruktúry – iteračné príkazy Príkaz for

Cieľom tejto lekcie je oboznámiť vás s posledným zo série iteračných príkazov, ktorým je príkaz for. Príkaz for je typický príkaz cyklu, ktorý používame v prípade, že vopred poznáme počet prechodov cyklom. Pomocou príkazu for budeme implementovať matematickú funkciu pre výpočet faktoriálu z nezáporného čísla.

Výstupom tejto lekcie bude funkcia na výpočet n-tej mocniny z reálneho čísla, ktorú využijeme v našom cieľo-vom programe kalkulátor. Vašou úlohou bude zimplementovať túto funkciu pomocou príkazu for.

Výpis programu P08.CPP 1: #include <stdio.h> 2: 3: /* prototyp funkcie */ 4: double faktorial(unsigned short x); 5: 6: void main() 7: { 8: short cislo; 9: 10: printf("Vypocet faktorialu\n------------------\n"); 11: printf("Zadaj cislo: "); 12: scanf("%d", &cislo); 13: 14: /* osetrenie na zaporne cislo a overflow; pre double je max. argument 15: faktorialu cislo 170, pretoze 170! = 7.257415615308E+306 */ 16: if (cislo < 0) 17: printf("MATH Error: Nemozem vyratat faktorial pre zaporne cislo...\n"); 18: else 19: if (cislo > 170) 20: printf("MATH Error: Number overflow for fact()...\n"); 21: else 22: printf("%d! = %.15lG\n\n", cislo, faktorial(cislo)); 23: 24: } 25: 26: /* funkcia pre vypocet faktorialu */ 27: double faktorial(unsigned short x) 28: { 29: double vysledok = 1; 30: unsigned short i; 31: 32: for (i = 2; i <= x; i++) 33: vysledok *= i; 34: 35: return(vysledok); 36: }

Vysvetlivky: short – modifikátor typu unsigned – modifikátor typu for – kľúčové slovo pre príkaz cyklu for

33

Typové modifikátory Typové modifikátory short a unsigned slúžia na zmenu rozsahu čísel, ktoré môže typ (uvedený za nimi) uchová-

vať. Ak sa typ, ku ktorému sa modifikátory vzťahujú, neuvedie (viď. riadky 4, 8, 27 a 30), berie sa za typ implicitne int. Obidva modifikátory môžu byť aplikované iba na celočíselné typy (char a int).

Na platforme i80x86 nemá modifikátor short žiadny význam, pretože číslo typu int aj short (resp. short int) obsadzuje miesto v pamäti o veľkosti 2 bajty, takže rozsah čísel, ktoré dokáže typ short int uchovávať je –32768 až +32767. Ale napríklad na systémoch VAX premenná typu int alokuje 4 bajty pamäti, kým premenná „typu“ short iba 2 bajty.

Modifikátor unsigned mení rozsah uchovávaných čísel tak, že posúva nulu zo stredu intervalu na jeho ľavý okraj, teda mení spodnú (⇒ aj hornú) hranicu rozsahu čísel, ktoré dokáže uchovávať premenná typu, na ktorú sa modifikátor aplikuje. Tak napr. na i80x86 platforme premenná typu int uchováva čísla v rozsahu -32768 až +328767, kým premenná toho istého typu s modifikátorom unsigned dokáže uchovať čísla v rozsahu 0 až 65535.

Ďalším typovým modifikátorom je long, s ktorého využitím sa stretneme v lekcii X – „Typová konverzia“. Na túto chvíľu iba poznamenajme, že premenná typu long resp. long int dokáže na platforme i80x86 uchovávať celé čísla v rozsahu –2 147 483 648 až +2 147 483 647 a premenná typu unsigned long resp. unsigned long int čísla v roz-sahu 0 až 4 294 967 295.

Cyklus for Syntax príkazu for:

for (výraz_štart; výraz_stop; výraz_iter) príkaz;

Poznámky: • Namiesto príkazu môžeme uviesť aj blok príkazov (uzavretý v zložených zátvorkách { a }). • Podobne ako pri cykloch while a do-while môžeme ovplyvniť riadenie prechodu cyklom pomocou príkazov

break a continue (viď. lekciu VI).

Obr. 8-1 Vývojový diagram príkazu for.

false … for výraz_stop

príkaz(y) cyklu

true

výraz_štart

výraz_iter

break

continue

34

Podľa diagramu na obrázku 8-1 môžeme príkaz cyklu for prepísať pomocou cyklu while nasledovne: výraz_štart; while (výraz_stop) { príkaz; výraz_iter; }

Sémantika príkazu for:

Cyklus for prebieha tak, že sa na začiatku vyhodnotí výraz_štart; otestuje sa, či je hodnota výraz_stop pravdivá (TRUE); vykoná sa príkaz a nakoniec sa vykoná výraz_iter. Potom začína ďalšia obrátka cyklom. Cyklus for pracuje tak dlho, kým je výraz_stop splnený, t.j. kým má hodnotu TRUE (nenulovú hodnotu). Telo cyklu (príkaz) sa v príkaze for nemusí vykonať ani raz, a to v prípade, ak hneď na začiatku výraz_stop nadobúda hodnotu FALSE.

Vo funkcii pre výpočet faktoriálu (riadky 27 až 36) pracuje cyklus for nasledovne: Na začiatku (riadok 32) priradí premennej i hodnotu 2, ktorú následne vo výraze_stop porovná s hodnotou x –

číslo, ktorého faktoriál chceme vypočítať. Ak je podmienka (i <= x) splnená, vykoná sa telo príkazu (riadok 33), tzn. uskutoční sa vynásobenie výsledku hodnotou i a potom sa zväčší hodnota premennej i o jedna (výraz_stop v riadku 32). Ďalej sa znovu vykoná test podmienky i <= x a v prípade úspechu sa znovu vykoná telo cyklu.

Telo cyklu sa vykonáva dovtedy, kým je splnená podmienka daná výrazom_stop. Ak hneď na začiatku nie je podmienka splnená (napr. chceme vypočítať 0! alebo 1!), telo cyklu sa nevykoná, ale premenná i už nadobúda hod-notu 2. V tomto prípade sa vykoná až prvý príkaz za cyklom for (riadok 35) t.j. vráti sa hodnota premennej vysledok, ktorá je inicializovaná na 1 hneď pri vstupe do funkcie (riadok 29).

Poznámka: Keďže návratová hodnota funkcie faktorial() je typu double a maximálna hodnota premennej typu double je pri 64-bitovej dĺžke číslo 1.7E+308, vykonávame vo funkcii main() test, či hodnota skutočného parametra funkcie faktorial() nepresahuje hodnotu 170 (riadok 19), pretože 170! = 7.257415615308E+306. Ak by sme totiž volali funkciu s parametrom väčším ako 170, pri násobení premennej vysledok (riadok 33) by došlo k preteče-niu hodnoty, pretože tá je rovnakého typu (riadok 29) ako návratová hodnota funkcie faktorial().

Výrazy výraz_štart, výraz_stop a výraz_iter nemusia spolu vôbec súvisieť a dokonca nemusia byť ani uvedené. Ak niektorý z výrazov nie je uvedený, je potrebné uviesť bodkočiarku „;“, ktorou je tento chýbajúci výraz oddelený od ostatných.

Napr.: Ak využijeme inicializáciu premennej i pri jej definícii, t.j. riadok 30 nahradíme príkazom unsigned short i = 2; potom môžeme cyklus for nahradiť príkazom

alebo

Ak neuvedieme ani jeden z výrazov v riadiacej časti cyklu for, tak zápisom for( ; ; ) označujeme nekoneč-ný cyklus, podobne ako while(1).

for ( ; i <= x; i++) vysledok *= i;

for ( ; i <= x; ) { vysledok *= i; i++; }

35

Cvičenia 1. Prepíšte funkciu faktoriálu pomocou nekonečného cyklu for a príkazov break prípadne continue.

Riešenie: program c08-1.cpp

2. Funkcia faktoriálu v programe P08.CPP počíta faktoriál pre argument x tak, že vykonáva násobenie čísel v postupnosti 2*3*...*x. Prepíšte funkciu faktorial() tak, že násobenie bude realizovať v postupnosti x*(x-1)*...*3*2. V riadiacej časti cyklu for využite operátor dekrementácie (–-), s ktorým ste sa oboznámili v predchádzajúcej lekcii.

Riešenie: program c08-2.cpp

3. Prepíšte funkciu double pow(double x, int y) z predchádzajúcej lekcie pomocou cyklu for. Keďže hodnota premennej y môže byť aj záporná, v riadiacej časti príkazu for použite funkciu abs() vo výraze_stop.

Riešenie: program c08-3.cpp

4. Prepíšte funkciu faktoriálu programu P08.CPP tak, že bude v sebe implementovať ošetrenie na overflow, tzn. vráti hodnotu –1 v prípade, že argument faktoriálu (premenná x) je väčší ako 170.

Riešenie: súbor c08-4.cpp

36

IX. lekcia: Iterácia pomocou rekurzie Cieľom tejto lekcie je oboznámiť vás s pojmom rekurzívna funkcia a využitím rekurzie na iteráciu. Výstupom

tejto lekcie bude funkcia na výpočet faktoriálu realizovaná pomocou rekurzie, ktorú využijeme v cieľovom programe kalkulátor.

Výpis programu P09.CPP 1: #include <stdio.h> 2: 3: /* prototyp funkcie */ 4: int expt(int x, int n); 5: 6: void main() 7: { 8: int cislo, exp; 9: 10: printf("Zadaj dve cele cisla: "); 11: scanf("%d %d", &cislo, &exp); 12: 13: printf("%d^%d = %d\n\n", cislo, exp, expt(cislo, exp)); 14: 15: } 16: 17: /* EXPonenTiation (umocnovanie) * 18: * Funkcia pre vypocet n-tej mocniny z x pomocou rekurzie * 19: * */ 20: int expt(int x, int n) 21: { 22: if (n == 0) 23: return 1; 24: else 25: return (x * expt(x, n - 1)); 26: }

Rekurzívna funkcia Pojem rekurzia znamená situáciu, v ktorej funkcia volá samú seba, a to buď priamo alebo nepriamo. Funkciu

v tomto prípade nazývame rekurzívnou. Nepriama rekurzia nastáva vtedy, keď jedna funkcia volá inú funkciu, ktorá potom opäť volá prvú funkciu. Priama rekurzia znamená, že v tele funkcie vykonávame volanie tej istej funkcie, t.j. samej seba.

Rekurziu môžeme použiť napr. na výpočet faktoriálu z čísla. Faktoriál čísla x označujeme x! a počíta sa nasle-dovne: x! = x * (x – 1) * (x – 2) * (x – 3) * ... * (2) * 1

Jeden zo spôsobov, ako možno vypočítať x! je: x! = x * (x – 1)!

Pokračujúc ďalším krokom, výpočet (x – 1)! môžeme realizovať použitím rovnakého pravidla: (x – 1)! = (x – 1) * (x – 2)!

Takto by sme pokračovali vo výpočte rekurzívne, až kým nenarazíme na číslo 0! (= 1), kedy výpočet končí.

Avšak predtým než zimplementujete rekurzívnu funkciu faktoriál, ukážeme si rekurziu na príklade funkcie, ktorá počíta n-tú mocninu čísla x. xn môžeme počítať tiež rekurzívne, a to tak, že využijeme vzťah: xn = x * xn-1, čo opäť vedie k rekurzii. V tomto výpočte pokračujeme, až kým n nie je rovné 0, kedy rekurzia končí, pretože x0 = 1.

Naša rekurzívna funkcia expt(), ktorá realizuje práve uvedený spôsob výpočtu n-tej mocniny čísla x, sa nachá-dza v riadkoch 20 až 26 (viď. výpis programu P09.CPP). Hodnota čísla, ktorého mocninu hľadáme, sa prenáša cez argument x (samozrejme, že nemusí byť iba celočíselného typu) a exponent sa prenáša cez argument n (riadok 20).

37

V riadku 22 sa kontroluje hodnota exponentu n. Ak nadobúda hodnotu 0, funkcia vráti číslo 1 (riadok 23), ináč funkcia vráti číslo x vynásobené hodnotou rekurzívne volanej funkcie expt() s argumentmi x a (n-1) (riadok 25). Funkcia teda volá samú seba, ale už s exponentom o jedna menším (n-1). Ak (n-1) nie je 0, funkcia expt() sa znovu volá s argumentmi x a ((n-1)-1), čo je totožné s volaním funkcie s argumentmi x a (n-2). Tento proces volania funkcie pokračuje, až kým nie je splnená podmienka uvedená v riadku 22.

Ak napríklad počítame hodnotu 23, funkcia expt() počíta mocninu nasledovne (v zátvorkách sú zobrazené dvoji-ce, ktoré reprezentujú argumenty funkcie):

2 * (2, (3 – 1)) * (2, ((3 – 1) – 1)) * (2, (((3 – 1) – 1) – 1)) Posledný výraz sa vyhodnotí ako číslo 1 a potom sa vykoná násobenie hrubo vytlačených čísel, teda 2*2*2*1.

Funkcia expt() je príkladom vykonania iterácie pomocou rekurzie.

Pozor! • Pri rekurzívnom volaní funkcie, musíme správne ošetriť zastavovaciu podmienku, ináč sa môže stať rekurzia nekonečnou – čo väčšinou vedie k havarovaniu programu na pretečení zásobníka. Napr. vo funkcii main() pred samotným volaním funkcie expt() nie je prevedená kontrola, či hodnota premennej exp (udáva exponent mocniny) je nezáporné číslo. Ak teda používateľ zadá druhé číslo menšie ako nula, rekurzia sa stane nekonečnou. • Neodporúčame používať rekurziu, ak sa môže vykonať príliš veľa iterácií (opakovaní príkazu). Rekurzia totiž využíva veľa pamäti (každé volanie funkcie obsadzuje miesto v zásobníku – na odovzdanie parametrov funkcii a uchovanie návratovej adresy; miesto v pamäti pre lokálne premenné sa tiež vymedzuje v zásobníku). Preto rekurziu používajte iba pri funkciách s plytkou rekurziou, t.j. s malým počtom vnáraní a s malým počtom lokálnych premenných.

Cvičenia 1. Nahraďte konštrukciu if–else v tele funkcie expt() ternárnym operátorom.

Riešenie: program c09-1.cpp

2. Napíšte pomocou rekurzie funkciu s prototypom int suma(int n), ktorá vráti sumu prvých n prirodzených čísel, t.j. vypočíta hodnotu 1 + 2 + ... + n. Ošetrite zastavovaciu podmienku rekurzie tak, že v prípade záporného čísla vo volaní funkcie vráti hodnotu 0.

Riešenie: program c09-2.cpp

3. Prepíšte funkciu faktorial() z príkladu P08.CPP pomocou rekurzie. Riešenie: program c09-3.cpp

4. Prepíšte funkciu pow(double x, int y) z príkladu P07.CPP pomocou rekurzie. Funkcia teda musí, na rozdiel od funkcie expt() uvedenej v tejto lekcii, „vedieť“ vypočítať aj zápornú mocninu.

Riešenie: program c09-4.cpp

5. Prepíšte funkciu z cvičenia 3 tak, aby ošetrovala number overflow rovnako ako v cvičení 4 lekcie VIII, t.j. vráti hodnotu -1 v prípade, že formálny parameter faktoriálu (premenná x) je väčší ako 170.

Riešenie: súbor c09-5.cpp

38

X. lekcia: Typová konverzia Cieľom tejto lekcie je oboznámiť vás s pojmom typová konverzia a jeho využitím pri programovaní v jazyku C.

Naučíme sa, že jazyk C rozpoznáva dva druhy typovej konverzie. S typovou konverziou sa budete v praxi často stretávať. Napríklad implementácia najrôznejších abstraktných údajových typov (napr. zreťazený zoznam), vyžaduje typovú konverziu smerníkov pri dynamickom prideľovaní pamäti.

Výstupom tejto lekcie budú matematické funkcie určené na rôzne typy zaokrúhľovania, ktoré využijeme v cieľovom programe kalkulátor. Vašou úlohou v cvičeniach bude implementovať ich pomocou typovej konverzie. Výpis programu P10.CPP 1: #include <stdio.h> 2: 3: /* prototyp funkcie */ 4: long trunc_impl(long x); 5: long trunc_expl(double x); 6: 7: void main() 8: { 9: double cislo; 10: 11: printf("Zadaj realne cislo: "); 12: scanf("%lf", &cislo); 13: 14: printf("Realne cislo %lf prevedene na cele cislo = %ld\n", 15: cislo, trunc_impl(cislo)); 16: printf("Realne cislo %lf prevedene na cele cislo = %ld\n\n", 17: cislo, trunc_expl(cislo)); 18: 19: } 20: 21: /* Priklad implicitnej typovej konverzie */ 22: long trunc_impl(long x) 23: { 24: return(x); 25: } 26: 27: /* Priklad explicitnej typovej konverzie */ 28: long trunc_expl(double x) 29: { 30: return((long) x); 31: }

Vysvetlivky: (long) – pretypovanie na typ long

Typová konverzia Pod pojmom typová konverzia sa rozumie prevod premennej alebo konštanty určitého typu na iný typ, napr.

long na double.

Jazyk C rozoznáva dva druhy typovej konverzie: • implicitná alebo automatická • explicitná alebo vynútená

Príklad implicitnej typovej konverzie vidíme na príklade volania funkcie trunc_impl (riadok 15) s argumen-tom typu reálne číslo double (riadok 9). Formálny parameter funkcie trunc_impl (premenná x) je však celočíselného typu long (riadok 22). Pri volaní funkcie s aktuálnym parametrom cislo, ktoré je typu double, sa toto

39

reálne číslo skonvertuje na celé číslo a to tak, že sa časť čísla za desatinnou čiarkou „usekne“ (oreže) – preto názov funkcie trunc_ (z anglického truncate). Konverzia sa volá implicitná preto, lebo nie sme schopní ovplyvniť tento typ konverzie (ovplyvniť ju môžeme samozrejme pomocou explicitnej typovej konverzie).

Iný príklad, kedy by sa využila implicitná typová konverzia, by bol, keby sa funkcia volala s parametrom typu int. Vtedy by sa parameter skonvertoval na typ long bez skreslenia hodnoty (žiadne orezávanie čísla).

Príklad explicitnej typovej konverzie je zas uvedený na príklade funkcie trunc_expl. Funkcia má formálny parameter reálne číslo x typu double (riadok 28). Funkciu voláme s aktuálnym parametrom cislo (riadok 17), ktoré je tiež typu double, takže v tomto prípade volania funkcie sa žiadna konverzia typov nevykonáva. Pri návrate z funkcie (riadok 30) je však premenná x typu double pomocou “(long)“ explicitne pretypovaná na typ long.

Explicitná konverzia sa tiež nazýva pretypovanie (casting alebo typecasting) a má formu: (typ) výraz,

ktorá znamená, že výraz (alebo premenná) je v čase prekladu konvertovaný na požadovaný typ.

V úvode sme spomenuli, že pod typovou konverziou sa rozumie aj prevod typu konštanty. Najčastejší dôvod explicitného pretypovávania je pri použití operácie delenia, napr.:

float f; f = (float) 5 / 3;

Ak by sme totiž nepoužili pretypovanie (float), delenie by sa vykonalo ako celočíselné a hodnota f by bola 1 a nie očakávaných 1.666.

Poznámka: Niekedy je explicitné pretypovanie nutné, aby sme sa zbavili pri preklade varovných hlásení o nevhod-nom type parametra.

Cvičenia 1. Napíšte ešte iný spôsob uplatnenia implicitnej typovej konverzie na príklade funkcie s prototypom

long trunc_impl(double x); Riešenie: súbor c10-1.cpp

2. Napíšte funkciu s prototypom long ceil(double x); ktorá vykonáva zaokrúhľovanie nahor. Inými slovami: Funkcia ceil() má nájsť najmenšie celé číslo nie menšie ako x.

Riešenie: súbor c10-2.cpp

3. Napíšte funkciu s prototypom long floor(double x); ktorá vykonáva zaokrúhľovanie nadol. Inými slovami: Funkcia floor() má nájsť najväčšie celé číslo nie väčšie ako x.

Riešenie: súbor c10-3.cpp

4. Pomocou funkcií z predchádzajúcich dvoch cvičení, funkcie abs() z cvičenia 2 lekcie IV a funkcie trunc_expl() z programu P10.CPP napíšte funkciu s prototypom long round(double x); ktorá zaokrúhli reálne číslo x na nula desatinných miest, t.j. vráti zaokrúhlené celé číslo. Pozor! round(-x) = -round(x)

Riešenie: súbor c10-4.cpp

40

XI. lekcia: Preprocesor jazyka C Cieľom tejto lekcie je naučiť vás pracovať s preprocesorom jazyka C, ktorého príkazy sa využívajú takmer

vo všetkých programoch. Všetky knižničné funkcie majú prototypy v hlavičkových súboroch, a tie je potrebné do našich programov vkladať práve pomocou príkazov preprocesora. Lekcia vás zoznámi s veľmi dôležitou vlastnosťou programovania v jazyku C, a tou je podmienený preklad. V lekcii sa ďalej naučíte, ako môžete optimalizovať program z hľadiska rýchlosti vykonávania výpočtu na počítači.

Výstupom tejto lekcie budú matematické funkcie (absolútna hodnota, druhá mocnina, zaokrúhľovanie na celé čísla a orezanie reálneho čísla) a štatistické funkcie (maximálna a minimálna hodnota), ktoré priamo využijeme v na-šom cieľovom programe kalkulátor. Okrem týchto funkcií bude vašou úlohou v cvičeniach zimplementovať funkcie pomocou makier s parametrami, ktoré využijeme pri výstavbe lexikálneho analyzátora a pri tvorbe funkcie konvertu-júcej reťazec na reálne číslo.

Výpis programu P11.CPP 1: #include <stdio.h> 2: #include <math.h> 3: 4: /* -- definicia makier bez parametrov -- */ 5: /* makro bez hodnoty */ 6: #define debug 7: /* presnost pre vypocet odmocniny */ 8: #define EPSILON 1E-9 9: 10: /* -- definicia makier s parametrom -- */ 11: #define sqr(x) ((x) * (x)) 12: #define abs(x) ((x) < 0 ? -(x) : (x)) 13: 14: /* -- prototyp funkcie -- */ 15: double root(double cislo, int n); 16: 17: /* -- hlavna funkcia -- */ 18: void main() 19: { 20: double x, vysledok; 21: int n; 22: 23: printf("Zadaj cislo, ktore chces odmocnit: "); 24: scanf("%lf", &x); 25: printf("Zadaj n (cele cislo) urcujuce n-tu odmocninu: "); 26: scanf("%d", &n); 27: 28: vysledok = root(x, n); 29: printf("%d. odmocnina z %lG je %.15lG\n", n, x, vysledok); 30: 31: printf("%lG^2 = %lG\n", vysledok, sqr(vysledok)); 32: 33: #ifdef debug 34: #undef sqr 35: #define sqr(x) (x * x) 36: printf("Volanie \"funkcie\" sqr po nespravnej definicia makra:\n"); 37: printf("sqr(2+3) = %d\n\n", sqr(2+3)); 38: #else 39: printf("sqr(2+3) = %d\n", sqr(2+3)); 40: #endif 41: 42: #ifndef debug 43: printf("Makro pre funkciu sqr bolo spravne definovane!\n\n"); 44: #endif 45: } 46:

41

47: /* Funkcia root() z prikladu P07.CPP bez definovania * 48: * konstantnej premennej EPSILON */ 49: double root(double cislo, int n) 50: { 51: double x_old = 1, /* x v kroku k */ 52: x_new, /* x v kroku k+1 */ 53: tmp; /* pomocna premenna na uschovanie povodnej hodnoty 54: x_old, t.j. este pred jej aktualizaciou */ 55: 56: if (cislo < 0) 57: { 58: printf("MATH Error: zaporne cislo pod odmocninou\n"); 59: return(0); 60: } 61: 62: if (n <= 0) 63: { 64: printf("MATH Error: nemozem vyratat n-tu odmocninu pre n <= 0\nn = %d\n", n); 65: return(0); 66: } 67: 68: do { 69: x_new = ((n - 1) * x_old + cislo/pow(x_old, n - 1))/n; 70: tmp = x_old; 71: x_old = x_new; 72: } while (abs(x_new - tmp) > EPSILON); 73: 74: return(x_new); 75: }

Vysvetlivky: #include – vloženie súboru #define – definovanie makra #undef – zrušenie definície makra #ifdef meno_makra – podmienený preklad textu v závislosti na tom, #else #endif či je makro meno_makra definované alebo nedefinované #infdef meno_makra – podmienený preklad textu v závislosti na tom, #else #endif či je makro meno_makra nedefinované alebo definované

Preprocesor jazyka C Preprocesor je súčasť každého prekladača jazyka C. Keď prekladáte program napísaný v jazyku C, preprocesor

je prvá zložka prekladača, ktorá spracováva program. Ak spustíte preklad programu, prekladač automaticky spúšťa preprocesor.

Preprocesor mení obsah zdrojového kódu na základe inštrukcií alebo direktív preprocesora (sú to vlastne príkazy pre preprocesor). Výstupom preprocesora je modifikovaný zdrojový kód, ktorý sa potom použije ako vstup pre ďalší krok kompilácie programu – samotný preklad. Súbor po spracovaní preprocesorom za normálnych okolností nikdy nevidíte, pretože prekladač ho ihneď po zostavení programu (prípadne po neúspešnom preklade, ak sa vyskytla chy-ba) vymaže.

Preprocesor jazyka C sa dá charakterizovať nasledovnými bodmi: § spracováva zdrojový text PRED samotným prekladom § nekontroluje syntaktickú správnosť programu § vykonáva iba zámenu textov, napr. identifikátorov konštánt za odpovedajúce číselné hodnoty – tzv. rozvoj

makier (macro processing) § vypúšťa zo zdrojového textu všetky komentáre § vykonáva podmienený preklad

Príkazy pre preprocesor musia byť uvedené na samostatnom riadku a musia začínať znakom # (hash mark).

} }

42

S príkazom preprocesora #include sme sa už oboznámili v prvej lekcii. Pre zopakovanie pripomíname, že direktíva nariaďuje preprocesoru, aby obsah súboru uvedeného za #include vložil do volajúceho súboru (t.j. do to-ho, ktorý príkaz #include obsahuje) na miesto, v ktorom sa príkaz #include nachádza. Príkaz #include má dva tvary: § #include "meno_suboru" – preprocesor hľadá súbor s názvom meno_suboru v rovnakom adresári, v ktorom

sa nachádza „volajúci“ súbor. Tento tvar sa používa zväčša pre prácu so súbormi, ktoré sme vytvorili my sami (použitie viď. cvičenie 2 lekcie XVI a nasledujúce lekcie).

§ #include <meno_suboru> – preprocesor hľadá súbor s názvom meno_suboru v systémovom adresári (uvedenom v nastavení prekladača). Používa sa pre prácu so štandardnými hlavičkovými súbormi.

V našom ukážkovom príklade sme vložili dva štandardné hlavičkové súbory: stdio.h (riadok 1) – pre prácu s príkazmi štandardného vstupu a výstupu (printf() a scanf()) a math.h (riadok 2) – pre vloženie prototypu funkcie pow() z knižnice MATH.

Makrá bez parametrov – príkaz #define Riadky 6 a 8 predstavujú makrá bez parametrov, ktoré sa často nazývajú symbolické konštanty. Tieto konštanty

zbavujú programy „magických“ čísiel, t.j. najrôznejších číselných konštánt, ktoré sa bez vysvetlenia objavujú v programe. Väčšinou sa konštanty definujú na začiatku programu (modulu), no najčastejšie v hlavičkových súboroch. Ich použitie zvyšuje modularitu programu. Náhrada symbolickej konštanty skutočnou hodnotou sa nazýva rozvojom (expanziou, substitúciou) makra.

Napr. v riadku 8 sme zadefinovali konštantu EPSILON, ktorej hodnotu využívame vo funkcii root() (riadok 72). Ak by sme namiesto tejto symbolickej konštanty mali uvedenú číselnú konštantu 1e-9, nemuselo by byť na prvý pohľad jasné, že sa jedná o presnosť výpočtu, ktorú v matematike zvyčajne označujeme epsilonom.

Ďalšou výhodou symbolických konštánt je už spomenuté zvýšenie modularity programu. Predstavte si, že by ste mali zadefinovaných 5 funkcií v tisícriadkovom kóde programu, z ktorých každá by mala použitú rovnakú číselnú konštantu, prípadne v niektorej funkcii by bolo zadefinované namiesto 1e-9 číslo 0.000000001, a chceli by ste zvýšiť presnosť výpočtu. Pokiaľ by ste nevyužili symbolickú konštantu, museli by ste prechádzať celým zdrojovým kódom a nahradiť každú číselnú konštantu novou hodnotou. V prípade použitia makra bez parametra, stačí zmeniť presnosť (hodnotu) iba na jednom mieste (v mieste definície konštanty).

Pozor! Za hodnotou v definícii makra nie je (v 99%) bodkočiarka.

Symbolická konštanta sa môže objaviť kdekoľvek v programe s jedinou výnimkou – nemala by byť súčasťou reťazca (medzi úvodzovkami), pretože tam k rozvoju konštanty (rozvoju makra) nedôjde.

Symbolická konštanta platí od miesta jej definície až do konca súboru (nie programu! Program môže pozostávať z viacerých súborov.), v ktorom bola definovaná.

V riadku 6 sme definovali konštantu debug, ktorej hodnota nie je špecifikovaná. Ako uvidíme ďalej v tejto lekcii, na jej hodnote nám nezáleží, podstatné je iba to, že je definovaná.

Makrá s parametrami Riadky 11, 12 a 35 predstavujú makrá s jedným parametrom. Všeobecná definícia makra s N parametrami má

nasledujúcu syntax: #define meno_makra(arg1, arg2, ..., argN) hodnota_makra Syntax volania je meno_makra(par1, par2, ..., parN);

Pozor! Medzi menom makra a otvárajúcou okrúhlou zátvorkou “(“ nesmie byť medzera. Argumenty by potom boli považované za hodnotu makra.

Väčšina našich doteraz definovaných funkcií vykonávala jednoduchú úlohu, ktorá sa dala napísať jedným prípadne dvomi príkazmi (napr. max(x,y) z príkladu P01.CPP, abs(x) z cvičenia 2 lekcie IV, funkcie z lekcie V, atď.). Tieto funkcie tým, že sú veľmi krátke, majú omnoho väčšiu administratívu, než je ich samotný užitočný kód. Pod administratívou rozumej predanie parametrov (zaberajú miesto v zásobníku), úschovu návratovej adresy, skok

43

do funkcie, návrat z funkcie do miesta volania a výber použitých parametrov. Táto administratíva samozrejme zdržuje výpočet programu. Ak použijeme makrá s parametrami (ako je uvedené v riadkoch 11, 12 a 35), réžie spojenej s volaním funkcie sa úplne zbavíme, zvýši sa rýchlosť vykonávania programu, ale zároveň sa zväčší dĺžka kódu programu.

Makrá s parametrami sa nazývajú aj vkladané funkcie (in-line functions), pretože na rozdiel od skutočných funkcií sa makrá s parametrami nevolajú, ale pred prekladom nahradí preprocesor meno makra konkrétnym textom. Praktické využitie je teda iba pre veľmi krátke funkcie.

Tak napríklad #define sqr(x) ((x) * (x)) spôsobí, že pri volaní sqr(vysledok) (riadok 31) sa pred samotným prekladom nahradí tento text za (vysledok) * (vysledok).

Všimnite si, že argument x v definícii makra je uzavretý do zátvoriek. Ak tak nespravíme, je veľká pravdepodobnosť, že program nebude pracovať korektne. Uvažujme napríklad zlú definíciu tohto makra (riadok 35). Pri volaní sqr(2+3) sa makro rozvinie do 2+3*2+3 a výsledok zjavne nebude očakávaných 25.

Je dobré uvádzať i vonkajšie zátvorky okolo hodnoty makra, pretože napr.: #define citaj(c) c = getchar() sa po volaní if (citaj(x) == 'a') rozvinie do logickej chyby: if (x = getchar() == 'a'). Poznámka: Operátor porovnania (==) má vyššiu prioritu ako príkaz priradenia (=).

Pozor! Ak sa objaví argument v hodnote makra viackrát, vtedy by makro nemalo byť volané s aktuálnym para-metrom, ktorý môže mať vedľajšie účinky, napr. v našej definícii makra sqr po volaní sqr(i++) bude premenná i inkrementovaná dvakrát, čo zrejme nie je správne.

U makier s parametrami nemožno – na rozdiel od funkcií – použiť rekurziu! ANSI C preprocesor zabraňuje prípadnej nekonečnej rekurzii makier tým, že potláča náhradu makra v jeho vlastnej definícii.

Ďalšou výhodou použitia makier s parametrami je, že nemusíme špecifikovať, akého typu sú argumenty a návratová hodnota, ako je to u funkcie. Napríklad predpokladajme nasledovnú definíciu funkcie abs: int abs(int x) { return(x < 0 ? -x : x); }

Po volaní printf("%f", abs(-2.5)); program zhavaruje, pretože podľa riadiaceho reťazca formátu očakáva hodnotu typu float. Ak však použijeme hore uvedenú definíciu „funkcie abs()“ pomocou makra, nič sa nestane, pretože makro sa rozvinie a teda nešpecifikuje sa žiadny typ. Výhoda teda je, že jednu funkciu abs() môžeme použiť aj na celočíselné typy, aj na typy reálnych čísel.

Zrušenie definície makra

Ak z nejakého dôvodu potrebujeme zrušiť predtým definované makro, použijeme direktívu #undef, ktorej syntax je: #undef meno_makra

Sémantika je nasledovná: Direktíva zruší makro meno_makra, tzn. makro meno_makra je platné od jeho definí-cie až po najbližší #undef pre toto makro alebo do konca súboru, v ktorom bolo makro definované.

Zrušenie makra používame najčastejšie vtedy, keď potrebujeme makro redefinovať. Napr. v našom príklade sme v riadku 34 zrušili starú (korektnú) definíciu makra sqr, aby sme v nasledujúcom riadku mohli zadefinovať schválne nesprávnu definíciu makra (z inštruktívnych dôvodov).

Podmienený preklad riadený definíciou makra Zložité a veľké programy väčšinou píšeme s ladiacimi časťami (pomocné výpisy), aj keď máme k dispozícii

výkonný debugger (ladiaci prostriedok). Po odladení programu však nastáva typický problém, ako tieto ladiace časti, ktoré vypisujú už nepotrebné (a teda nevhodné) informácie a zdržujú vykonávanie programu, z odladeného programu odstrániť. Najčastejšie ich „zakomentujeme“, no tu vyvstáva problém vnorených komentárov, ktoré ako už vieme, sú v ANSI C zakázané. Inou alternatívou býva vymazanie týchto ladiacich častí, ale aj toto riešenie so sebou prináša niektoré úskalia. Napríklad v „mazacej eufórii“ vymažeme aj to, čo sme vymazať nechceli (najčastejšie sú to zdieľa-né premenné definované v ladiacej časti, ktoré sú využívané aj v programe) a program potom nepracuje.

44

Jazyk C na riešenie tohto problému poskytuje príkazy preprocesora, pomocou ktorých môžeme určiť, ktoré časti programu sa majú prekladať a ktoré nie – od toho je odvodený názov podmienený preklad. To znamená, že všetky ladiace časti už pri vytváraní programu označujeme ako podmienene prekladané a pri ladení ich prekladáme a po odladení ich neprekladáme. Ladiace časti sú tak trvalou súčasťou zdrojového súboru, ale voliteľnou súčasťou programu. Preprocesor potom na náš jediný príkaz všetky ladiace časti vypustí sám, ale ak budú niekedy v budúcnosti potrebné, potom ich jediným príkazom opäť do programu zaradíme.

Na riadkoch 33, 38 a 40 sme použili príkazy preprocesora používané pre podmienený preklad, ktoré sú veľmi podobné známej riadiacej konštrukcii if–else.

#ifdef symbolicka_konstanta čast1 #else čast2 #endif

Prekladač túto časť programu spracováva tak, že ak je symbolicka_konstanta definovaná (na jej hodnote nezáleží ⇒ môže byť aj 0), prekladá sa iba čast1, ináč sa prekladá iba čast2. Časť #else podobne ako pri príkaze if-else nie je povinná.

V našom príklade sme na začiatku programu definovali konštantu debug, takže sa preloží časť zdrojového kódu medzi #ifdef debug a #else (riadky 34 až 37 včítane). Ak chceme zrušiť ladiaci režim, stačí definíciu konštanty debug „zakomentovať“.

K dispozícii je ešte jeden príkaz preprocesora (#ifndef ... #else .., #endif), ktorý je iba negáciou predchádzajúceho, čiže je riadený tým, či symbolická konštanta nebola definovaná (viď. riadky 42 až 44).

Cvičenia 1. Odskúšajte funkciu zátvoriek okolo argumentu x v definícii makra abs (riadok 12) pre volanie s parametrom

výrazu 2-5.

Riešenie: program c11-1.cpp

2. Napíšte makro trunc(x), ktoré vykoná pretypovanie na typ long, t.j. prepíšte funkciu trunc_expl() z predchádzajúcej lekcie pomocou makra s parametrom.

Riešenie: súbor c11-2.cpp

3. Prepíšte funkcie isdigit(), islower() a toupper() z príkladu P05.CPP pomocou makier s parametrami. V definícii makra toupper() využite predtým definované makro islower().

Riešenie: súbor c11-3.cpp

4. Prepíšte funkcie max() a min() z cvičenia 3 a 4 lekcie IV pomocou makier s parametrami.

Riešenie: súbor c11-4.cpp

5. Prepíšte funkciu round() z cvičenia 4 predchádzajúcej lekcie pomocou makra trunc(x) definovaného v cvičení 2.

Riešenie: súbor c11-5.cpp

45

Tipy a triky Tip 1: Viacriadkové makro (napr. pre niekoľko príkazov, ktoré sa často opakujú v programe, môžeme definovať

makro) zadefinujeme pomocou konštrukcie: #define meno_makra {prikaz1; \ prikaz2; \ ... \ prikazN; \ }

Tip2: Makrá, ktoré ste definovali v cvičení 3 (isdigit, islower a toupper) a ešte mnoho ďalších užitočných makier, sa štandardne nachádzajú aj v hlavičkovom súbore ctype.h. V našom programe kalkulátor sme ich definovali sami z inštruktívnych dôvodov.

Tip3: Ak hlási prekladač nejakú chybu, na ktorú nemôžete prísť, je vhodné prehliadnuť si súbor po spracovaní preprocesorom. Chyba totiž môže byť v definícii makra (najčastejšie nesprávny počet zátvoriek) a po rozvo-ji makra nedokáže prekladač správne identifikovať riadok, v ktorom sa vyskytla chyba. V prekladači jazyka C od fy. Borland spustíte preprocesor pomocou príkazu CPP.EXE PROGRAM.CPP, ktorý vygeneruje súbor s názvom PROGRAM.I – to je už súbor po spracovaní preprocesorom, tesne pred prekladom. Vyskúšajte si použitie samostatného preprocesora na nasledujúcom príklade:

#define sqr(x) (x * x) void main() { int x; x=sqr(3+2); }

Opravte definíciu makra sqr a vygenerujte znovu súbor po spracovaní preprocesorom. Porovnajte obidva výstupné súbory.

Tip4: Ešte raz zdôrazňujeme, že treba dávať veľký pozor, ak sa objaví argument v hodnote makra viackrát. Makro by v tomto prípade nemalo byť volané s aktuálnym parametrom, ktorý môže mať vedľajšie účinky. Napr. ak zavoláme makro toupper() z cvičenia 3 s parametrom funkcie na získanie stlačeného klávesu, teda:

if (toupper(getche()) == 'A') ... tak po spustení bude program očakávať dve alebo tri stlačenia klávesu v závislosti od splnenia podmienky – viď. rozvinutie makra toupper(). Ak chceme zabezpečiť správnu funkciu programu, musíme si zadefino-vať premennú, do ktorej priradíme návratovú hodnotu funkcie getche() a až potom môžeme napísať horeuvedený príkaz, tzn. napríklad:

#include <conio.h> ... char ch; ... ch = getche(); if (toupper(ch) == 'A') ...

46

XII. lekcia: Unárne operátory ++ a –– Reťazce ako jednorozmerné polia

V tejto lekcii sa oboznámite s unárnymi operátormi ++ a –– v jazyku C a ich použitím v prefixovom a v post-fixovom tvare. Naučíte sa, čo sú to jednorozmerné polia a ako sú reprezentované v pamäti. Ďalej sa naučíte pracovať s reťazcami a dozviete sa, ako reťazce a jednorozmerné polia navzájom súvisia.

Výstupom tejto lekcie bude kostra programu kalkulátor, ktorú predstavuje procedúra interaktiv(). Úlohou procedúry je získavať od používateľa reťazce, ktoré obsahujú matematické výrazy. Procedúra číta reťazce zadané z klávesnice, až kým používateľ nezadá výraz začínajúci dvojicou znakov „/q“ (napr. /quit). Reťazce sú kontrolované na správny počet a poradie ľavých a pravých okrúhlych zátvoriek.

Výpis programu P12.CPP 1: #include <stdio.h> 2: #include <conio.h> /* kvoli volaniu clrscr(); */ 3: #include <string.h> /* kvoli volaniu strnicmp(); */ 4: 5: /* definicia symbolickej konstanty */ 6: #define LINE_LEN 255 7: 8: /* prototypy funkcii */ 9: int skontroluj_zatvorky(char str[]); 10: void interaktiv(void); 11: 12: void main() 13: { 14: interaktiv(); 15: } 16: void interaktiv(void) 17: { 18: int i = 0; /* pocitadlo prikazov */ 19: char string[LINE_LEN]; /* retazec nacitany z klavesnice */ 20: 21: clrscr(); 22: 23: while(1) 24: { 25: printf("%d> ", ++i); 26: gets(string); 27: 28: if (strnicmp("/q", string, 2) == 0) 29: return; 30: else 31: if (!skontroluj_zatvorky(string)) 32: { 33: puts("Chyba: Nespravny pocet zatvoriek alebo ich poradie."); 34: continue; 35: } 36: } 37: } 38: 39: /* Funkcia skontroluje, ci su v retazci str vsetky zatvorky sparovane 40: * a ci su v spravnom poradi. V pripade chyby vrati 0, inac 1. 41: */ 42: int skontroluj_zatvorky(char str[]) 43: { 44: int pocet = 0; /* udava pocet otvorenych zatvoriek */ 45: int i; /* index v retazci (poli znakov) str */ 46: 47: for ( i = 0; str[i] != '\0'; i++)

47

48: { 49: if (str[i] == '(') pocet++; 50: if (str[i] == ')') 51: { 52: pocet--; 53: 54: /* nespravne poradie, ')' predchadza '(' */ 55: if (pocet < 0) return(0); 56: } 57: } 58: 59: /* pocet > 0 => zostali otvorene zatvorky */ 60: if (pocet) return(0); 61: 62: /* pocet == 0 => spravny pocet parov zatvoriek */ 63: return(1); 64: }

Vysvetlivky: ++i – inkrementácia premennej i v prefixovom tvare i++ – inkrementácia premennej i v postfixovom tvare pocet-- – dekrementácia premennej pocet char string[LINE_LEN]; – definícia poľa string o veľkosti 255 (= hodnota konštanty LINE_LEN) znakov str[i] – i-ty prvok v poli str clrscr(); – procedúra na vyčistenie obrazovky gets(...); – načítanie reťazca z klávesnice puts(...); – vypísanie reťazca na obrazovku a odriadkovanie strnicmp(...) – porovnanie častí dvoch reťazcov bez rozlišovania malých a veľkých písmen

Unárne operátory ++ a –– ++ predstavuje operátor inkrementácie (zväčšenie o jedničku) -- predstavuje operátor dekrementácie (zmenšenie o jedničku)

Oba výrazy sa dajú použiť ako predpony (prefix) aj ako prípony (postfix) s nasledovným významom: ++premenná – premenná je inkrementovaná pred použitím, tzn. že najprv sa hodnota premennej zväčší

o jedničku a až potom sa táto nová hodnota použije vo výraze, v ktorom sa premenná nachádza. premenná++ – premenná je inkrementovaná po použití, tzn. že najprv sa vráti pôvodná hodnota výrazu,

v ktorom sa premenná nachádza a až potom sa hodnota premennej zväčší o jedničku.

V procedúre interaktiv() na riadku 18 je premenná i inicializovaná na hodnotu 0. V riadku 25 sa najskôr premenná i zväčší o jedničku a až potom sa vyhodnotí vyraz „i”, t.j. vráti sa hodnota i do funkcie printf(). Takže pri prvom prechode cyklom sa vypíše“1> “ a nie “0> “. Ak by sme použili postfixový zápis (i++), tak podľa definície by sa najskôr vrátila hodnota premennej i a až potom by sa vykonala inkrementácia.

Inkrementácia/dekrementácia premenných v postfixovom tvare (riadky 47, 49 a 52) je použitá iba vo význame skráteného zápisu, teda namiesto premenná = premenná + 1 sa píše premenná++.

Jednorozmerné polia Pole predstavuje dátovú štruktúru zloženú z rovnakých prvkov. V C majú polia dolnú hranicu vždy 0, teda pole

začína prvkom s indexom 0! Príkaz char string[LINE_LEN]; alokuje miesto v pamäti pre LINE_LEN prvkov typu char, pričom rozsah

indexov je 0 až (LINE_LEN – 1). Hodnota LINE_LEN musí byť známa v čase prekladu, čiže musí to byť konštantný výraz – najčastejšie sa používa symbolická konštanta.

48

K jednotlivým prvkom poľa sa pristupuje pomocou indexu. K prvému prvku poľa pristúpime pomocou string[0], k druhému pomocou string[1] a v našom príklade posledný prvok poľa je string[254] resp. string[LINE_LEN - 1].

Prvok string[255] už nie je súčasťou poľa – pamäťová adresa tohto prvku už nepatrí poľu! Jazyk C zásadne nekontroluje hranice poľa a preto pri pokuse pristúpiť k prvku mimo poľa prekladač nehlási žiadnu chybu ani varovanie.

Prvky poľa sú uložené v pamäti sekvenčne (v postupnosti za sebou). V riadku 19 sme definovali pole s názvom string o veľkosti 255 znakov. Ak predpokladáme, že veľkosť typu char je jeden bajt a začiatok poľa leží na adrese 100, tak potom pole je v pamäti reprezentované nasledovne:

Obr. 12-1 Príklad reprezentácie poľa string (z programu P12.CPP) v pamäti.

Pole ako parameter funkcie Pole môže byť samozrejme parametrom funkcie. Skutočný parameter sa do funkcie prenesie odkazom, tzn. že

pomocou mena poľa sa predá adresa začiatku poľa. To má však ten význam, že položky poľa môžu byť vo funkcii zmenené a túto zmenu si ponechajú aj po opustení funkcie.

Ak v našej funkcii skontroluj_zatvorky() vykonáme príkaz str[0]='a';, tak po opustení funkcie a náv-rate na miesto volania (riadok 31) bude v reťazci string prvý znak 'a'.

Pole ako formálny parameter je špecifikované identifikátorom nasledovaným prázdnymi hranatými zátvorkami „[]“ (viď. riadok 42). Volanie funkcie sa prevedie pomocou odovzdania mena poľa (v skutočnosti sa prenesie adresa začiatku poľa) tak, ako je to uvedené v riadku 31. Vo volaní funkcie skontroluj_zatvorky(string) skutočný parameter string iba hovorí: „od symbolickej adresy string začína pole s prvkami typu char“.

Z predchádzajúceho odseku vyplýva fakt, že pokiaľ potrebujeme vo funkcii pracujúcej s poľom poznať jeho veľkosť, potom musíme túto veľkosť preniesť ako ďalší formálny parameter. Teda pole ako skutočný parameter stráca vo funkcii štatút poľa a jeho veľkosť sa vo funkcii nedá nijako zistiť.

Tip: Ako už bolo spomenuté, pole sa do funkcie predáva pomocou volania odkazom a teda prvky poľa je možné vo

funkcii meniť, pričom zmena je trvalá. Ak potrebujeme zabezpečiť, aby funkcia nemohla meniť obsah poľa (žiaden prvok), musíme v hlavičke funkcie pred formálnym parametrom poľa uviesť kľúčové slovo const. Napr. zápis hlavičky v tvare int skontroluj_zatvorky(const char str[]) znamená, že prvky poľa str nie je možné meniť. Pri pokuse o zápis do poľa prekladač vygeneruje chybu.

1. prvok 2. prvok 3. prvok 254. prvok 255. prvok

100 101 102 353 354 adresa:

string:

49

Reťazce Reťazec je špeciálny typ jednorozmerného poľa, ktorý je zložený z prvkov typu char. Reťazec je vždy ukončený

znakom ‘\0‘ (znak s ASCII hodnotou nula; neodporúča sa používať prostú 0, aj keď počítaču je to jedno – použitie znakovej nuly ‘\0‘ zvyšuje čitateľnosť programu). Podľa tohto znaku sa pozná koniec reťazca a teda aj jeho dĺžka. Tento fakt má nasledujúce dôsledky: § Reťazec môže mať ľubovoľnú dĺžku, obmedzenú iba veľkosťou pamäte. Z celej tejto pridelenej pamäti je ale

„aktívna“ (práve využitá) len jej časť od začiatku až do prvého znaku ‘\0‘. Všetky ďalšie informácie uložené až za ‘\0‘ sú pri štandardnom spracovávaní reťazcov nedostupné (samozrejme je ich možné použiť, pokiaľ sa na reťazec pozeráme ako na jednorozmerné pole prvkov typu char), pretože práca s reťazcom končí vždy pri dosiahnutí prvého znaku ‘\0‘. § Pri definovaní reťazca, t.j. alokácii miesta pre reťazec, musíme alokovať jeden bajt naviac, práve pre túto ‘\0‘. § Pokiaľ zabudneme na koniec reťazca dať znak ‘\0‘, alebo ak tento znak omylom prepíšeme, považuje sa za reťa-

zec celá nasledujúca oblasť pamäti tak dlho, pokiaľ sa niekde ďalej v pamäti tento znak neobjaví. To väčšinou vedie k chybnej funkcii programu, pokiaľ do tejto pamäti zapisujeme.

V riadku 19 máme definíciu poľa znakov o veľkosti 255 (hodnota symbolickej konštanty LINE_LEN) znakov, takže do tohto poľa môžeme umiestniť reťazec dlhý maximálne 254 znakov (posledný znak je ukončovacia nula – ‘\0‘). Posledný znak reťazca má index 253, pretože indexovanie začína od nuly.

Práca s reťazcom Načítanie reťazca

Na formátované čítanie reťazca slúži funkcia scanf(), ktorej tvar pre načítanie reťazca je: scanf("%s", string);

Aktuálnemu parametru string nepredchádza ampersand (znak &), pretože string už sám o sebe predstavuje adresu – adresu začiatku reťazca. Na načítanie iba prvých niekoľko znakov je možné vo formátovej špecifikácii medzi znakmi % a s uviesť maximálnu dĺžku načítaného reťazca (pozri lekciu III).

Funkcia scanf() pracuje tak, že preskočí všetky biele znaky (medzere, tabulátory, nové riadky) a načíta z kláves-nice reťazec iba po prvý biely znak. Tzn., že ak zadáme z klávesnice napríklad výraz “2 + 3“, tak v premennej string bude uchovaný iba reťazec “2“ (ukončený ‘\0‘). Uvedený spôsob práce funkcie scanf() si odskúšate praktic-ky v cvičení 5 tejto lekcie.

Na načítanie riadku z klávesnice slúži funkcia gets() – viď riadok 26, ktorá načíta do premennej string celý riadok až po znak ‘\n‘ (znak ‘\n‘ do reťazca už neuloží) a na koniec reťazca pridá ukončovací znak ‘\0‘.

Vypísanie reťazca Na zobrazenie reťazca na obrazovku slúži štandardná funkcia printf(), ktorej tvar pre výpis reťazca je:

printf("%s", string); Pokiaľ chceme na obrazovku vypísať reťazec string ako riadok ukončený znakom ‘\n‘, použijeme funkciu

puts() – napr. puts(string); V našom príklade sme v riadku 33 použili funkciu puts() na vypísanie konštantného reťazca.

Porovnanie reťazcov Funkcia strnicmp(reťazec1, reťazec2, N) zabezpečuje porovnanie dvoch reťazcov (reťazec1 a reťazec2) bez roz-

lišovania malých a veľkých písmen (písmenko i v názve funkcie znamená ignore case) avšak maximálne do dĺžky N znakov (písmenko n v názve funkcie znamená obmedzenú dĺžku na n znakov). Funkcia vráti 0, ak sú obidva reťazce rovnaké. Vráti záporné číslo, ak je reťazec1 lexikograficky menší než reťazec2, a kladné číslo v opačnom prípade.

Procedúra interaktiv() teda v nekonečnom cykle načítava reťazec z klávesnice a kontroluje ho na správny počet zátvoriek aj ich poradie, t.j. či otvárajúca (ľavá) zátvorka predchádza zatvárajúcej (pravej) zátvorke. Cyklus sa opakuje dovtedy, kým používateľ nezadá z klávesnice reťazec začínajúci sa na dvojicu znakov “/q“, napr.: /q, /quit, ...

50

Prístup k jednotlivým znakom reťazca Funkcia skontroluj_zatvorky() kontroluje, či sa v reťazci str nachádza správny počet zátvoriek, a či sú

zátvorky v správnom poradí, tzn. každá ľavá zátvorka má svoju pravú zátvorku a žiadna pravá zátvorka nepredchá-dza ľavej zátvorke.

Funkcia vráti 1, ak sú v reťazci spárované všetky zátvorky, ináč vráti 0. Funkcia prechádza pomocou cyklu for a indexu i celým reťazcom str, až do ukončovacieho znaku ‘\0‘

a v prípade ľavej otvárajúcej zátvorky inkrementuje počítadlo otvorených zátvoriek (riadok 49). V prípade pravej zatvárajúcej zátvorky dekrementuje počítadlo (riadok 52) a kontroluje, či jeho hodnota nie je záporná (riadok 55) ⇒ v reťazci sa nachádza ukončovacia zátvorka pred otvárajúcou. Po prechode celým reťazcom skontroluje (riadok 60), či zostali niektoré zátvorky nespárované. Ak áno, vráti nulu, ináč jedničku.

Cvičenia 1. Na príklade procedúry interaktiv() si odskúšajte význam prefixového a postfixového zápisu inkrementácie

(inkrementácia premennej i vo volaní funkcie printf()). Riešenie: program c12-1.cpp

2. V hlavičke funkcie skontroluj_zatvorky() zmeňte formálny parameter str na konštantné pole a v tele funkcie skúste do poľa zapísať na miesto prvého prvku znak 'a'. Nezabudnite urobiť zmenu aj v prototype funkcie!

Riešenie: program c12-2.cpp

3. Na príklade procedúry interaktiv() vyskúšajte ekvivalenciu príkazov puts(reťazec) a printf("%s\n", reťazec).

Riešenie: program c12-3.cpp

4. Prepíšte procedúru pouzitie() z cvičenia 1 lekcie 3 pomocou príkazov puts(). Riešenie: súbor c12-4.cpp

5. Zameňte riadok 26 programu P12.CPP za nasledovný scanf("%s", string); a vyskúšajte program na nasledovných vstupoch:

"abs(-2)+5" " abs(-2)+5" "abs( -2 ) + 5"

Riešenie: program c12-5.cpp

51

XIII. lekcia: Smerníky a volanie odkazom Cieľom tejto lekcie je dôkladne vás oboznámiť so smerníkmi a ich využitím pri realizácii tzv. volania odkazom.

V lekcii sa najskôr naučíte, čo je to smerník, ako sa definuje a ako sa inicializuje. Zistíte, že smerníky sú vždy zvia-zané s určitým údajovým typom. Po úvodnom zoznámení sa so smerníkmi, nasleduje veľmi dôležitá časť: smerníky a funkcie. V lekcii II ste sa naučili, že jazyk C podporuje iba jeden spôsob prenášania parametrov do funkcie, a to hodnotou. Nevýhodou tohto spôsobu je, že hodnoty skutočných parametrov nie je možné vo vnútri funkcie meniť. Ak potrebujete zabezpečiť trvalú zmenu hodnôt skutočných parametrov, musíte realizovať volanie odkazom. V tejto lekcii sa dozviete, ako sa v jazyku C takéto volanie zabezpečuje.

Výstupom tejto lekcie bude prototyp funkcie str2d(), ktorá konvertuje reťazec na reálne číslo. Funkcia bude vracať dve hodnoty a keďže vieme, že každá funkcia v jazyku C môže štandardne vrátiť iba jednu hodnotu (viď. lek-ciu II), funkcia bude na svoju implementáciu využívať smerníky.

Výpis programu P13.CPP 1: #include <stdio.h> 2: 3: /* makro z P11.CPP */ 4: #define abs(x) ((x) < 0 ? -(x) : (x)) 5: 6: /* prototyp funkcie */ 7: void pow(double x, int y, double *z); 8: int NSD(int a, int b); 9: void vymen(int *p_x, int *p_y); 10: 11: void main() 12: { 13: double x, vysl = 1; 14: int n; 15: double *p_vysl; 16: int cis1, cis2; 17: 18: p_vysl = &vysl; 19: 20: printf("Zadaj cislo, ktore chces umocnit: "); 21: scanf("%lf", &x); 22: printf("Zadaj n (cele cislo) urcujuce n-tu mocninu: "); 23: scanf("%d", &n); 24: 25: pow(x, n, p_vysl); 26: printf("%lG^%d=%.15lG\n\n", x, n, *p_vysl); 27: 28: printf("NSD - Zadaj dve cisla: "); 29: scanf("%d %d", &cis1, &cis2); 30: cis1=abs(cis1); cis2=abs(cis2); 31: printf("NSD = %d", NSD(cis1, cis2)); 32: } 33: 34: /* funkcia vypocita hodnotu x^y a ulozi ju na adresu premennej z */ 35: void pow(double x, int y, double *z) 36: { 37: int i; 38: double vysledok = 1; 39: 40: if (y == 0) 41: *z = 1; 42: else 43: { 44: for (i = 1; i <= abs(y); i++) 45: vysledok *= (y < 0 ? 1/x : x); 46:

52

47: *z = vysledok; 48: } 49: } 50: 51: /* Dijkstrov algoritmus pre vypocet najvacsieho spolocneho delitela */ 52: int NSD(int a, int b) 53: { 54: if (a == 0 || b == 0) 55: return(1); 56: 57: while(a != b) 58: { 59: if (b > a) 60: vymen(&a, &b); 61: a -= b; 62: } 63: 64: return (a); 65: } 66: 67: void vymen(int *p_x, int *p_y) 68: { 69: int pom; 70: 71: pom = *p_x; 72: *p_x = *p_y; 73: *p_y = pom; 74: }

Vysvetlivky: double *z – z predstavuje smerník na typ double *z = 1 – * predstavuje dereferenčný operátor &vysl – & predstavuje adresný (referenčný) operátor

Smerníky Smerník (nazývaný tiež pointer) je premenná, ako každá iná, iba hodnota uložená v tejto premennej má odlišný

význam od hodnôt premenných, ktoré sme doteraz používali (napr. int n). Pointer ukazujúci na premennú var je premenná, ktorá uchováva pamäťovú adresu premennej var. Pointer teda

predstavuje adresu v pamäti. Na pochopenie smerníkov potrebujete základné znalosti, ako počítač uchováva informácie v pamäti. Operačná

pamäť počítača pozostáva z mnoho tisícok buniek umiestnených sekvenčne za sebou. Každá bunka je identifikovaná jedinečnou adresou. Rozsah pamäťových adries je od 0 po maximálnu hodnotu, ktorá závisí od veľkosti inštalovanej pamäti.

Keď zadefinujete premennú v programe C, prekladač zarezervuje pre túto premennú pamäťové miesto (bunku/y), identifikované jedinečnou adresou. Prekladač teda priradí menu premennej adresu, tzn. meno premennej predstavuje symbolickú adresu. Keď v programe použijete meno premennej, automaticky sa sprístupní pamäťové miesto prislúchajúce tejto premennej. Použije sa adresa pamäťového miesta, ktorá je však pred programátorom skrytá.

Obrázok 13-1 to znázorňuje schematicky. Premenná nazvaná vysl bola zadefinovaná a inicializovaná na hod-notu 1. Prekladač rezervoval pre túto premennú miesto v pamäti na adrese 1004 a asocioval (zviazal) názov premen-nej vysl s touto adresou.

53

Obr. 13-1 Hodnota programovej premennej je uložená na určitej pamäťovej adrese.

Vytvorenie smerníka Adresa premennej vysl (alebo ľubovoľnej inej premennej) je číslo a v jazyku C sa môže spracovávať ako

hocijaké iné číslo. Ak vieme adresu premennej, môžeme vytvoriť druhú premennú, do ktorej uložíme adresu prvej premennej. Prvým krokom je zadefinovanie premennej, ktorá bude uchovávať adresu premennej vysl. Nazvime túto premennú napr. p_vysl. Na začiatku je p_vysl neinicializovaná. Miesto v pamäti bolo pridelené pre p_vysl, ale jeho hodnota nie je známa, ako znázorňuje obrázok 13-2.

Obr. 13-2 Pamäťové miesto bolo alokované pre premennú p_vysl. Ďalším krokom je uloženie adresy premennej vysl do premennej p_vysl. Teraz premenná p_vysl ukazuje

miesto v pamäti, kde je vysl uložená. V terminológii jazyka C to nazývame, že p_vysl ukazuje na vysl, alebo p_vysl je smerník/pointer na vysl. Túto situáciu znázorňuje obrázok 13-3.

Obr. 13-3 Premenná p_vysl obsahuje adresu premennej vysl, a je teda smerníkom na vysl. Zhrnutie: Pointer je premenná, ktorá obsahuje adresu inej premennej.

Smerníky a jednoduché premenné V predchádzajúcom príklade premenná typu smerník ukazovala na jednoduchú premennú (teda nie pole).

Nasledujúca časť vysvetľuje, ako vytvoriť a použiť smerníky jednoduchých premenných.

Definovanie smerníkov Pointer je pamäťová premenná a podobne ako iné premenné, musí byť definovaná predtým, než sa môže prvý-

krát použiť. Definícia smerníku má nasledujúcu formu: názov_typu *ptr_názov;

názov_typu je ľubovoľný typ (napr. int, char, double, ...) v jazyku C a označuje typ premennej, na ktorú smerník ukazuje. Operátor hviezdička (*) pred menom smerníka označuje, že ptr_názov je pointer na typ názov_typu a nie premenná typu názov_typu. Smerníky môžu byť definované spolu s klasickými premennými, teda napr. riadky 13 a 15 programu P13.CPP môžeme zlúčiť do jednej definície:

double x, vysl = 1, *p_vysl;

54

Inicializácia smerníkov Keď sme už zadefinovali smerník p_vysl, musíme ho najprv inicializovať. Nikdy nepoužívajte neinicializo-

vaný smerník! Smerník môžete používať, až keď na niečo (nejakú premennú) ukazuje. Kým smerník neuchováva adresu niektorej premennej, je úplne neužitočný (priam nebezpečný, ak ho použijeme na ľavej strane priraďovacieho príkazu), pretože obsahuje adresu ukazujúcu niekde do pamäti. Na inicializáciu smerníka používame adresný operátor – znak & (ampersand). Ak v programe umiestnime & pred meno premennej, adresný operátor vráti adresu premennej. Preto smerníky inicializujeme pomocou príkazu v tvare:

pointer = &premenná;

Pozrime sa naspäť na príklad z obrázku 13-3. Príkaz, ktorý zinicializuje premennú p_vysl tak, aby ukazovala na premennú vysl, bude (viď. riadok 18 programu P13.CPP):

p_vysl = &vysl; /* priraď adresu premennej vysl do p_vysl */ Pred inicializáciou, p_vysl neukazoval na nič, presnejšie povedané, ukazoval niekde do pamäti. Po inicializácii

p_vysl ukazuje na vysl.

Použitie smerníkov Teraz už vieme smerník definovať a inicializovať, a pravdepodobne nás zaujíma, ako ho môžeme použiť. Keď

pred menom premennej typu smerník použijeme dereferenčný operátor (*), znamená to, že sa odvolávame na premennú, na ktorú smerník ukazuje.

Pokračujúc v predchádzajúcom príklade, v ktorom p_vysl ukazuje už na premennú vysl, ak napíšeme *p_vysl, odvolávame sa na premennú vysl. Ak chceme zobraziť hodnotu výsledku vysl, napíšeme:

printf("%lG", vysl); alebo

printf("%lG", *p_vysl); (pozri riadok 26 programu P13.CPP) V jazyku C sú tieto dva príkazy ekvivalentné. Pristupovanie k obsahu premennej použitím mena premennej sa

nazýva priamy prístup. Pristupovanie k obsahu premennej použitím smerníka na premennú sa nazýva nepriamy prístup. Obrázok 13-4 ukazuje, ako sa pomocou dereferenčného operátora pred menom smerníka odvolávame na hodnotu premennej, na ktorú smerník ukazuje.

Obr. 13-4 Použitie dereferenčného operátora so smerníkom. Zhrnutie: Ak máte smerník pomenovaný ptr, ktorý bol inicializovaný adresou premennej var, nasledujúce

tvrdenia sú platné: • *ptr aj var sa odvolávajú na obsah/hodnotu premennej var. • ptr a &var sa odvolávajú na adresu premennej var.

Ako vidíte, meno smerníka bez dereferenčného operátora pristupuje k samotnej hodnote smerníka, ktorou je samozrejme adresa premennej, na ktorú smerník ukazuje.

Smerníky a typy premenných Predchádzajúci výklad ignoroval fakt, že rôzne typy premenných zaberajú rôzne veľké množstvo pamäti. Pre

väčšinu operačných systémov typ char zaberá jeden bajt, int dva bajty, float štyri bajty, atď. Každý jeden bajt pamäte má svoju vlastnú adresu, takže viacbajtové premenné v skutočnosti obsadzujú niekoľko adries.

„Ako potom smerníky zaobchádzajú s adresami viacbajtových premenných?“ Adresa premennej vlastne predstavuje adresu prvého (najnižšieho) bajtu, ktorý premenná obsadzuje. Teda pointer je ekvivalentný s adresou najnižšieho bajtu premennej, na ktorú smerník ukazuje. Napriek tomu je každý smerník zviazaný s určitým typom

55

premennej, preto používame termín „pointer na typ ...“. Dôvod viazanosti smerníka na určitý typ viď. v nasledujúcej lekcii „Smerníky a polia“.

Nulový pointer NULL V jazyku C existuje symbolická konštanta NULL, ktorá je definovaná v stdio.h ako:

#define NULL 0 alebo ako #define NULL ((void *) 0) NULL je možné priradiť bez pretypovania všetkým typom smerníkov (smerníkom na ľubovoľný typ premennej) a používa sa pre označenie, že tento pointer neukazuje na nič. Preto, ak hneď pri definícii neinicializujeme smerník na adresu niektorej premennej, mali by sme ho inicializovať na hodnotu NULL – vyhneme sa tým problémom, ak sa dereferencovaný (neinicializovaný) smerník nachádza na ľavej strane priraďovacieho príkazu.

Smerníky a funkcie – volanie odkazom V II. lekcii (Funkcie a práca s pamäťou) sme sa oboznámili so spôsobom predávania parametrov funkcii a zistili

sme, že jazyk C umožňuje iba jeden spôsob prenášania parametrov, a to hodnotou. Pri tomto spôsobe prenášania parametrov sa hodnoty aktuálnych (skutočných) parametrov kopírujú cez zásobník do formálnych parametrov funkcie, a teda skutočné parametre nemôžu byť vo funkcii zmenené.

Ďalší problém s funkciami nastáva v tom, že funkcia dokáže vrátiť iba jedinú hodnotu. Ak potrebujeme, aby nám funkcia vrátila viacero hodnôt, musíme použiť buď globálne premenné, alebo ako návratový typ použijeme štruktúru. Nevýhoda pri vracaní hodnôt z funkcie pomocou štruktúry je, že na prenos návratovej hodnoty sa používa zásobník, čiže ak potrebujeme vrátiť väčší objem údajov, je s tým spojená veľká réžia.

Jednou z veľmi užitočných vlastností smerníkov je, že umožňujú predať parametre „odkazom“. Pointery v tomto prípade použijeme, keď chceme vo funkcii natrvalo zmeniť hodnotu skutočného parametru. V praxi to znamená, že nepredávame hodnotu premennej, ale adresu tejto premennej. Obr. 13-5 Vplyv volania odkazom na hodnoty skutočných parametrov.

Volanie odkazom vo funkcii alebo procedúre nie je v skutočnosti volanie odkazom, tak ako je to v iných programovacích jazykoch (C++, Java, Pascal, ...), kde formálny parameter označíme určitým kľúčovým slovom resp. formálny parameter zadefinujeme ako referenciu. V tomto prípade kompilátor príslušného jazyka zaistí, že sa pri volaní funkcie (procedúry) do zásobníka uloží adresa premennej (adresa skutočného parametra), ktorej hodnota má byť zmenená. Vo funkcii/procedúre sa potom s formálnym parametrom pracuje celkom „normálne“ – teda bez akýchkoľvek trikov so smerníkmi. Prekladač sám zaistí, že sa pri priraďovacom príkaze nebude meniť hodnota v zásobníku, ale hodnota skutočného parametra.

V C je toto „volanie odkazom“ opäť iba volaním hodnotou, kedy sa v zásobníku vytvorí lokálna kópia pre uloženie parametra – adresy skutočného parametra. Táto lokálna premenná síce zaniká s ukončením príslušnej funkcie, ale má tú vlastnosť, že je v nej uložený pointer, pomocou ktorého sa nepriamo (viď. nepriamy prístup) zmenia údaje, ktoré nemajú s touto funkciou nič spoločné – boli definované mimo tejto funkcie a nezanikajú s jej ukončením. Čiže výsledok je rovnaký ako pri skutočnom volaní odkazom, ale postup spracovávania je odlišný. To, čo v jazykoch podporujúcich volanie odkazom vykonáva prekladač automaticky, musíme v jazyku C urobiť sami – čiže nepracovať s formálnym parametrom ako s normálnou premennou, ale ako so smerníkom.

Pre zjednodušenie však budeme naďalej používať termín volanie odkazom. Nejasnosti môžu vzniknúť až pri štúdiu jazyka C++ (objektovo orientované C), ktorý umožňuje skutočné volanie odkazom.

Skutočné parametre Adresa

Formálne parametre

Volajúci program Funkcia

56

Uvedený spôsob realizácie volania odkazom a volania hodnotou objasňujú nasledovné dva obrázky:

Obr. 13-6 Prenesenie argumentu hodnotou (funkcia nemôže zmeniť hodnotu pôvodnej premennej x).

Obr. 13-7 Prenesenie argumentu odkazom (funkcia môže meniť hodnotu pôvodnej premennej x).

V riadkoch 67 až 74 programu P13.CPP máme typický príklad volania odkazom. Funkcia vymen() vymení obsah dvoch premenných. Funkciu vymen() voláme so skutočnými parametrami tak, ako je to uvedené v riadku 60. Predpokladajme, že sme funkciu NSD() zavolali s parametrami a=12 a b=16. Na nasledujúcich obrázkoch je pod-robne znázornené, ako vyzerajú jednotlivé premenné pri postupnom vykonávaní funkcie vymen().

1. Pred volaním funkcie vymen(&a, &b);

12 16

a b

57

2. Tesne po zavolaní funkcie vymen(&a, &b);

3. Po vykonaní funkcie vymen(&a, &b); tesne pred jej ukončením

4. Po opustení funkcie vymen(&a, &b);

Pozor: • Veľmi častá chyba pri volaní je: vymen(a, b); ktorá spôsobí, že sa bude zapisovať na adresy dané

obsahom premenných a a b, teda na absolútnu adresu 12 a 16, čo vedie vo väčšine prípadov k zrúteniu programu.

• Druhou častou chybou je volanie: vymen(*a, *b); ktoré spôsobí, že zápis bude vykonaný na adresách adries z obsahu a a b, teda napr. z absolútnej adresy 12 sa vezme hodnota, ktorá sa použije ako adresa, na ktorej sa vykoná zmena. Výsledkom je opäť najčastejšie zrútenie sa programu.

Analýza funkcie pow() (riadky 35 až 49)

V programe P13.CPP sme vytvorili opäť funkciu pow(), ktorá umocňuje hodnotu x na y. Tento raz sme však vytvorili funkciu ako procedúru, tzn. že jej návratový typ je void. Funkcia musí vypočítanú hodnotu mocniny nejakým spôsobom vrátiť, a tak sme jeden z jej formálnych parametrov označili ako smerník na premennú typu double (double *z). Funkcia teda vráti výsledok umocňovania pomocou tejto premennej. Premenná z predstavuje adresu premennej, do ktorej uložíme výsledok funkcie x^y.

Keďže premenná z je smerník, pri volaní funkcie (riadok 25) musíme ako skutočný parameter dodať adresu premennej vysl (premenná p_vysl), do ktorej chceme výsledok uložiť. V riadkoch 41 a 47 sme použili dereferen-čný operátor (*), ktorým sa odvolávame na hodnotu, na ktorú smerník z ukazuje – teda premennú vysl (pozri riadok 18).

Všimnite si, že pri volaní funkcie pow() – riadok 25 – sme použili smerník p_vysl, ktorý ukazuje na premennú vysl. Samozrejme, že sme mohli použiť priamo adresu premennej vysl, ktorú by sme získali pomocou adresného operátora &, tzn. volanie funkcie by malo tvar: pow(x, n, &vysl); Teraz by vám už malo byť jasné, prečo vo funkcii scanf() používame pred premennými znak ampersand (&). Funkcia scanf() totiž vracia počet úspešne načítaných argumentov (viď. lekciu III) a samotné načítané hodnoty musí nejakým spôsobom vrátiť argumentom – keďže, ako vieme, funkcia dokáže vrátiť iba jednu hodnotu, načítané hodnoty musí scanf() vrátiť cez smerníky

12 16

a b

p_x

p_y

?

pom

? znamená nedefinovanú hodnotu, pretože premenná je lokálna, a teda je vytvorená v zásobníku

16 12

a b

p_x

p_y

12

pom

pom = *p_x; *p_y = pom;

*p_x = *p_y;

16 12

a b

58

(volanie odkazom). Výnimku pri funkcii scanf() tvoria argumenty, ktoré nie sú jednoduchými premennými, ako sú napr. polia, reťazce a pod., pred ktorými neuvádzame adresný operátor, pretože tieto premenné, ako uvidíme v ďalšej lekcii, sú samé o sebe smerníkmi.

Smerník ako skutočný parameter funkcie Ak potrebujeme vo funkcii zmeniť nie hodnotu jednoduchej premennej, ale hodnotu smerníka (t.j. adresu, na

ktorú smerník ukazuje), musíme ako formálny parameter použiť pointer na pointer na typ. Vo funkcii potom k hodnote smerníka pristúpime pomocou dereferenčného operátora a mena tohto formálneho parametra. Napr. funkcia f(), ktorá mení hodnotu smerníka na typ integer, má prototyp void f(int **ptr); Vo vnútri funkcie f() potom k premennej typu smerník pristupujeme pomocou výrazu *ptr.

Tipy a triky Ak potrebujete vypísať adresu, ktorú smerník ptr uchováva, použite %p v riadiacom reťazci formátu funkcie printf(). Napr.: printf("%p", ptr);

Cvičenia 1. Upravte definíciu smerníka p_vysl vo funkcii main() tak, že ho pri definícii zároveň inicializujete na hodnotu

NULL. Riešenie: program c13-1.cpp

2. Prepíšte volanie funkcie pow() v tele funkcie main() tak, aby sa nepoužil smerník na premennú vysl, ale aby funkcia main() odovzdala priamo adresu premennej vysl.

Riešenie: program c13-2.cpp

3. Vyskúšajte si rozdiel medzi volaním odkazom a volaním hodnotou na príklade funkcie pow(). To znamená, že prepíšte funkciu pow() na funkciu s prototypom void pow(double x, int y, double z); ktorá vypočíta z=x^y, a túto funkciu volajte v main() s premennými x, n a vysl.

Riešenie: program c13-3.cpp

4. Prepíšte funkciu root() z programu P07.CPP na funkciu s prototypom void root(double cislo, int n, double *vysledok); ktorá vypočíta výraz n-tej odmocniny z cisla a výsledok uloží na adresu, kam ukazuje pointer vysledok. Napíšte príklad príkazu (vrátane definície premenných), ktorým zavoláte túto funkciu.

Riešenie: program c13-4.cpp

5. Napíšte prototyp funkcie str2d(), ktorá vracia číslo typu double a má dva vstupné parametre. Prvým je reťazec str a druhým je pointer na pointer na typ char nazvaný enptr.

Riešenie: súbor c13-5.cpp

6. Napíšte funkciu vymen_ptr() podobnú funkcii vymen() z príkladu P13.CPP, ktorá vymení obsahy dvoch smerníkov na typ int.

Riešenie: súbor c13-6.cpp

59

XIV. lekcia: Smerníky a polia, pointerová aritmetika, pole reťazcov V tejto lekcii sa naučíte podobnosti smerníkov a polí. Zistíte, že medzi smerníkmi a poliami existuje špeciálny

vzťah a pochopíte, prečo každé pole začína indexom 0 a prečo hranice poľa nie sú v jazyku C kontrolované. Obozná-mite sa so smerníkovou aritmetikou a jej významom pri prístupe k prvkom poľa. Nakoniec sa zoznámite s dvojroz-mernými poliami (pole reťazcov) a tým, ako sú v pamäti reprezentované.

Výstupom tejto lekcie bude kostra funkcie getSymbol(), ktorá predstavuje lexikálny analyzátor cieľového programu kalkulátor. Úlohou funkcie bude rozpoznať všetky symboly matematických operácií a funkcií, ktoré sa nachádzajú vo vstupnom reťazci. Vstupný reťazec zadáva používateľ z klávesnice a na jeho načítanie sa využíva program P14.CPP. Funkcia programu uvedeného nižšie (viď. výpis programu P14.CPP) je rovnaká ako funkcia prog-ramu P12.CPP (pozri lekciu XII). Rozdiel je iba v tom, že funkcia, ktorá kontroluje reťazec na správny počet a pora-die ľavých a pravých okrúhlych zátvoriek, využíva na svoju implementáciu smerníky.

Výpis programu P14.CPP – program P12.CPP pomocou smerníkov. 1: #include <stdio.h> 2: #include <conio.h> /* kvoli volaniu clrscr(); */ 3: #include <string.h> /* kvoli volaniu strnicmp(); */ 4: 5: /* definicia symbolickej konstanty */ 6: #define LINE_LEN 255 7: 8: /* prototypy funkcii */ 9: int skontroluj_zatvorky(char *str); 10: void interaktiv(void); 11: 12: void main() 13: { 14: interaktiv(); 15: } 16: 17: void interaktiv(void) 18: { 19: int i = 0; /* pocitadlo prikazov */ 20: char string[LINE_LEN]; /* retazec nacitany z klavesnice */ 21: 22: clrscr(); 23: 24: while(1) 25: { 26: printf("%d> ", ++i); 27: gets(string); 28: 29: if (strnicmp("/q", string, 2) == 0) 30: return; 31: else 32: if (!skontroluj_zatvorky(string)) 33: { 34: puts("Chyba: Nespravny pocet zatvoriek alebo ich poradie."); 35: continue; 36: } 37: } 38: } 39: 40: /* Funkcia skontroluje, ci su v retazci str vsetky zatvorky sparovane 41: * a ci su v spravnom poradi. V pripade chyby vrati 0, inac 1. 42: */ 43: int skontroluj_zatvorky(char *str) 44: { 45: int pocet = 0; /* udava pocet otvorenych zatvoriek */ 46:

60

47: for ( ; *str != '\0'; str++) 48: { 49: if (*str == '(') pocet++; 50: 51: if (*str == ')') 52: { 53: pocet--; 54: 55: /* nespravne poradie, ')' predchadza '(' */ 56: if (pocet < 0) return(0); 57: } 58: } 59: 60: /* pocet > 0 => zostali otvorene zatvorky */ 61: if (pocet) return(0); 62: 63: /* pocet == 0 => spravny pocet parov zatvoriek */ 64: return(1); 65: }

Vysvetlivky:

char *str; – definícia smerníka na typ char (str predstavuje adresu začiatku poľa/reťazca) *str – prístup k prvku poľa / ku znaku reťazca str++ – presunutie ukazovateľa na ďalší prvok poľa / na ďalší znak v reťazci

Smerníky a polia Smerníky sú veľmi užitočné pri práci s poľom. Medzi smerníkmi a poliami existuje špeciálny vzťah. Keď

používate indexový prístup k prvkom poľa, s ktorým ste sa oboznámili v lekcii XII „Reťazce ako jednorozmerné polia“, v skutočnosti používate smerníky bez toho, aby ste o tom vedeli. Nasledujúce odstavce vám vysvetlia, ako to celé pracuje.

Meno poľa ako smerník Meno poľa bez hranatých zátvoriek ([]) reprezentuje adresu prvého prvku poľa. Takže pokiaľ sme v našom

príklade definovali pole string[] (riadok 20), string predstavuje adresu prvého prvku poľa (riadky 27, 29 a 32). „Moment!“ mohli by ste povedať. „Nepotrebujeme náhodou použiť adresný operátor na získanie adresy?“ Áno.

Môžete tiež použiť výraz &string[0] na získanie adresy prvého prvku poľa. Keďže každé pole v jazyku C sa začína (indexuje) od nuly, vzťah string = &string[0] je pravdivý.

Videli ste, že meno poľa predstavuje smerník na začiatok poľa. Nezabudnite, že toto meno je konštantný smer-ník; nemôže byť zmenený a zostáva nemenný počas vykonávania programu. Dáva to zmysel: Ak by sme zmenili jeho hodnotu, mohol by ukazovať hocikde inde a nie na začiatok poľa.

Samozrejme, že my si v programe môžeme vytvoriť premennú typu smerník a inicializovať ju tak, aby ukazova-la na začiatok poľa. Napr. v riadku 43 sme v hlavičke funkcie skontroluj_zatvorky() definovali premennú str ako smerník na typ char a inicializovali sme ho adresou prvého prvku poľa string – volanie funkcie v riadku 32. Keďže str je premenná typu smerník, môže byť modifikovaná a teda môže ukazovať na ľubovoľné pamäťové miesto. Na rozdiel od poľa (premenná string) smerník str nie je viazaný iba na prvý prvok poľa string[] a môže ukazovať aj na iný prvok poľa string[]. A ako? Odpoveď nám poskytne odsek „vzťah medzi smerníkmi a poliami“, no predtým sa musíme ešte oboznámiť so smerníkovou aritmetikou.

Z časti „Pole ako parameter funkcie“ lekcie XII a predchádzajúcich odstavcov vyplýva, že identifikátor poľa je konštantný smerník, ale identifikátor poľa ako formálny parameter funkcie je premenný smerník.

61

Pointerová aritmetika S pointermi je možné vykonávať niektoré aritmetické operácie. Keďže pointre predstavujú adresy, aritmetické

operácie, ktoré môžeme vykonávať nad pointermi, majú trochu iný význam ako aritmetické operácie s normálnymi premennými. Platné operácie so smerníkmi sú: • súčet smerníka a celého čísla • rozdiel smerníka a celého čísla • porovnanie dvoch smerníkov rovnakého typu • rozdiel dvoch smerníkov rovnakého typu Ostatné aritmetické operácie so smerníkmi je síce možné vykonať, no nemajú žiadny zmysel a bývajú zdrojom chýb.

Súčet smerníka a celého čísla Výraz: ptr + n znamená, že sa odkazujeme na n-tý prvok za prvkom, na ktorý smerník ptr práve

ukazuje. Ako už vieme, každý smerník je definovaný ako smerník na určitý typ a adresa, ktorú uchováva, predstavuje adresu najnižšieho bajtu premennej, na ktorú smerník ukazuje – viď. lekciu XIII „Smerníky a typy premenných“. Takže hodnota adresy prvku ptr + n je súčet adresy obsiahnutej v premennej ptr a n * veľkosť typu (napr. veľkosť typu int sú 2 bajty), na ktorý smerník ukazuje, čiže k pointeru sa nepričíta príslušné celé číslo (n), ale násobok tohto čísla a veľkosti typu, na ktorý pointer ukazuje.

Nasledujúci obrázok schematicky znázorňuje situáciu, v ktorej máme definovanú premennú ptr ako smerník na typ int, a pričítame k nej hodnotu 3. Smerník ptr ukazuje na adresu 1000.

Obr. 14-1 Súčet smerníka a celého čísla.

Pretože súčet celého čísla a pointera je opäť pointer, je možné písať príkazy typu: ptr = ptr + 5; kde ptr bude ukazovať na 5-ty prvok za pôvodným prvkom.

Odčítanie celého čísla od smerníka Výraz: ptr - n znamená, že sa odkazujeme na n-tý prvok pred prvkom, na ktorý smerník ptr práve

ukazuje.

Vzťah medzi smerníkmi a poliami Vychádzajúc z faktu, že index každého poľa v C začína od nuly, adresu ľubovoľného prvku môžeme vypočítať

podľa vzťahu: &x[i] = bázová adresa x + i * veľkosť typu; kde veľkosť typu je počet bajtov, ktoré jeden prvok poľa obsadzuje v pamäti (typ je typ poľa). Premenná typu pole, v našom prípade x, predstavuje adresu do pamäti (adresu prvého prvku poľa).

Takže výraz x[i] je ekvivalentný s výrazom *(x + i), pretože x + i je adresa daná súčtom bázovej adresy poľa predstavovanej hodnotou x a indexu predstavovaného hodnotou i. Operátor * umožňuje získať obsah na tejto adrese.

Prístup k prvkom poľa pomocou smerníkov býva obyčajne omnoho efektívnejší než prístup pomocou indexovania. Ak napr. prechádzame poľom v cykle (riadok 47), pre získanie ďalšieho prvku poľa sa pripočítava iba konštanta – t.j. veľkosť prvku poľa – k aktuálnej adrese súčasného prvku, no pri indexovaní sa musí touto konštantou vynásobiť index a potom sa musí výsledok pričítať k bázovej adrese. Samozrejme, že to závisí od implementácie a od toho, ako prekladač optimalizuje – kompilátor by mal previesť (tam, kde je to možné) prístup pomocou indexov na prístup pomocou smerníkov.

-3847

1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010

ptr (2 bajty začínajúc od 1000)

12252

ptr + 3 (2 bajty začínajúc od 1006)

62

Analýza funkcie skontroluj_zatvorky() (riadky 43 až 65)

Ako ste si už asi všimli, funkcia skontroluj_zatvorky() je kópiou funkcie z príkladu P12.CPP. Rozdiel je iba v tom, že funkcia používa namiesto indexovania smerníky. Formálny parameter funkcie je nahradený smerníkom na typ char, tzn. že str predstavuje začiatok poľa znakov. Pretože koniec reťazca je označený ‘\0’, všetko, čo funkcia potrebuje ako vstupný parameter, je adresa začiatku reťazca – na to sme použili smerník. Pripomíname, že pri formálnych parametroch funkcie je jedno, či použijeme notáciu typ pole[] alebo typ *smerník, pretože pri volaní funkcie sa ako skutočný parameter dodá adresa začiatku poľa.

Vo for cykle (riadok 47) je ukončovacím výrazom *str != 0, čo znamená, že cyklus sa ukončí pri dosiahnutí konca reťazca. Iteračným výrazom je str++, ktorý inkrementuje pointer, t.j. na konci každej obrátky v cykle smerník str ukazuje na ďalší znak reťazca. Namiesto pristupovania k prvku str[i] sme použili prístup pomocou dereferenčného operátora (*), teda *str (index i zvyšujeme iteračným výrazom str++).

Tip: Keďže formálnym parametrom funkcie je smerník, realizuje sa tzv. volanie odkazom (viď. predchádzajúcu

lekciu), čo má jeden z vedľajších efektov, a to, že vo funkcii je možné zmeniť obsah reťazca. Ak potrebujeme zabezpečiť, že sa skutočný parameter – reťazec string (riadok 32) – nesmie vo funkcii meniť, označíme formálny parameter funkcie pomocou kľúčového slova const. Teda hlavička funkcie skontroluj_zatvorky() bude mať tvar:

int skontroluj_zatvorky(const char *str)

Poznámka: Väčšina knižničných funkcií pre prácu s reťazcami má formálne parametre typu const char*, čo neznamená, že

funkciu môžeme volať iba s konštantným reťazcom, ale nás to informuje o tom, že funkcia nemení reťazec zadaný ako vstupný parameter, ale ho len číta. Takisto všetky knižničné funkcie pre prácu s reťazcami pre vstup jedného reťazca používajú iba jeden formálny parameter, ktorým je začiatok, odkiaľ sa začína reťazec spracovávať. Viac formálnych parametrov (ako napr. veľkosť poľa, v ktorom je reťazec uložený) pre jeden vstupný reťazec nie je potrebných, pretože koniec reťazca je označený znakom ‘\0’. Teda všetko, čo funkcia potrebuje vedieť, je adresa začiatku reťazca.

Pole reťazcov Pole reťazcov predstavuje dvojrozmerné pole s rôznou dĺžkou jednotlivých riadkov. Prvým indexom (prvý roz-

mer) pristupujeme k vybranému reťazcu a druhým indexom (druhý rozmer) pristupujeme k samotnému znaku tohto reťazca. Pre dvojrozmerné aj viacrozmerné polia platí, že všetky indexy začínajú od nuly. Pole reťazcov definujeme v jazyku C pomocou konštrukcie:

char *pole[N]; ktorá vytvorí pole reťazcov (presnejšie povedané pole smerníkov na reťazce) o veľkosti N prvkov. Tomuto poľu smerníkov môžeme priradiť hodnoty – adresy reťazcov – napr.:

pole[0] = "prvý" pole[1] = "druhý" pole[2] = "tretí" ...

Po takto prevedených priradeniach bude situácia v pamäti vyzerať nasledovne:

pole prvý\0 druhý\0

... tretí\0

[0] [1] [2]

63

Jednotlivé znaky z prvého reťazca sa čítajú z adries: pole[0][0] pole[0][1] pole[0][2] atď.

Pokiaľ pole pri definícii zároveň inicializujeme, veľkosť poľa (počet prvkov/reťazcov) nemusíme definovať, pretože prekladač to vykoná za nás. Predchádzajúce pole reťazcov by sme definovali a zároveň inicializovali nasledovne:

char *pole[] = {"prvý", "druhý", "tretí", ...};

Cvičenia 1. Zabezpečte funkciu skontroluj_zatvorky() tak, aby reťazec str nemohol byť vo vnútri funkcie

modifikovaný. Správnosť overte pridaním príkazu (v tele funkcie), ktorý skráti reťazec na dĺžku 0 znakov. Riešenie: program c14-1.cpp

2. Definujte a zároveň inicializujte pole reťazcov ako globálnu premennú nazvanú RW, ktorá bude obsahovať jednoznakové reťazce základných matematických operácii (plus, mínus, súčin, delenie) a znak ľavej a pravej okrúhlej zátvorky. Ďalej definujte symbolickú konštantu (pomocou makra bez parametrov) NORW, ktorá udáva počet prvkov množiny/poľa RW.

Riešenie: program c14-2.cpp

3. Napíšte funkciu vypocitaj(), ktorá má vstupný parameter reťazec a návratovú hodnotu typu int. Do funkcie presuňte kontrolu vstupného reťazca na správny počet a poradie zátvoriek z funkcie interaktiv() (riadky 32 až 36). Funkcia vráti v prípade úspešnej kontroly 0, ináč -1. Funkciu volajte z funkcie interaktiv() z miesta, kde bola vykonávaná kontrola na zátvorky. Ďalej definujte globálnu premennú ptr typu smerník na typ char (inicializujte ju na NULL), ktorá bude predstavovať smerník na aktuálny znak v načítanom reťazci. Túto premennú napĺňajte na konci funkcie vypocitaj() adresou začiatku načítaného reťazca.

Riešenie: program c14-3.cpp

4. Napíšte funkciu s prototypom void getSymbol(); ktorá prechádza načítaným reťazcom a vypíše na samostatný riadok prvý zistený symbol v načítanom reťazci. Reťazec je uchovaný v globálnej premennej ptr z predchádzajúceho cvičenia. Platné symboly, ktoré sa môžu nachádzať v reťazci, sú definované v poli RW z cvičenia 2, plus v reťazci sa môžu nachádzať aj číslice.

Spôsob práce funkcie: Funkcia preskočí všetky biele znaky (medzere ' ' a tabulátory '\t') a v prípade konca reťazca vypíše správu "koniec". Na zistenie čísla použite makro isdigit() z cvičenia 3 lekcie XI. V prípade zistenia neznámeho symbolu vypíšte správu "neznamy symbol", presuňte smerník ptr na ďalší znak v reťazci a funkciu ukončite. V prípade zistenia symbolu z RW/čísla vypíšte symbol (prvok pola RW) a nastavte smerník ptr na ďalší znak (nasledujúci znak po symbole/čísle). Poznámka: Na porovnávanie symbolov použite prístup pomocou indexov. RW uvažujte ako dvojrozmerné pole, tzn. if (*ptr == RW[i][0]) ...

Riešenie: program c14-4.cpp

5. Funkciu predchádzajúceho cvičenia overte pomocou nasledovnej časti programu: while (*ptr != '\0') getSymbol();

ktorú umiestnite do funkcie vypocitaj() z cvičenia 3, tesne za inicializáciou smerníka ptr (za nastavením smerníka ptr na prvý znak načítaného reťazca). Spustite program pre nasledujúci reťazec zadaný z klávesnice: "1 + 2/3 - (7 * 4)/5"

Riešenie: program c14-5.cpp

64

6. Doplňte v programe z predchádzajúceho cvičenia pole RW (pridaním na koniec zoznamu prvkov) definované v cvičení 2 o nasledujúce reťazce, predstavujúce matematické funkcie, v tomto poradí:

"FACT" – faktoriál "NSD" – najväčší spoločný deliteľ "MIN" – minimum z dvoch čísel "MAX" – maximum z dvoch čísel "ROUND" – zaokrúhlenie na celé číslo "CEIL" – zaokrúhľovanie nahor "FLOOR" – zaokrúhľovanie nadol "SQRT" – druhá odmocnina "SQR" – druhá mocnina "PWR" – n-tá mocnina "ROOT" – n-tá odmocnina "MOD" – zvyšok po delení "ABS" – absolútna hodnota "TRUNC" – orezanie reálneho čísla na celú časť ";" – oddeľovač pre funkcie pracujúce s dvoma argumentmi

Upravte symbolickú konštantu NORW na číslo 21, označujúce mohutnosť množiny RW, t.j. počet prvkov poľa RW. Riešenie: program c14-6.cpp

7. Upravte procedúru getSymbol() z cvičenia 4 tak, že na porovnanie symbolov matematických operácií (množina RW z predchádzajúceho cvičenia) použijete funkciu strnicmp(), pričom za maximálnu dĺžku porovnávaných častí reťazcov považujte dĺžku reťazca/prvku poľa RW. Dĺžku reťazca zistíte pomocou štandardnej knižničnej funkcie strlen(), ktorej prototyp sa nachádza v hlavičkovom súbore string.h. Pri zistení symbolu z množiny RW nastavte globálny smerník ptr na prvý znak za zisteným symbolom, ináč na nasledujúci znak reťazca. Funkcia strlen(): int strlen(const char *s); – vráti dĺžku reťazca s bez ukončovacieho znaku '\0'.

Správnosť funkcie overte na reťazci "1 + Fact(4)/3 * max(1;NSD(3;6)) - abs(-2)/SqrT(9)". Riešenie: program c14-7.cpp

Poznámka: V predchádzajúcom cvičení v poli RW predchádza reťazec "SQRT" reťazcu "SQR" preto, aby sa pri porovnávaní reťazcov (v procedúre getSymbol()) pomocou funkcie strnicmp() v kombinácii so strlen() pri prechode prvkami poľa RW v cykle for nevyhodnotil reťazec "SQRT(...)" ako symbol SQR a smerník ptr by následne ukazoval na zvyšok reťazca, t.j. na "T(...)".

65

XV. lekcia: Parametre funkcie main() a príkaz switch Cieľom tejto lekcie je oboznámiť vás s parametrami funkcie main(), ktoré sa v praxi väčšinou využívajú,

pretože dovoľujú spúšťať programy v neinteraktívnom režime. Tzn., že príkazy na ovládanie programu prípadne vstupné údaje sa zadajú priamo z príkazového riadku a program už pracuje automaticky, bez zásahu používateľa. Ďalej sa v lekcii naučíte o príkaze jazyka C, ktorým sa dosahuje mnohonásobné vetvenie programu. Parametre funkcie main() aj príkaz pre mnohonásobné vetvenie sú demonštrované na programe P15.CPP (viď. doleuvedený výpis programu). Program predstavuje úplnú kostru programu kalkulátor, čo sa týka používateľského rozhrania. Vo zvyšných lekciách sa budú už len rozvíjať prípadne dopĺňať nové funkcie, ktorými sa zabezpečuje funkcionalita programu.

Výstupom tejto lekcie bude funkcia str2d(), ktorej prototyp ste navrhli v lekcii XIII. Vašou úlohou v cvičeniach bude zimplementovať túto funkciu a zapracovať ju do funkcie, ktorá vykonáva lexikálnu analýzu vstupného reťazca.

Program uvedený v nasledujúcom výpise je rozšírený program z cvičenia 7 predchádzajúcej lekcie. Program je doplnený o definíciu symbolických konštánt, ktoré predstavujú Booleovské hodnoty; o funkciu str2l(), ktorá konvertuje reťazec na celé číslo typu long; o zobrazenie rozpoznaného čísla vo vstupnom reťazci a o funkciu pouzitie() z cvičenia 4 lekcie XII. Parametre funkcie main() a príkaz pre mnohonásobné vetvenie programu spolu s volaniami príslušných funkcií sú v programe P15.CPP použité na vytvorenie kostry programu kalkulátor. Návod na ovládanie programu kalkulátor získate spustením programu z príkazového riadku s parametrom "–h" resp. "–H".

Výpis programu P15.CPP – rozšírený program C14-7.CPP. 1: #include <stdio.h> 2: #include <conio.h> /* kvoli volaniu clrscr(); */ 3: #include <string.h> 4: 5: /* definicia symbolickych konstant */ 6: #define LINE_LEN 255 7: #define NORW 21 /* pocet rezervovanych slov = mohutnost mnoziny RW */ 8: #define FALSE 0 /* Booleovske hodnoty */ 9: #define TRUE 1 10: 11: /* globalne premenne */ 12: char * RW[] = { /* rezervovane slova pre funkcie a operatory */ 13: "+", 14: "-", 15: "*", 16: "/", 17: "(", 18: ")", 19: "FACT", 20: "NSD", 21: "MIN", 22: "MAX", 23: "ROUND", 24: "CEIL", 25: "FLOOR", 26: "SQRT", 27: "SQR", 28: "PWR", 29: "ROOT", 30: "MOD", 31: "ABS", 32: "TRUNC", 33: ";" 34: }; 35: char * ptr = NULL; /* smernik na aktualny znak v nacitanom retazci */ 36: long cislo; /* cele cislo v nacitanom retazci */

66

37: 38: /* definicia makier s parametrami */ 39: #define isdigit(x) ((x) >= '0' && (x) <= '9') 40: 41: /* prototypy funkcii - deklaracie funkcii */ 42: int skontroluj_zatvorky(char *str); 43: void interaktiv(void); 44: int vypocitaj(char string[]); 45: void getSymbol(); 46: void zo_suboru(char fname1[]); 47: void do_suboru(char fname1[], char fname2[]); 48: void pouzitie(); 49: long str2l(char *str, char **enptr); 50: 51: /* hlavna funkcia */ 52: void main(int argc, char *argv[]) 53: { 54: switch (argc) 55: { 56: case 1: interaktiv(); /* interaktivny vypocet formul */ 57: break; 58: case 2: if (stricmp(argv[1], "-h") == 0) 59: pouzitie(); 60: else 61: zo_suboru(argv[1]); 62: break; 63: case 3: do_suboru(argv[1], argv[2]); 64: break; 65: default: pouzitie(); /* vypis navod na pouzitie */ 66: } 67: } 68: 69: /* definicie uzivatelskych funkcii */ 70: void interaktiv(void) 71: { 72: int i = 0; /* pocitadlo prikazov */ 73: char string[LINE_LEN]; /* retazec nacitany z klavesnice */ 74: 75: clrscr(); 76: 77: while(1) 78: { 79: printf("%d> ", ++i); 80: gets(string); 81: 82: if (strnicmp("/q", string, 2) == 0) 83: return; 84: else 85: /* samotne spracovanie nacitaneho vyrazu */ 86: vypocitaj(string); 87: } 88: } 89: 90: /* Funkcia skontroluje, ci su v retazci str vsetky zatvorky sparovane 91: * a ci su v spravnom poradi. V pripade chyby vrati 0, inac 1. 92: */ 93: int skontroluj_zatvorky(char *str) 94: { 95: int pocet = 0; /* udava pocet otvorenych zatvoriek */ 96: 97: for ( ; *str != '\0'; str++) 98: { 99: if (*str == '(') pocet++;

67

100: 101: if (*str == ')') 102: { 103: pocet--; 104: 105: /* nespravne poradie, ')' predchadza '(' */ 106: if (pocet < 0) return(0); 107: } 108: } 109: 110: /* pocet > 0 => zostali otvorene zatvorky */ 111: if (pocet) return(0); 112: 113: /* pocet == 0 => spravny pocet parov zatvoriek */ 114: return(1); 115: } 116: 117: int vypocitaj(char string[]) 118: { 119: if (!skontroluj_zatvorky(string)) 120: { 121: puts("Chyba: Nespravny pocet zatvoriek alebo ich poradie."); 122: return(-1); 123: } 124: 125: /* nastavenie globalneho smernika ptr na prvy znak retazca */ 126: ptr = string; 127: 128: while (*ptr != '\0') 129: getSymbol(); 130: 131: return(0); 132: } 133: 134: void getSymbol() 135: { 136: int i; /* index do pola RW */ 137: int len; /* dlzka retazca z RW, o ktoru treba posunut smernik ptr */ 138: 139: while (*ptr == ' ' || *ptr == '\t') 140: ptr++; 141: 142: if (*ptr == '\0') 143: { 144: puts("koniec"); 145: return; 146: } 147: 148: if (isdigit(*ptr)) /* je to cislo */ 149: { 150: cislo = str2l(ptr, &ptr); 151: printf("%ld\n", cislo); 152: return; 153: } 154: 155: for (i = 0; i < NORW; i++) 156: if (strnicmp(ptr, RW[i], len=strlen(RW[i])) == 0) 157: { 158: puts(RW[i]); 159: ptr += len; 160: return; 161: } 162:

68

163: puts("neznamy symbol"); 164: ptr++; 165: } 166: 167: /* procedura vypise navod na pouzitie */ 168: void pouzitie() 169: { 170: puts("\nHelp k programu calc\n--------------------"); 171: puts("Spustenie programu: calc.exe [subor1] [subor2]"); 172: puts("bez parametrov - spusti kakulator v interaktivnom mode"); 173: puts("subor1 - vstupny subor, v ktorom su ulozene vyrazy"); 174: puts("subor2 - volitelne meno vystupneho suboru, v ktorom budu vypocitane vyrazy"); 175: puts(" ak nie je zadane, vysledky sa vypisu na obrazovku\n"); 176: } 177: 178: /* Funkcia str2l konvertuje retazec znakov na cele cislo typu long. */ 179: long str2l(char *str, char **enptr) 180: { 181: long x = 0; /* cela cast cisla */ 182: int was_num = FALSE; /* premenna udavajuca, ci bolo zadane cislo */ 183: short unary1 = 1; /* znamienko - unarne plus/minus pred cislom */ 184: char *ptr = str; /* pomocny pointer - zaciatok retazca */ 185: 186: /* preskocenie bielych znakov */ 187: while (*str == ' ' || *str == '\t') 188: str++; 189: 190: /* unarne plus/minus pred cislom */ 191: if (*str == '+' || *str == '-') 192: unary1 = (*str++ == '-' ? -1 : +1); 193: 194: /* samotne cislice */ 195: while (isdigit(*str)) 196: { 197: x = x * 10 + (*str++ - '0'); 198: was_num = TRUE; 199: } 200: 201: /* vystup funkcie */ 202: if (enptr != NULL) 203: *enptr = (was_num ? str : ptr); 204: return(unary1 * x); 205: } 206: 207: void zo_suboru(char fname1[]) 208: { 209: printf("Vstupny subor: '%s'\n", fname1); 210: } 211: 212: void do_suboru(char fname1[], char fname2[]) 213: { 214: printf("Vstupny subor: '%s'\n", fname1); 215: printf("Vystupny subor: '%s'\n", fname2); 216: }

69

Vysvetlivky: int argc – parameter udávajúci počet reťazcov v príkazovom riadku char *argv[] – pole smerníkov na reťazce z príkazového riadku switch – príkaz pre mnohonásobné vetvenie programu case – vetva príkazu switch stricmp – porovnávanie dvoch reťazcov bez rozlišovania malých a veľkých písmen *str++ – získanie hodnoty, na ktorú smerník ukazuje a inkrementácia smerníka po vyhodnotení

výrazu, v ktorom sa *str++ nachádza.

Parametre funkcie main() Doposiaľ sme používali funkciu main() ako funkciu bez parametrov a buď s implicitnou návratovou hodnotou

typu int alebo s návratovou hodnotou typu void. Pomocou návratovej hodnoty možno odovzdať volajúcemu programu – čo je v prípade funkcie main() operač-

ný systém, ktorý program spustil – výsledok práce programu. Napríklad v DOSe sa takto vrátená hodnota zapíše do systémovej premennej ERRORLEVEL, odkiaľ môže byť prečítaná napr. v dávkovom (.bat) súbore. Spôsob využitia návratovej hodnoty ANSI štandard jazyka C nijak nedefinuje a záleží to vždy od konkrétneho operačného systému.

Na rozdiel od návratovej hodnoty, pre parametre funkcie main() platia presné pravidlá. Funkcia main() môže mať jeden, dva alebo žiadny formálny parameter. Hodnoty skutočných parametrov dodáva spúšťajúci operačný systém.

Ak má main() parametre, sú z historických dôvodov pomenované vždy ako argc a argv. Ich účelom je predať programu argumenty zo vstupného príkazového riadku, teda parametre, s ktorými bol program spustený. Väčšina programov totiž pracuje tak, že umožňujú zadať údaje potrebné pre svoje ovládanie už z príkazového riadku. Vtedy je možné program spúšťať aj z dávkového súboru a po spustení sa už nepýta na ďalšie informácie. Pokiaľ používateľ programu nezadá pri spustení parametre, vypíše sa buď návod, alebo sa program spustí v interaktívnom režime (ako je to v našom prípade – viď. funkciu main()).

Ak je náš program P15.EXE spustený príkazom: "p15 priklady.cal vystup.txt" a funkcia main() má hlavičku ako je uvedené v riadku 52, potom má parameter argc hodnotu 3, pretože udáva počet reťazcov na vstup-nom riadku – teda 1. "p15", 2. "priklady.cal", 3. "vystup.txt" a pole smerníkov na reťazce argv ukazuje nasledovne:

argv[0] na reťazec: p15 argv[1] na reťazec: priklady.cal argv[2] na reťazec: vystup.txt

Poznámky: • Argument príkazového riadku, ktorý je uzavretý do úvodzoviek, sa počíta za jeden reťazec. • Pretože jednotlivé parametre vstupného riadku sú normálne reťazce, je možné pre prácu s nimi

používať všetky funkcie, ktoré pracujú s reťazcami.

70

Príkaz switch Jazyk C obsahuje príkaz prepínača, alebo ináč, príkaz pre mnohonásobné vetvenie programu. Prepínač slúži

k vetveniu výpočtu podľa hodnoty celočíselného výrazu. Syntax príkazu switch je nasledovná: switch (výraz) { case hodnota1 : príkazy vetvy 1; break; case hodnota2 : príkazy vetvy 2; break;

... case hodnotaN : príkazy vetvy N; break; default : príkazy vetvy default; break; }

Program vyhodnotí výraz a jeho hodnotu porovná s hodnotou každého návestia case. Ak nastane zhoda hodno-ty case návestia s hodnotou výrazu, riadenie programu je prenesené na toto návestie a vykonajú sa príslušné príkazy tejto vetvy. Ak nenastane zhoda s ani jednou vetvou príkazu switch, riadenie je prenesené na návestie default.

Pokiaľ vetva default nie je uvedená a hodnota výrazu sa nezhoduje so žiadnym z case návestí, riadenie je prene-sené na prvý príkaz nasledujúci za prepínačom (príkazom switch).

Obr.15-1 Riadenie programu pri príkaze switch.

switch

výraz = = hodnotaN

príkazy vetvy 1

príkazy vetvy 2

príkazy vetvy N

výraz = = hodnota2

výraz = = hodnota1 true

true

true

false

false

false

break

break

break

voliteľné príkazy default vetvy

nasledujúci príkaz za switch

71

Ak vetva prepínača nie je ukončená pomocou príkazu break, program neopúšťa switch, ale spracováva príkazy nasledujúcej vetvy v poradí! V tejto činnosti pokračuje až po najbližší príkaz break alebo do ukončenia príkazu switch. Ak teda potrebujeme zabezpečiť, aby sa tie isté príkazy vykonali pre niekoľko hodnôt, vetvy s príslušnými case návestiami neukončíme príkazom break.

Poznámky: • Výraz, podľa ktorého sa rozhoduje, musí byť celočíselného typu (char, int). • Príkazy za návestím default sa vykonajú až vtedy, keď nie je zhoda so žiadnou hodnotou case návestia,

pričom vetva default môže byť v prepínači uvedená kdekoľvek. • Vetvu default používajte vždy, aj keď si myslíte, že ste pokryli všetky možné prípady. • Príkaz break ruší vždy najvnútornejšiu slučku cyklu, alebo ukončuje príkaz switch. Treba teda dávať pozor,

ak switch obsahuje nejaký cyklus alebo naopak. Ak je napr. switch v cykle while, potom break spôsobí ukončenie switch a nie while.

• Používajte príkaz switch namiesto príkazu if, ak sú vyhodnocované viac ako dve podmienky pre tú istú premennú.

Porovnanie switch s konštrukciou if–else–if Príkaz switch dovoľuje programu vykonať príkazy na základe výrazu, ktorý môže nadobúdať viac ako dve

hodnoty. Riadiace štruktúry, ako napr. if, sú obmedzené na vyhodnocovanie výrazov, ktoré môžu nadobúdať iba dve hodnoty: TRUE alebo FALSE. Na riadenie programu podľa viacerých ako dvoch hodnôt by sme museli použiť konštrukciu if–else–if, t.j. niekoľko vnorených if príkazov.

Rozhodovací výraz if môže testovať hodnoty akéhokoľvek typu (je vhodné, aby výsledkom testu bola celočí-selná hodnota, najlepšie 0 a 1, ináč automaticky prebehne konverzia), zatiaľ čo výsledok rozhodovacieho výrazu príkazu switch musí byť výhradne celočíselného typu.

V konštrukcii if–else–if sa vykoná nanajvýš jedna vetva príkazov. Ani pri prepínači sa nemusí vykonať žiadny z príkazov, no môže ich byť vykonaných viacero. Konštantné návestie určuje iba prvý z nich. Pokiaľ chceme, odde-líme nasledujúce príkazy príkazom break.

V prepínači sa návestie default môže vyskytovať kdekoľvek. Zodpovedajúci variant v konštrukcii if–else–if môže byť umiestnený iba na konci (posledná vetva else), prípadne iba na začiatku – no to by sme však museli negovať všetky podmienky.

Analýza funkcie str2l() (riadky 179 až 205)

Funkcia str2l() konvertuje reťazec znakov na celé číslo typu long. str je postupnosť znakov, ktorá môže predstavovať celé číslo typu long. Znaky v str musia zodpovedať tomuto všeobecnému formátu: [ws] [sn] [ddd] kde

[ws] = nepovinné biele znaky (whitespaces, t.j. medzera alebo '\t') [sn] = nepovinné znamienko (sign) pred číslom (+ alebo –) [ddd] = nepovinné číslice (digits) Funkcia zastaví čítanie na prvom znaku, ktorý nezodpovedá hore uvedenému formátu. Návratová hodnota:

– V prípade úspechu str2l() vráti hodnotu v str ako číslo typu long – V prípade chyby str2l() vráti nulu

Výstupný argument enptr: Ak enptr nie je NULL, tzn. že funkcia bola zavolaná s adresou smerníka na typ char, funkcia str2l() nastaví smerník *enptr na znak, pri ktorom funkcia zastavila čítanie (smerník na ďalší znak v reťazci).

72

Pri vstupe do funkcie prebehne nasledovná inicializácia premenných: § x = 0; x predstavuje číslo, ktoré funkcia vráti § was_num = FALSE; was_num je premenná, ktorá indikuje, či sa vo vstupnom reťazci str nachádza číslo.

FALSE je symbolická konštanta definovaná pomocou makra bez parametrov (riadok 8). § unary1 = 1; premenná, ktorá predstavuje hodnotu znamienka pred číslom v reťazci str.

1 = plus, -1 = mínus § *ptr = str; smerník ukazujúci na začiatok reťazca (smerník str sa vo funkcii inkrementuje, a ak sa

vo vstupnom reťazci nenachádza číslo a enptr != NULL, tak funkcia musí v *enptr vrátiť pôvodnú hodnotu str, t.j. smerník na začiatok reťazca).

Prvým krokom funkcie je preskočenie bielych znakov (medzera a tabulátor), ktoré sa vykonáva v cykle while. Ak *str (znak, na ktorý ukazuje str) je ‘ ‘ alebo ‘\t‘ (riadok 187), inkrementuje sa smerník str (posúva sa na ďalší znak v reťazci – riadok 188).

Po preskočení všetkých bielych znakov sa určí (riadok 191), či je v reťazci pred číslom znamienko. Ak áno, tak hodnota premennej unary1 sa nastaví na +1/–1 podľa toho, či je to plus alebo mínus (riadok 192). Výraz *str++ znamená získanie hodnoty *str a inkrementáciu smerníka str po vyhodnotení výrazu (v tomto prípade ternárny operátor), v ktorom sa *str++ nachádza. Riadok 192 je teda ekvivalentný s nasledujúcou dvojicou riadkov:

unary1 = (*str == '-' ? -1 : +1); str++;

Ďalším krokom funkcie je vytvorenie hodnoty čísla (premenná x) v cykle while (riadky 195 až 199). Ak *str predstavuje číslicu (znaky medzi ‘0’ až ‘9’ včítane), tak hodnotu premennej x vynásobí desiatimi (posuv o jeden rád doľava) a pripočíta k nej hodnotu číslice v *str (rozdiel medzi ASCII hodnotami znakov *str a '0'). Zároveň sa nastaví was_num na TRUE, tzn. že v reťazci bolo uvedené číslo (a nie napr. "–a8").

Posledným krokom funkcie je výstup funkcie (riadky 202 až 204). Ak enptr nie je NULL (riadok 202), tzn. že funkcia bola zavolaná s adresou smerníka na typ char, nastaví sa smerník *enptr (riadok 203) buď na znak, pri ktorom funkcia zastavila čítanie (smerník na ďalší znak v reťazci po čísle), alebo na začiatok reťazca (uchovaný v premennej ptr), a to v závislosti na hodnote was_num (viď. predchádzajúci odsek). Nakoniec sa vráti hodnota čísla prenásobená znamienkom pred týmto číslom (riadok 204).

Cvičenia 1. Napíšte program, ktorý vypíše počet argumentov príkazového riadku včítane názvu programu a tieto parametre

vytlačí na obrazovku (každý na samostatnom riadku). Riešenie: program c15-1.cpp

2. Napíšte procedúru s prototypom void hodnotenie(int znamka); ktorá vypíše slovný názov známky (napr.: 1 → "vyborne", 2 → "velmi dobre", …). Ošetrite aj nekorektnú hodnotu známky.

Riešenie: súbor c15-2.cpp

3. Prepíšte príkaz switch v tele funkcie main() programu P15.CPP pomocou konštrukcie if–else–if, a funkciu skontroluj_zatvorky() (telo cyklu for) pomocou príkazu switch.

Riešenie: program c15-3.cpp

4. Prepíšte funkciu str2l() na funkciu str2d() s prototypom double str2d(char *str, char **enptr); ktorá konvertuje reťazec znakov na číslo typu double, tzn. uvažujte konverziu reálnych čísel v reťazci.

Všeobecný formát čísla v reťazci str je: [ws] [sn] [ddd] [.] [ddd] kde [ws] = nepovinné biele znaky [sn] = nepovinné znamienko pred číslom (+ alebo -) [ddd] = nepovinné číslice (0 až 9) [.] = nepovinný znak oddeľovača celej časti od desatinnej

73

Vstupné argumenty funkcie majú ekvivalentný význam ako vo funkcii str2l() a návratová hodnota funkcie je buď číslo typu double (v prípade úspechu) alebo nula (v prípade chyby – ak sa v reťazci nenachádza číslo). Výstupný argument enptr má totožný význam ako pri funkcii str2l().

Na vytvorenie hodnoty desatinnej časti čísla použite funkciu pow() z cvičenia 3 lekcie XIII. Riešenie: program c15-4.cpp

5. Rozšírte funkciu str2d() z predchádzajúceho cvičenia o možnosť konvertovať aj číslo zapísané v exponenciál-nom tvare, t.j. všeobecný formát čísla v reťazci str je: [ws] [sn] [ddd] [.] [ddd] [fmt[sn]ddd] kde [ws] = nepovinné biele znaky [sn] = nepovinné znamienko pred číslom alebo exponentom (+ alebo -) [ddd] = nepovinné číslice (0 až 9) [.] = nepovinný znak oddeľovača celej časti od desatinnej [fmt] = nepovinný znak e alebo E predstavujúci symbol exponentu

Na zistenie symbolu e/E použite makro toupper() z cvičenia 3 lekcie XI. Riešenie: program c15-5.cpp

6. Prepíšte program uvedený na začiatku lekcie (program P15.CPP) pomocou funkcie str2d() z predchádzajúceho cvičenia na program pracujúci s reálnymi číslami.

Riešenie: program c15-6.cpp

Tipy a triky V knižnici MATH (hlavičkový súbor math.h), štandardne dodávanej k prekladaču jazyka C, existuje funkcia

strtod() s prototypom double strtod(const char *s, char **endptr); ktorá vykonáva rovnakú funkciu ako funkcia str2d() implementovaná v cvičení 5. Avšak táto knižničná funkcia nepracuje korektne (výstupný parameter *endptr) pre reťazce, ktoré obsahujú symbol e/E, no číslo zapísané v nich nie je v exponenciálnom tvare. Napr.: char *ptr;

... strtod("5.27eabc", &ptr);

ptr bude ukazovať na "abc" a nie na očakávaný reťazec "eabc"!

74

XVI. lekcia: Definícia nového typu, vymenovaný typ, operátor čiarky Cieľom tejto lekcie je naučiť vás, ako môžete vytvoriť v jazyku C užívateľský typ. Ďalej sa naučíte, čo je to

vymenovaný typ, na čo slúži a kedy sa používa. Pomocou vymenovaného typu budeme implementovať funkciu getSymbol(), ktorá realizuje lexikálny analyzátor cieľového programu kalkulátor. Úlohou lexikálneho analyzátora je rozpoznať symboly vo vstupnom reťazci, ktorý obsahuje matematický výraz. Nakoniec sa v lekcii oboznámite s operátorom sekvenčného spracovania (operátor čiarky).

Výstupom tejto lekcie bude program P16.CPP upravený tak, že aplikačne nezávislé časti programu kalkulátor (t.j. všetky funkcie, ktoré sa dajú použiť aj pre iný program ako je kalkulátor) budú presunuté do „knižničného“ súboru FUNKCIE.CPP, a ten bude vložený do tohto programu. Knižnica FUNKCIE.CPP bude obsahovať prevažne funkcie, ktoré boli implementované v predchádzajúcich lekciách.

Výpis programu P16.CPP – program vychádza C15-6.CPP. 1: #include <stdio.h> 2: #include <conio.h> 3: #include <string.h> 4: 5: /* definicia symbolickych konstant */ 6: #define LINE_LEN 255 /* dlzka jedneho riadku zadaneho z klavesnice */ 7: #define NORW 21 /* pocet rezervovanych slov = mohutnost mnoziny RW */ 8: 9: /* definicia vymenovanych typov */ 10: typedef enum {FALSE, TRUE} BOOLEAN; /* Booleovske hodnoty */ 11: typedef enum {errsym = -1, 12: plussym, minussym, mulsym, divsym, 13: lparen, rparen, 14: factsym, nsdsym, minsym, maxsym, 15: roundsym, ceilsym, floorsym, 16: sqrtsym, sqrsym, pwrsym, rootsym, 17: modsym, abssym, truncsym, 18: /* symbol oddelovaca ';' */ 19: semicolon, 20: /* a teraz dva specialne symboly: cislo a koniec retazca '\0' */ 21: number, 22: endsym 23: } SYM_OP; /* symboly funkcii a operatorov (+ cisla a oddelovaca) */ 24: 25: /* globalne premenne */ 26: char * RW[] = { /* rezervovane slova pre funkcie a operatory */ 27: "+", 28: "-", 29: "*", 30: "/", 31: "(", 32: ")", 33: "FACT", 34: "NSD", 35: "MIN", 36: "MAX", 37: "ROUND", 38: "CEIL", 39: "FLOOR", 40: "SQRT", 41: "SQR", 42: "PWR", 43: "ROOT", 44: "MOD", 45: "ABS", 46: "TRUNC", 47: ";"

75

48: }; 49: char * ptr = NULL;/* smernik na aktualny znak v nacitanom retazci */ 50: SYM_OP sym; /* prvy symbol - vrateny z nacitaneho retazca pomocou getSymbol() */ 51: double cislo; /* ak je sym cislo, tak premenna cislo obsahuje hodnotu cisla */ 52: 53: /* definicia makier s parametrami */ 54: #define abs(x) ((x) < 0 ? -(x) : (x)) 55: #define isdigit(x) ((x) >= '0' && (x) <= '9') 56: #define islower(x) ((x) >= 'a' && (x) <= 'z') 57: #define toupper(ch) (islower((ch)) ? (ch) - ('a' - 'A') : (ch)) 58: 59: /* prototypy funkcii - deklaracie funkcii */ 60: int skontroluj_zatvorky(char *str); 61: void interaktiv(void); 62: int vypocitaj(char string[]); 63: void getSymbol(); 64: void zo_suboru(char fname1[]); 65: void do_suboru(char fname1[], char fname2[]); 66: void pouzitie(); 67: double str2d(char *str, char **enptr); 68: double pow(double x, int y); 69: void print_sym(SYM_OP sym); 70: 71: /* hlavna funkcia */ 72: void main(int argc, char *argv[]) 73: { 74: switch (argc) 75: { 76: case 1: interaktiv(); /* interaktivny vypocet formul */ 77: break; 78: case 2: if (stricmp(argv[1], "-h") == 0) 79: pouzitie(); 80: else 81: zo_suboru(argv[1]); 82: break; 83: case 3: do_suboru(argv[1], argv[2]); 84: break; 85: default: pouzitie(); /* vypis navod na pouzitie */ 86: } 87: } 88: 89: /* definicie uzivatelskych funkcii */ 90: void interaktiv(void) 91: { 92: int i = 0; /* pocitadlo prikazov */ 93: char string[LINE_LEN]; /* retazec nacitany z klavesnice */ 94: 95: clrscr(); 96: 97: while(1) 98: { 99: printf("%d> ", ++i); 100: gets(string); 101: 102: if (strnicmp("/q", string, 2) == 0) 103: return; 104: else 105: /* samotne spracovanie nacitaneho vyrazu */ 106: vypocitaj(string); 107: } 108: } 109:

76

110: /* Funkcia skontroluje, ci su v retazci str vsetky zatvorky sparovane 111: * a ci su v spravnom poradi. V pripade chyby vrati 0, inac 1. 112: */ 113: int skontroluj_zatvorky(char *str) 114: { 115: int pocet = 0; /* udava pocet otvorenych zatvoriek */ 116: 117: for ( ; *str != '\0'; str++) 118: { 119: if (*str == '(') pocet++; 120: 121: if (*str == ')') 122: { 123: pocet--; 124: 125: /* nespravne poradie, ')' predchadza '(' */ 126: if (pocet < 0) return(0); 127: } 128: } 129: 130: /* pocet > 0 => zostali otvorene zatvorky */ 131: if (pocet) return(0); 132: 133: /* pocet == 0 => spravny pocet parov zatvoriek */ 134: return(1); 135: } 136: 137: int vypocitaj(char string[]) 138: { 139: if (!skontroluj_zatvorky(string)) 140: { 141: puts("Chyba: Nespravny pocet zatvoriek alebo ich poradie."); 142: return(-1); 143: } 144: 145: /* nastavenie globalneho smernika ptr na prvy znak retazca */ 146: ptr = string; 147: 148: /* Pomocny vypis nacitanych symbolov */ 149: while (getSymbol(), sym != endsym && sym != errsym) 150: { 151: print_sym(sym); 152: putchar('\n'); 153: } 154: 155: if (sym != endsym) 156: { 157: puts("Chyba vo vyraze alebo neznama funkcia!"); 158: return(-1); 159: } 160: 161: return(0); 162: } 163: 164: /* Funkcia getSymbol() - lexikalny analyzator */ 165: void getSymbol() 166: { 167: int i; /* index do pola RW */ 168: int len; /* dlzka retazca z RW, o ktoru treba posunut smernik ptr */ 169: 170: while (*ptr == ' ' || *ptr == '\t') 171: ptr++; 172:

77

173: if (*ptr == '\0') 174: { 175: sym = endsym; 176: return; 177: } 178: 179: if (isdigit(*ptr) || *ptr == '.') /* je to cislo */ 180: { 181: cislo = str2d(ptr, &ptr); 182: sym = number; 183: return; 184: } 185: 186: for (i = 0; i < NORW; i++) 187: if (strnicmp(ptr, RW[i], len=strlen(RW[i])) == 0) 188: { 189: sym = (SYM_OP) i; 190: ptr += len; 191: return; 192: } 193: 194: /* ak presiel az sem, tak ide o chybny operator */ 195: sym = errsym; 196: } 197: 198: /* procedura vypise navod na pouzitie */ 199: void pouzitie() 200: { 201: puts("\nHelp k programu calc\n--------------------"); 202: puts("Spustenie programu: calc.exe [subor1] [subor2]"); 203: puts("bez parametrov - spusti kakulator v interaktivnom mode"); 204: puts("subor1 - vstupny subor, v ktorom su ulozene vyrazy"); 205: puts("subor2 - volitelne meno vystupneho suboru, v ktorom budu vypocitane vyrazy"); 206: puts(" ak nie je zadane, vysledky sa vypisu na obrazovku\n"); 207: } 208: 209: /* Funkcia str2d konvertuje retazec znakov na realne cislo typu double. */ 210: double str2d(char *str, char **enptr) 211: { 212: double x = 0; /* cast cisla pred desatinnou ciarkou */ 213: double y = 0; /* cast cisla po desatinnej ciarke */ 214: int z = 0; /* cast cisla v exponente */ 215: int i = 0; /* premenna cyklu */ 216: BOOLEAN was_num = FALSE;/* premenna udavajuca, ci bola zadana aspon jedna cast 217: cisla (pred alebo po desatinnej ciarke) a cislo 218: v exponente (po znaku E) */ 219: short unary1 = 1; /* znamienko - unarne plus/minus pred cislom */ 220: short unary2 = 1; /* znamienko - unarne plus/minus pred exponentom */ 221: char *ptr = str; /* pomocny pointer - zaciatok retazca */ 222: 223: /* preskocenie bielych znakov */ 224: while (*str == ' ' || *str == '\t') 225: str++; 226: 227: /* unarne plus/minus pred cislom */ 228: if (*str == '+' || *str == '-') 229: unary1 = (*str++ == '-' ? -1 : +1); 230: 231: /* cast cisla pred desatinnou ciarkou */ 232: while (isdigit(*str)) 233: { 234: x = x * 10 + (*str++ - '0');

78

235: was_num = TRUE; 236: } 237: 238: /* zistenie casti po desatinnej ciarke */ 239: if (*str == '.') 240: { 241: str++; 242: 243: /* cast cisla po desatinnej ciarke */ 244: while (isdigit(*str)) 245: { 246: y += (*str++ - '0') * pow(10,--i); 247: was_num = TRUE; 248: } 249: 250: } 251: 252: /* nezistil som pritomnost cisla => skonci */ 253: if (!was_num) 254: { 255: if (enptr != NULL) 256: *enptr = ptr; 257: return(0); 258: } 259: 260: /* pred zistovanim cisla v exponente, resetuj premenne kvoli tomu, 261: ze za symbolom E nemusi byt cislo 262: */ 263: ptr = str; was_num = FALSE; 264: 265: /* zistenie exponentu */ 266: if (toupper(*str) == 'E') 267: { 268: str++; 269: 270: /* unarne plus/minus pred cislom v exponente */ 271: if (*str == '+' || *str == '-') 272: unary2 = (*str++ == '-' ? -1 : +1); 273: 274: /* cast cisla v exponente */ 275: while (isdigit(*str)) 276: { 277: z = z * 10 + (*str++ - '0'); 278: was_num = TRUE; 279: } 280: } 281: 282: /* vystup funkcie */ 283: if (enptr != NULL) 284: *enptr = (was_num ? str : ptr); 285: return(unary1 * (x + y) * pow(10, unary2 * z)); 286: } 287: 288: void zo_suboru(char fname1[]) 289: { 290: printf("Vstupny subor: '%s'\n", fname1); 291: } 292: 293: void do_suboru(char fname1[], char fname2[]) 294: { 295: printf("Vstupny subor: '%s'\n", fname1); 296: printf("Vystupny subor: '%s'\n", fname2); 297: }

79

298: 299: double pow(double x, int y) 300: { 301: int i; 302: double vysledok = 1; 303: 304: if (y == 0) return(1); 305: 306: for (i = 1; i <= abs(y); i++) 307: vysledok *= (y < 0 ? 1/x : x); 308: 309: return(vysledok); 310: } 311: 312: /* Procedura print_sym vypise slovny nazov symbolu sym. */ 313: void print_sym(SYM_OP sym) 314: { 315: switch(sym) 316: { 317: case errsym : printf("\nChyba vo vyraze!\n"); break; 318: case plussym : printf("scitanie [+]"); break; 319: case minussym : printf("odcitanie [-]"); break; 320: case mulsym : printf("nasobenie [*]"); break; 321: case divsym : printf("delenie [/]"); break; 322: case lparen : printf("lava zatvorka [(]"); break; 323: case rparen : printf("prava zatvorka [)]"); break; 324: case factsym : printf("faktorial [FACT]"); break; 325: case nsdsym : printf("najvacsi spolocny delitel [NSD]"); break; 326: case minsym : printf("minimum [MIN]"); break; 327: case maxsym : printf("maximum [MAX]"); break; 328: case roundsym : printf("zaokruhlovanie [ROUND]"); break; 329: case ceilsym : printf("zaokruhlovanie nahor [CEIL]"); break; 330: case floorsym : printf("zaokruhlovanie nadol [FLOOR]"); break; 331: case sqrsym : printf("druha mocnina [SQR]"); break; 332: case sqrtsym : printf("druha odmocnina [SQRT]"); break; 333: case pwrsym : printf("n-ta mocnina [PWR]"); break; 334: case rootsym : printf("n-ta odmocnina [ROOT]"); break; 335: case modsym : printf("modulo [MOD]"); break; 336: case abssym : printf("absolutna hodnota [ABS]"); break; 337: case truncsym : printf("cela cast cisla [TRUNC]"); break; 338: case semicolon: printf("oddelovac [;]"); break; 339: case number : printf("cislo %lG",cislo); break; 340: case endsym : printf("koniec vyrazu"); break; 341: default : printf("\nNeznamy symbol vo vyraze!\n"); 342: } 343: }

Vysvetlivky: typedef – kľúčové slovo označujúce definíciu nového typu enum – kľúčové slovo na definovanie vymenovaného typu , (riadok 149) – operátor čiarky

Definícia nového typu Kľúčové slovo typedef sa používa na vytvorenie nového mena pre existujúci typ údajov – typedef teda vytvára

synonymum. Napríklad príkaz typedef int integer;

vytvorí typ integer ako synonymum pre int. Následne môžeme používať integer na definíciu premenných typu int, na deklaráciu, pretypovanie, atď.

80

Syntax pre všeobecný tvar príkazu typedef je: typedef <existujúci typ> <identifikátor>; Sémantika pre horeuvedenú syntax príkazu: Príkaz vytvorí nový identifikátor pre existujúci typ údajov.

Poznámky: • typedef nevytvára nový dátový typ, ale nám iba dovoľuje používať odlišné meno pre preddefinovaný

údajový typ. • Vytvorením nového identifikátora nedefinujeme novú premennú, ktorá vyhradzuje pamäť, ale definujeme

nový typ, ktorý iba určuje vzorec (šablónu) pre ďalšie akcie (definíciu, deklaráciu, pretypovanie, ...). V našom programe sme v riadku 10 vytvorili nové meno BOOLEAN pre vymenovaný typ údajov, ktoré sme

v riadku 216 použili na definíciu premennej was_num. V riadkoch 11 až 23 sme zase vytvorili nový typ nazvaný SYM_OP, pomocou ktorého sme definovali premennú sym (riadok 50), deklarovali funkciu print_sym() (riadok 69) a pretypovali premennú i typu integer (riadok 189).

Vymenovaný typ Vymenovaný typ (angl. enumerated type) nám dovoľuje definovať zoznam symbolických konštánt, ktoré môžu

byť, a najčastejšie aj sú, vzájomne závislé. Takto definované symbolické konštanty nemajú síce nič spoločné s preprocessingom (viď. lekciu XI), ale používajú sa rovnakým spôsobom. Použitím konštánt z vymenovaného typu sprehľadňujeme program a zvyšujeme jeho modularitu. Samotný vymenovaný typ nám iba združuje konštanty.

V riadku 10 máme definovaný vymenovaný typ, ktorý sa skladá z dvoch konštánt FALSE a TRUE. Konštanty sa zapisujú do zložených zátvoriek a sú oddelené čiarkami. Poradie identifikátorov v zložených zátvorkách je dôležité! Pokiaľ explicitne nepriradíme číselné hodnoty jednotlivým prvkom vymenovaného typu (pomocou konštrukcie identifikátor = hodnota), potom majú tieto prvky implicitné hodnoty 0, 1, 2, atď. Prvý prvok má teda implicitne hodnotu 0 a každý následník má hodnotu o jedničku väčšiu než jeho predchodca.

Vymenovaný typ sa použije vždy vtedy, ak majú konštanty nejakú vzájomnú súvislosť, napr. Booleovské hodnoty TRUE a FALSE. Tieto konštanty teda nebudú definované ako v predchádzajúcich lekciách pomocou makier bez parametrov:

#define FALSE 0 #define TRUE 1

ale na ich definíciu použijeme vymenovaný typ, ako je uvedené v riadku 10: typedef enum {FALSE, TRUE} BOOLEAN;

Takto definované konštanty potom možno využívať v najrôznejších prípadoch, napr.: if (isdigit(x) == FALSE)

čiže vôbec nie je potrebné definovať premennú tohto vymenovaného typu, stačí iba definícia vymenovaného typu BOOLEAN (a je možné využívať priamo prvky tohto typu).

V riadkoch 11 až 23 sme definovali vymenovaný typ SYM_OP, v ktorom sme definovali symboly jednotlivých matematických funkcií a operátorov plus symbol čísla (number), symbol konca reťazca (endsym) a symbol oddeľo-vača (semicolon). Prvému prvku (symbol chyby – errsym) sme explicitne priradili hodnotu –1 a nasledovným symbolom sme hodnoty nepriradili, tzn. že implicitne nadobúdajú hodnoty 0, 1, 2, ..., 22 (= hodnota endsym).

Je možné explicitne inicializovať len niektoré prvky a pre ostatné prvky inicializované implicitne platí, že ich hodnota je vždy o 1 väčšia než hodnota predchádzajúceho prvku. Následník môže mať explicitne priradenú hodnotu, ktorá nemusí súvisieť s predchodcom. Takto môžu vzniknúť „diery“ v číslovaní, prípadne i synonymá. Vďaka tejto možnosti nemôže prekladač kontrolovať, či premenná nadobúda korektné hodnoty alebo nie.

Jednotlivé definované premenné typu enum sú vnútorne reprezentované ako pamäťovo najmenej náročný celočíselný typ, ktorý ešte môže obsahovať hodnoty daného typu enum. Napr. definícia premennej was_num v riadku 216 je vnútorne reprezentovaná typom unsigned char a definícia globálnej premennej sym v riadku 50 je vnútorne reprezentovaná typom signed char. Hodnoty symbolických konštánt vymenovaného typu sú teda reprezentované podmnožinou oboru hodnôt typu int a môžu byť použité kdekoľvek v programe, kde je dovolené použiť konštantu typu int.

81

Všimnite si, že v našom príklade sme definovali vymenovaný typ SYM_OP zámerne tak, aby sa hodnoty jednotlivých konštánt (symbolov mat. funkcií a operátorov) v rozsahu plussym až semicolon zhodovali s indexom prvkov poľa RW[] (riadky 26 až 48) s rovnakým významom. Prakticky teda platí: plussym = 0 ⇔ RW[0] = “+”, minussym = 1 ⇔ RW[1] = “–”, …, semicolon = 20 ⇔ RW[20] = “;”. Túto vlastnosť sme využili vo funkcii getSymbol() (riadok 189) pri zisťovaní symbolu v reťazci reprezentovanom smerníkom ptr. Premenná i, ktorou prechádzame v cykle for jednotlivými prvkami poľa smerníkov na char (pole RW) a porovnávame ich s reťazcom, na ktorý ukazuje smerník ptr do maximálnej dĺžky len, je typu int (riadok 167). Pri zhode, t.j. nájdení reťazcovej konštanty reprezentujúcej matematickú funkciu/operátor, nastavíme (riadok 189) hodnotu globálnej premennej sym na symbolickú konštantu z vymenovaného typu SYM_OP. Predtým však ešte pretypujeme (pozri explicitnú typovú konverziu v lekcii X) premennú i na typ SYM_OP, aby prekladač nehlásil varovania o nekompatibilite typov.

Poznamenajme, že hodnoty vymenovaných typov nie je možné posielať na výstup v tvare, v akom sme ich definovali. Môžeme ich zobraziť iba ako odpovedajúce celočíselné ekvivalenty. Obdobne ich môžeme čítať zo vstupu. Konštanty vymenovaného typu sa teda vo svojej textovej podobe nachádzajú iba v zdrojovom tvare programu. Preložený program pracuje už iba s číselnými hodnotami symbolických konštánt vymenovaného typu.

Pokiaľ skutočne potrebujeme vytlačiť meno položky, potom je vhodným riešením napr. použitie prepínača switch, čo je univerzálne (ale trochu rozvláčne) riešenie. V našom príklade sme definovali procedúru print_sym() (riadky 313 až 343), ktorá vypíše slovný názov symbolickej konštanty sym zadanej ako vstupný parameter. Toto riešenie je výhodné najmä v prípadoch, keď sú položky vymenovaného typu inicializované rôznymi hodnotami (nie sekvenčne za sebou ako pri implicitnej inicializácii).

Druhou, častejšie využívanou možnosťou, ako vytlačiť meno položky, je využitie poľa smerníkov na char. Napr. v našom príklade pole RW. Položky vymenovaného typu SYM_OP v rozsahu plussym až semicolon môžeme vytla-čiť pomocou príkazu printf("%s", RW[sym]); kde sym je premenná typu SYM_OP. Tento spôsob je elegantný, ale použiteľný iba pre neinicializovaný vymenovaný typ, kedy hodnoty položiek začínajú od 0 (= index prvého prv-ku poľa).

Operátor čiarky Iba štyri operátory v C zaručujú vyhodnotenie ľavého operandu pred vyhodnotením pravého operandu. Sú to

logický súčin ( && ), logický súčet ( || ), ternárny operátor ( ? : ) a operátor čiarky ( , ). Syntax operátoru čiarky je jednoduchá. Výraz je zložený z podvýrazov, ktoré sú oddelené čiarkou:

výraz1, výraz2 Výsledok je nasledovný: § Obidva výrazy sú vyhodnotené, pričom je najprv vyhodnotený ľavý výraz (výraz1). § Celý výraz je vyhodnotený na hodnotu pravého výrazu (výraz2).

Poznámka: Operátor čiarky má najnižšiu prioritu zo všetkých operátorov v jazyku C a preto treba pri príkazoch používajúcich operátor čiarky zväčša použiť zátvorky.

V našom príklade sme v riadku 149 použili operátor čiarky, ktorý zabezpečuje nasledovné spracovanie pod-mienky cyklu while. Najskôr sa zavolá procedúra getSymbol(), ktorá nastaví hodnotu globálnej premennej sym. Následne sa testuje hodnota sym na nerovnosť so symbolmi endsym a errsym, čo je zároveň aj výsledok použitia výrazu s operátorom čiarky.

Operátor čiarky používajte iba v riadiacich častiach príkazov for a while – napr.: for (i = 0, j = 0; i < MAX; i++, j++)

82

Analýza funkcie getSymbol() (riadky 165 až 196)

Funkcia getSymbol() predstavuje lexikálny analyzátor. Úlohou lexikálnej analýzy je vytvoriť z jednotlivých znakov vstupného reťazca vyššie (lexikálne) jednotky – symboly, ako je napr. číslo, matematická funkcia, oddeľovač a pod. Funkcia getSymbol() prechádza vstupným reťazcom, ktorý je reprezentovaný globálnym smerníkom ptr. Prvý rozpoznaný reťazec z poľa RW v reťazci, na ktorý ukazuje ptr, konvertuje na symbol z množiny symbolických konštánt SYM_OP a tento symbol uloží do globálnej premennej sym.

Prvým krokom funkcie je preskočenie bielych znakov (medzera a tabulátor), ktoré sa vykonáva v cykle while v riadkoch 170 a 171. Ak *ptr (znak, na ktorý ukazuje ptr) nadobúda hodnotu ‘ ‘ alebo ‘\t‘, smerník ptr sa inkre-mentuje (posúva na ďalší znak v reťazci).

Po preskočení všetkých bielych znakov sa testuje reťazec na nulový znak ‘\0‘ (riadok 173). V prípade úspechu, t.j. dosiahnutia konca reťazca, sa sym nastaví na endsym (symbol konca reťazca) a lexikálna analýza končí (riadok 176).

V prípade nedosiahnutia konca reťazca analýza ďalej pokračuje a v riadku 179 sa testuje prítomnosť čísla v reťazci. Číslo začína buď číslicou – volanie makra isdigit(), alebo desatinnou čiarkou – porovnanie na znak ‘.’. Ak je podmienka splnená, do globálnej premennej cislo sa priradí hodnota čísla v reťazci a smerník ptr sa nastaví na prvý znak za číslom (riadok 181). Hodnota globálnej premennej sym sa nastaví na number (prvok množiny SYM_OP), čo indikuje symbol pre čísla (riadok 182). Následne lexikálna analýza končí a program funkciu opúšťa (riadok 183).

V riadkoch 186 až 192 prebieha lexikálna analýza reťazca na výskyt symbolov matematických funkcií a operá-torov. Bližšiu špecifikáciu testovania a nastavenia premennej sym nájdete v časti „Vymenovaný typ“ tejto lekcie. Za zmienku stojí iba posuv smerníka ptr na ďalší znak za rozpoznaným symbolom (riadok 190). V prípade rozpoz-nania symbolu pomocou funkcie strnicmp() sa v riadku 187 nastaví dĺžka reťazca predstavujúceho matematickú funkciu do premennej len a v riadku 190 sa o túto dĺžku inkrementuje smerník ptr.

Ak lexikálny analyzátor nerozpozná symbol v reťazci (nie je to ani číslo, ani žiadna matematická funkcia z poľa RW), funkcia getSymbol() nastaví premennú sym na errsym (riadok 195), ktorý označuje chybný symbol.

Cvičenia 1. Prepíšte procedúru print_sym() tak, že na vypísanie slovného názvu symbolu využijete pole RW. Na výpis

symbolov, ktoré nemajú ekvivalent v RW (errsym, endsym a number) použite príkaz switch. V prípade symbolu number vypíšte aj hodnotu získaného čísla.

Riešenie: program c16-1.cpp

2. Presuňte programovo nezávislé časti (matematické funkcie, ktoré môžeme využiť aj v iných programoch) do nového súboru FUNKCIE.CPP a ten vložte do zdrojového súboru programu P16.CPP pomocou direktívy #include (viď. lekcia XI). Viacnásobnému vloženiu súboru pomocou direktívy #include zabránite pomocou doleuvedenej konštrukcie preprocesora. Viacnásobná definícia funkcie/makra je v jazyku C neprípustná! Do súboru FUNKCIE.CPP umiestnite príkazy preprocesora pre podmienený preklad:

#ifndef __funkcie_cpp #define __funkcie_cpp ... obsah súboru funkcie.cpp ... #endif

Do súboru FUNKCIE.CPP presuňte nasledovné funkcie a makrá z doteraz prebraných lekcií: • vymenovaný typ BOOLEAN z P16.CPP • makrá s parametrami: isdigit, islower a toupper z cvičenia 3 lekcie XI • makrá s parametrami: max, min z cvičenia 4 a trunc z cvičenia 2 lekcie XI • makrá s parametrami: abs a sqr zo súboru P11.CPP • funkcie: skontroluj_zatvorky() a str2d() z P16.CPP • funkciu root() z cvičenia 3 lekcie VII; zvýšte presnosť výpočtu nastavením konštanty EPSILON na 1E-99

83

• funkciu pow() z cvičenia 3 lekcie VIII • funkciu faktorial() z cvičenia 5 lekcie IX • funkciu NSD() z cvičenia 6 lekcie VI • funkciu ceil() z cvičenia 2 lekcie X • funkciu floor() z cvičenia 3 lekcie X • funkciu round() z cvičenia 5 lekcie XI

Riešenie: program c16-2.cpp a súbor funkcie.cpp

84

XVII. lekcia: Práca so súbormi, štandardný vstup/výstup Cieľom tejto lekcie je naučiť vás pracovať so súbormi v programoch jazyka C. Zoznámite sa s tým, ako sa pri-

stupuje k súborom a na aké dve hlavné skupiny rozdeľuje súbory jazyk C. Dozviete sa, prečo je potrebné uzavierať súbory ihneď po ukončení práce s nimi. Ďalej sa naučíte, ako jazyk C vnútorne reprezentuje štandardný vstup/výstup, a ako ho je možné presmerovať napr. na vstup/výstup zo/do súboru bez zásahu do vlastného programu. Naučíte sa pracovať s funkciami pre čítanie/zápis zo/do súboru pomocou troch rôznych spôsobov a nakoniec sa doz-viete, načo slúži štandardný chybový výstup a prečo sa v programoch používa.

Výstupom tejto lekcie bude definitívna kostra programu kalkulátor, ktorá realizuje čítanie vstupných reťazcov predstavujúcich matematické výrazy buď v interaktívnom režime, kedy používateľ programu zadáva reťazce z klá-vesnice, alebo v dávkovom režime, kedy používateľ špecifikuje súbor, v ktorom sa nachádzajú matematické výrazy, prípadne súbor, do ktorého sa majú uložiť výsledky vypočítané programom kalkulátor.

Výpis programu P17.CPP – vychádza z programu C16-2.CPP a FUNKCIE.CPP; odlišuje sa iba vo funkcii do_suboru(), ktorej vnútro je rozvinuté.

1: #include <stdio.h> 2: #include <conio.h> 3: #include <string.h> 4: #include "funkcie.cpp" 5: 6: /* --- definicia symbolickych konstant --- */ 7: /* dlzka jedneho riadku vstupneho suboru resp. riadku zadaneho z klavesnice */ 8: #define LINE_LEN 255 9: /* pocet rezervovanych slov = mohutnost mnoziny RW */ 10: #define NORW 21 11: 12: /* --- definicia vymenovanych typov --- */ 13: typedef enum {errsym = -1, 14: plussym, minussym, mulsym, divsym, 15: lparen, rparen, 16: factsym, nsdsym, minsym, maxsym, 17: roundsym, ceilsym, floorsym, 18: sqrtsym, sqrsym, pwrsym, rootsym, 19: modsym, abssym, truncsym, 20: /* symbol oddelovaca ';' */ 21: semicolon, 22: /* a teraz dva specialne symboly: cislo a koniec retazca '\0' */ 23: number, 24: endsym 25: } SYM_OP; /* symboly funkcii a operatorov (+ cisla a oddelovaca) */ 26: 27: /* --- globalne premenne --- */ 28: char * RW[] = { /* rezervovane slova pre funkcie a operatory */ 29: "+", 30: "-", 31: "*", 32: "/", 33: "(", 34: ")", 35: "FACT", 36: "NSD", 37: "MIN", 38: "MAX", 39: "ROUND", 40: "CEIL", 41: "FLOOR", 42: "SQRT", 43: "SQR", 44: "PWR", 45: "ROOT",

85

46: "MOD", 47: "ABS", 48: "TRUNC", 49: ";" 50: }; 51: char * ptr = NULL; /* smernik na aktualny znak v nacitanom retazci */ 52: SYM_OP sym; /* prvy symbol - vrateny z nacitaneho retazca pomocou getSymbol() */ 53: double cislo; /* ak je sym cislo, tak premenna cislo obsahuje hodnotu cisla */ 54: 55: /* --- prototypy funkcii - deklaracie funkcii --- */ 56: void interaktiv(void); 57: int vypocitaj(char string[]); 58: void getSymbol(); 59: void zo_suboru(char fname1[]); 60: void do_suboru(char fname1[], char fname2[]); 61: void pouzitie(); 62: void print_sym(SYM_OP sym); 63: 64: /* --- hlavna funkcia --- */ 65: void main(int argc, char *argv[]) 66: { 67: switch (argc) 68: { 69: case 1: interaktiv(); /* interaktivny vypocet formul */ 70: break; 71: case 2: if (stricmp(argv[1], "-h") == 0) 72: pouzitie(); 73: else 74: zo_suboru(argv[1]); 75: break; 76: case 3: do_suboru(argv[1], argv[2]); 77: break; 78: default: pouzitie(); /* vypis navod na pouzitie */ 79: } 80: } 81: 82: /* --- definicie uzivatelskych funkcii --- */ 83: void interaktiv(void) 84: { 85: int i = 0; /* pocitadlo prikazov */ 86: char string[LINE_LEN]; /* retazec nacitany z klavesnice */ 87: 88: clrscr(); 89: 90: while(1) 91: { 92: printf("%d> ", ++i); 93: gets(string); 94: 95: if (strnicmp("/q", string, 2) == 0) 96: return; 97: else 98: /* samotne spracovanie nacitaneho vyrazu */ 99: vypocitaj(string); 100: } 101: } 102: 103: int vypocitaj(char string[]) 104: { 105: if (!skontroluj_zatvorky(string)) 106: { 107: puts("Chyba: Nespravny pocet zatvoriek alebo ich poradie."); 108: return(-1);

86

109: } 110: 111: /* nastavenie globalneho smernika ptr na prvy znak retazca */ 112: ptr = string; 113: 114: /* Pomocny vypis nacitanych symbolov */ 115: while (getSymbol(), sym != endsym && sym != errsym) 116: { 117: print_sym(sym); 118: putchar('\n'); 119: } 120: 121: if (sym != endsym) 122: { 123: puts("Chyba vo vyraze alebo neznama funkcia!"); 124: return(-1); 125: } 126: 127: return(0); 128: } 129: 130: /* Funkcia getSymbol() - lexikalny analyzator */ 131: void getSymbol() 132: { 133: int i; /* index do pola RW */ 134: int len; /* dlzka retazca z RW, o ktoru treba posunut smernik ptr */ 135: 136: while (*ptr == ' ' || *ptr == '\t') 137: ptr++; 138: 139: if (*ptr == '\0') 140: { 141: sym = endsym; 142: return; 143: } 144: 145: if (isdigit(*ptr) || *ptr == '.') /* je to cislo */ 146: { 147: cislo = str2d(ptr, &ptr); 148: sym = number; 149: return; 150: } 151: 152: for (i = 0; i < NORW; i++) 153: if (strnicmp(ptr, RW[i], len=strlen(RW[i])) == 0) 154: { 155: sym = (SYM_OP) i; 156: ptr += len; 157: return; 158: } 159: 160: /* ak presiel az sem, tak ide o chybny operator */ 161: sym = errsym; 162: } 163: 164: /* procedura vypise navod na pouzitie */ 165: void pouzitie() 166: { 167: puts("\nHelp k programu calc\n--------------------"); 168: puts("Spustenie programu: calc.exe [subor1] [subor2]"); 169: puts("bez parametrov - spusti kakulator v interaktivnom mode"); 170: puts("subor1 - vstupny subor, v ktorom su ulozene vyrazy");

87

171: puts("subor2 - volitelne meno vystupneho suboru, v ktorom budu vypocitane vyrazy"); 172: puts(" ak nie je zadane, vysledky sa vypisu na obrazovku\n"); 173: } 174: 175: void zo_suboru(char fname1[]) 176: { 177: printf("Vstupny subor: '%s'\n", fname1); 178: } 179: 180: void do_suboru(char fname1[], char fname2[]) 181: { 182: FILE *fr, *fw; /* subor na citanie (fr) a na zapis (fw) */ 183: char line[LINE_LEN]; /* jeden riadok vstupneho suboru */ 184: 185: /* otvorenie suboru fname1 na citanie */ 186: if ((fr = fopen(fname1, "rt")) == NULL) 187: { 188: fprintf(stderr, "Nemozem otvorit subor '%s'!\n", fname1); 189: fprintf(stderr, "Skontrolujte meno suboru.\n\n"); 190: return; 191: } 192: 193: /* otvorenie suboru fname2 na zapis */ 194: if ((fw = fopen(fname2, "wt")) == NULL) 195: { 196: fprintf(stderr, "Nemozem vytvorit subor '%s'!\n\n", fname2); 197: fclose(fr); 198: return; 199: } 200: 201: /* citanie a spracovavanie vyrazov */ 202: /* nacitanie riadku zo vstupneho suboru */ 203: while (fgets(line, LINE_LEN, fr) != NULL) 204: { 205: /* odstranenie koncoveho \n, ak sa nacitalo. 206: posledny riadok nemusi byt ukonceny \n */ 207: if (line[strlen(line) - 1] == '\n') 208: line[strlen(line) - 1] = '\0'; 209: 210: /* zapisanie povodneho vyrazu do suboru so zaciatocnym znakom > */ 211: if (fprintf(fw, "> %s\n", line) == EOF) 212: { 213: fprintf(stderr, "Nastala chyba pri zapise so suboru!\n\n"); 214: return; 215: } 216: } 217: 218: /* uzavretie suborov */ 219: fclose(fr); 220: 221: if (fclose(fw)) 222: { 223: fprintf(stderr, "Nemozem zatvorit subor '%s'!\n", fname2); 224: fprintf(stderr, "Nemusia byt zapisane vsetky vysledky.\n\n"); 225: return; 226: } 227: } 228: 229: /* Procedura print_sym vypise slovny nazov symbolu sym. */ 230: void print_sym(SYM_OP sym) 231: { 232: if (sym > errsym && sym < number)

88

233: printf("%s", RW[sym]); 234: else 235: switch(sym) 236: { 237: case errsym : printf("\nChyba vo vyraze!\n"); break; 238: case number : printf("cislo %lG",cislo); break; 239: case endsym : printf("koniec vyrazu"); break; 240: default : printf("\nNeznamy symbol vo vyraze!\n"); 241: } 242: }

Vysvetlivky: FILE * – smerník na objekt typu FILE fopen – funkcia na otvorenie súboru stderr – štandardný prúd pre výpis chybových správ fprintf – funkcia pre formátovaný výstup do súboru fclose – funkcia pre uzavretie súboru fgets – funkcie pre riadkovo orientovaný vstup zo súboru

Práca so súbormi Z programátorského hľadiska je súbor postupnosť po sebe idúcich bajtov od začiatku do konca súboru.

Starosťou operačného systému je, aby nám tieto bajty dodal v správnom poradí. Z dôvodu čo najväčšieho obmedze-nia počtu vstupno-výstupných operácií (t.j. zvýšenia rýchlosti prístupu) sú I/O operácie bufferované (takéto súbory nazývame prúdy dát, angl. stream), tzn.: • Pre vstup:

Naraz sa prečíta celý blok údajov z disku do pamäti (bufferu). Jednotlivé položky sa potom čítajú z pamäti a nie priamo z disku. Napr. ak potrebujeme prečítať 2 znaky zo súboru, načíta sa do pamäti celý úsek súboru (napr. blok dát veľkosti 512 bajtov), v ktorom sú tieto dva znaky. Potom sa prečíta z tejto pamäte prvý znak a potom druhý znak – ale už bez prístupu na vonkajšie médium (disk, disketa, ...), čiže podstatne rýchlejšie.

• Pre výstup: Údaje sa nezapisujú priamo na disk, ale do bufferu (pamäti). Keď je buffer plný, celý jeho obsah sa

automaticky zapíše na disk do súboru, ako jeden blok údajov. Výhodou je opäť väčšia rýchlosť zápisu do pamäti než na disk.

ANSI štandard jazyka C možnosť nebufferovaných I/O operácií priamo nepripúšťa, ale je ich možné obísť pomocou štandardnej funkcie setvbuf(), ktorou sa dá nastaviť bufferovanie blokové, riadkové alebo žiadne.

Vstupy z klávesnice a výstupy na obrazovku, tzv. „interaktívne I/O“ sa považujú za špeciálny prípad súborových I/O.

V jazyku C rozlišujeme dva typy súborov: • Textové – súbor pozostáva z riadkov. Každý riadok obsahuje nula alebo viac znakov a končí jedným alebo

viacerými znakmi označujúcimi koniec riadku. Maximálna dĺžka riadku je 255 znakov. Dôležité je zapamä-tať si, že riadok nie je C-čkovský reťazec, t.j. nekončí znakom ’\0’. Pri používaní textových súborov dochá-dza ku konverzii medzi znakom ’\n’ používaným v jazyku C na označenie nového riadku a znakom (prípad-ne znakmi), ktorým označuje operačný systém koniec riadku v súboroch na disku. V systémoch DOS je to kombinácia znakov carriage-return a linefeed (CR-LF). Keď sa údaje zapisujú do súboru otvoreného v textovom režime, každý znak ’\n’ je konvertovaný na postupnosť znakov CR-LF; a keď sa údaje čítajú zo súboru, každá kombinácia CR-LF je konvertovaná na znak ’\n’.

• Binárne – všetky dáta, ktoré sa do súboru zapisujú resp. sa z neho čítajú, ostávajú nezmenené. Súbor nie je rozdeľovaný na riadky a znak ’\n’ sa interpretuje iba vo význame linefeed (LF – hexadecimálne 0x0A). Takisto znak “Ctrl-Z”, používaný v textových súboroch na označenie konca súboru, nemá v binárnych súboroch žiadny význam a pracuje sa s ním ako s každým iným bajtom.

Napriek tomu, že jazyk C rozlišuje dva typy súborov, je nutné poznamenať, že s oboma typmi súborov sa pracuje úplne rovnako.

89

Otvorenie súboru Proces vytvorenia prúdu dát (stream) zviazaného so súborom na disku sa nazýva otvorenie súboru. Po otvorení

súboru môžeme z neho čítať, zapisovať alebo oboje. Na otvorenie súboru používame knižničnú funkciu fopen(), ktorej prototyp sa nachádza v STDIO.H a vyzerá

nasledovne: FILE *fopen(const char *filename, const char *mode);

Funkcia vracia pointer na typ FILE, čo je štruktúra deklarovaná v STDIO.H. Členy štruktúry FILE sú používané programom pri rôznych súborových operáciách, ale z programového hľadiska je to pre nás nezaujímavé. Podstatné je z toho to, že pre každý súbor, ktorý chceme otvoriť, musíme definovať premennú typu smerník na typ FILE. Keď vykonáme volanie fopen(), funkcia vytvorí inštanciu štruktúry FILE a vráti pointer ukazujúci na ňu. Tento pointer používame vo všetkých ďalších operáciách so súborom.

Ak otvorenie súboru zlyhá, fopen() vráti NULL. Takéto zlyhanie môže byť spôsobené napríklad hardvérovou chybou alebo pokusom o otvorenie súboru na nenaformátovanej diskete.

Argument filename predstavuje meno súboru, ktorý chceme otvoriť. Argument môže obsahovať aj špecifikáciu cesty a môže to byť reťazcová konštanta alebo smerník na reťazec.

Argument mode špecifikuje režim, v ktorom súbor otvárame, tzn. či je súbor textový (t) alebo binárny (b), a či je určený na čítanie (r), na zápis (w, a) alebo oboje (r+, w+, a+). Bližšie informácie o režime otvárania súborov získate v dokumentácii k prekladaču alebo na prednáške.

V našom príklade sme definovali vo funkcii do_suboru() dva smerníky na typ FILE (riadok 182) a to fr, ktorý používame na čítanie zo súboru a fw, ktorý používame na zápis do súboru. V riadku 186 sme zavolali funkciu fopen(), ktorou otvárame súbor v textovom režime iba na čítanie, s názvom definovaným obsahom reťazca fname1. Funkcia vytvorí prúd dát, ktorý sme priradili premennej fr. Výsledok funkcie (okrúhle zátvorky okolo priraďovacie-ho príkazu sú potrebné, lebo operátor == má vyššiu prioritu než =) porovnávame na NULL, tzn. testujeme či sa podarilo súbor otvoriť (napr. súbor so zadaným menom nemusí existovať). Ak sa súbor nepodarilo otvoriť, vykonajú sa príkazy v riadkoch 187 až 191.

V riadku 194 zase otvárame súbor v textovom móde iba na zápis. Názov súboru je špecifikovaný reťazcom fname2 a pokiaľ súbor neexistuje, vytvorí sa nový. Pokiaľ súbor existuje, prepíše sa novým a to bez upozornenia! Ak otvorenie súboru zlyhá (napr. pokúšame sa prepísať súbor s atribútom read-only), vykonajú sa príkazy v riadkoch 195 až 199.

Uzavretie súboru Po ukončení práce so súborom – už nebudeme ďalej z neho čítať alebo doň zapisovať – je nutné o tom

informovať operačný systém. Táto akcia sa nazýva uzavretie súboru a vykonáva sa pomocou funkcie fclose(f), kde f je typu FILE *.

Aj keď sa súbory po ukončení programu vo väčšine operačných systémov automaticky uzavierajú, nie je dobré sa na to spoliehať už len preto, že počet súčasne otvorených súborov je obmedzený. Druhým dobrým dôvodom, prečo uzavrieť súbor ihneď po ukončení práce s ním, je zápis bufferu do súboru. Na začiatku lekcie sme spomínali, že vstupno-výstupné operácie sú v jazyku C bufferované. Príkaz fclose() je teda potrebný, pretože po ukončení programu nie je vždy zaistené, že je súbor automaticky celý zapísaný na disk a uzavretý (flush). V praxi to znamená, že obsah posledného bufferu už nemusí byť z pamäti zapísaný do súboru, čo sa prejaví tým, že do súboru niečo dokázateľne zapisujeme, ale v súbore to po skončení programu nie je. Tretím dôvodom na uzavretie súboru ihneď po ukončení práce s ním je, aby sa pri prípadnej ďalšej havárii programu nestalo, že v súbore budú chýbať niektoré údaje, ktoré sme do neho síce programom zapísali, ale zostali iba v bufferi.

V našom príklade sme v riadku 197 zavolali funkciu fclose() s argumentom prúdu (stream) prvého súboru (fr), pretože ak zlyhá otvorenie druhého súboru (fw), prvý súbor ostane otvorený. V riadku 219 sme po ukončení práce so súbormi uzavreli prvý súbor a v riadku 221 sme uzavreli druhý súbor. Funkcia fclose(), vracia v prípade úspešného uzavretia súboru a zapísania bufferu na disk 0, v prípade chyby konštantu EOF. Preto je na riadkoch 222 až 226 ošetrenie, ktoré sa vykoná v prípade neúspešného uzavretia súboru. Tip:

Ak potrebujeme zapísať údaje z buffera na disk bez toho, aby sme súbor uzavreli, môžeme využiť knižničnú funkciu fflush() alebo flushall().

90

Štandardný vstup a výstup ANSI štandard jazyka C definuje tri štandardne preddefinované prúdy (streams) údajov, taktiež nazývané súbory

štandardného vstupu/výstupu. Tieto prúdy údajov sa automaticky otvoria, keď spustíme program napísaný v jazyku C a zatvoria, keď program skončí svoje vykonávanie. Nasledujúca tabuľka 17-1 zobrazuje zoznam štandardných prúdov a zariadení, ktoré sú k nim pripojené.

Názov Prúd Zariadenie stdin Štandardný vstup Klávesnica stdout Štandardný výstup Obrazovka stderr Štandardný chybový výstup Obrazovka stdprn* Štandardná tlačiareň paralelný port (LPT1:) stdaux* Štandardné príslušenstvo sériový port (COM1:) * podporované iba pod DOS-om

Tab. 17-1 päť štandardných prúdov

Vždy, keď sme použili na zobrazenie textu na obrazovke funkciu printf() alebo puts(), v skutočnosti sme použili prúd stdout. Podobne, keď sme na načítanie vstupu z klávesnice použili funkciu gets() alebo scanf(), použili sme prúd stdin. Štandardné prúdy údajov sa otvoria automaticky pri spustení programu, ale ostatné prúdy, ako sú napr. tie, ktoré používame na manipuláciu s údajmi uloženými na disku, treba otvoriť explicitne (viď. odsek „Otvorenie súboru“ v predchádzajúcej časti tejto lekcie).

Základné operácie s otvoreným súborom Pomocou prúdu (stream) dát, ktorý je zviazaný so súborom na disku, teda s otvoreným súborom, môžete vyko-

návať zápis údajov do súboru, čítanie údajov zo súboru alebo kombináciu obidvoch. Zápis údajov do súboru môžete vykonať v zásade tromi spôsobmi:

• Môžete použiť formátovaný výstup na uloženie fomátovaných údajov do súboru. Formátovaný výstup by ste mali používať iba s textovými súbormi. Hlavné použitie formátovaného výstupu je vytvorenie súborov obsahujúcich textové a číselné údaje, ktoré sa budú čítať inými programami, ako sú napr. tabuľkový proce-sor alebo databázový program. Súbory na opätovné čítanie C-čkovskými programami vytvárame pomocou formátovaného výstupu veľmi zriedkavo, ak vôbec niekedy.

• Môžete použiť znakovo orientovaný výstup na uloženie znakov alebo riadkov do súboru. Hoci technicky je možné použiť znakovo orientovaný výstup s binárnymi súbormi, je to nespoľahlivý spôsob. Znakovo orientovaný výstup by ste mali obmedziť iba na textové súbory. Hlavné použitie znakovo orientovaného výstupu je uloženie textových (ale nie číselných) údajov v takej forme, ktorá môže byť čítaná ako progra-mami v jazyku C, tak aj ostatnými programami, ako sú napr. textové editory.

• Môžete použiť priamy výstup na uloženie obsahu časti pamäti priamo do súboru na disku. Táto metóda je určená iba pre binárne súbory. Priamy výstup je najlepší spôsob na ukladanie údajov, ktoré sa neskôr použi-jú programami v jazyku C.

Keď potrebujete čítať údaje zo súboru, máte na ponuku tri prislúchajúce spôsoby: formátovaný vstup, znakovo orientovaný vstup alebo priamy vstup. Spôsob, ktorý použijete na čítanie údajov, závisí celkom od povahy údajov, ktoré sú v súbore uložené. Vo všeobecnosti čítajte údaje v rovnakom režime, v akom ste ich do súboru zapisovali.

Formátovaný výstup do súboru Na zápis údajov do súboru pomocou formátovaného výstupu používame knižničnú funkciu fprintf(), ktorej

prototyp sa nachádza v hlavičkovom súbore STDIO.H a vyzerá nasledovne: int fprintf(FILE *stream, const char *format, ...);

Prvým argumentom je smerník na typ FILE. Na zápis údajov do súboru dodáme ako prvý argument smerník, ktorý sme obdržali po otvorení súboru – po volaní funkcie fopen(). Druhým argumentom je riadiaci reťazec formátu, pre ktorý platia rovnaké pravidlá ako pre riadiaci reťazec formátu funkcie printf() (viď. lekcia III). Zvyšné argumen-

91

ty funkcie predstavujú mená premenných resp. konštanty, ktorých hodnoty majú byť zapísané do súboru. Ich počet závisí od počtu znakov ’%’ v riadiacom reťazci formátu. Výstupom funkcie je počet úspešne zapísaných bajtov.

Funkcia fprintf() pracuje presne takisto ako funkcia printf(), iba s tým rozdielom, že svoj výstup posiela na prúd údajov špecifikovaný prvým argumentom. V skutočnosti, keď zadáte stdout ako argument stream, funkcia fprintf() je ekvivalentná s funkciou printf().

V našom príklade sme v riadku 211 použili funkciu fprintf() na formátovaný výstup do súboru, keď do súboru fw zapisujeme riadok načítaný zo súboru fr (ako uvidíme neskôr), pričom pred neho zapíšeme dvojicu znakov “> “. V ostatných riadkoch výskytu funkcie fprintf() zapisujeme informácie na štandardný chybový výstup – stderr, ktorý je rovnako ako štandardný výstup (stdout) pripojený k obrazovke, tzn. že chybové hlásenia sa zobrazujú na obrazov-ke. Dôvod, prečo sme chybové správy neposielali na stdout resp. prečo sme nepoužili funkciu printf() alebo puts() viď. v odseku „Presmerovanie štandardného vstupu a výstupu“.

Formátovaný vstup zo súboru Na načítanie údajov zo súboru pomocou formátovaného vstupu používame knižničnú funkciu fscanf(), ktorá sa

používa podobne ako scanf(), ibaže vstup prichádza zo špecifikovaného prúdu údajov namiesto z stdin. Prototyp funkcie sa nachádza v STDIO.H a vyzerá nasledovne:

int fscanf(FILE *stream, const char *format, ...); Argument stream je smerník na typ FILE vrátený funkciou fopen() a format je pointer na riadiaci reťazec

formátu, ktorý špecifikuje, ako fscanf() číta údaje zo vstupu. Štruktúra riadiaceho reťazca formátu je taká istá ako pre funkciu scanf() (viď. lekcia III). Zvyšné argumenty funkcie predstavujú adresy premenných, kde má fscanf() priradiť vstup. Návratová hodnota funkcie predstavuje počet úspešne načítaných hodnôt premenných.

Funkcia fscanf() pracuje presne takisto ako funkcia scanf(), iba s tým rozdielom, že údaje sú načítavané zo zada-ného prúdu a nie z stdin. Pokiaľ zadáme stdin ako argument stream, funkcia fscanf() je identická s funkciou scanf().

Znakovo orientovaný vstup zo súboru Na vstup jedného znaku zo súboru (prúdu) používame funkciu getc() alebo fgetc(). Na vstup celého riadku zo

súboru (prúdu) nám jazyk C poskytuje funkciu fgets(). Pripomíname, že riadok je postupnosť nula alebo viacerých znakov ukončených znakom „nový riadok“. Na načítanie riadku zo súboru teda používame knižničnú funkciu fgets(), ktorej prototyp sa nachádza v hlavičkovom súbore STDIO.H a vyzerá nasledovne:

char *fgets(char *s, int n, FILE *stream); Funkcia číta reťazec znakov zo súboru stream až do konca riadku, najviac však n - 1 znakov a včítane znaku

’\n’ (rozdiel oproti gets() !) ho uloží do reťazca s. Funkcia vracia smerník na s, alebo pri dosiahnutí konca súboru vracia NULL.

Argument s predstavuje smerník na reťazec, kam sa uloží načítaný vstup, n je maximálny počet znakov zo vstupu a stream je smerník na typ FILE, ktorý vrátila funkcia fopen(), keď bol súbor otvorený.

Volanie funkcie fgets() spôsobí, že sa čítajú znaky zo stream do pamäte, začínajúc miestom, na ktoré ukazuje s. Znaky sa čítajú dovtedy, kým sa nedosiahne koniec riadku alebo sa nenačíta n – 1 znakov. Nastavením n na počet bajtov rezervovaných pre reťazec s sa vyhýbame prepísaniu pamäte za miestom prideleným reťazcu s. (n – 1 dovoľuje funkcii fgets() pridať na koniec ukončujúci znak ’\0’, ktorý aj pridá na koniec reťazca.) Pri úspešnom načítaní fgets() vráti smerník na s. Pri chybe alebo dosiahnutí konca súboru (EOF) funkcia vracia NULL.

Ako ste si už všimli, fgets() nemusí načítať ako vstup celý riadok (čo je všetko až po nasledujúci znak nového riadku). Ak funkcia načíta n – 1 znakov pred dosiahnutím znaku „nový riadok“, fgets() zastaví čítanie. Nasledujúca operácia čítania zo súboru začína od miesta, kde funkcia zastavila čítanie. Na zaistenie, že fgets() načíta celý riadok do reťazca, teda že čítanie zastaví iba na znakoch „nový riadok“, musíte nastaviť argument n dostatočne veľký a definovať primerane dlhý reťazec s.

V našom programe v riadku 203 načítavame v cykle while riadok po riadku zo vstupného súboru fr, až kým nenarazíme na koniec súboru. Načítaný vstup sa ukladá do reťazca line, ktorý je definovaný v riadku 183 ako reťazec znakov dĺžky LINE_LEN (posledný znak je ukončovacia ’\0’). Maximálna dĺžka načítaného reťazca je teda LINE_LEN – 1 znakov. Ak sa do poľa znakov line načíta celý riadok (vrátane znaku ’\n’ označujúceho nový riadok), tak sa posledný znak reťazca nahradí znakom konca reťazca ’\0’ (riadky 207 a 208).

92

Tip: Pretože pomocou fgets() dokážeme určiť maximálnu dĺžku čítaného riadku, môžeme ju použiť aj na čítanie

z terminálu, kde táto možnosť neexistuje (viď. funkciu gets() v lekcii XII). V tomto prípade použijeme príkaz: fgets(s, max, stdin);

Znakovo orientovaný výstup do súboru Na výstup jedného znaku do súboru (prúdu) používame funkciu putc() alebo fputc(). Knižničná funkcia fputc()

zapíše jeden znak do špecifikovaného prúdu. Jej prototyp sa nachádza v hlavičkovom súbore STDIO.H a vyzerá nasledovne:

int fputc(int ch, FILE *stream); Argument ch je znak, ktorý sa má zapísať do súboru stream. Formálny parameter ch je síce typu int, ale pri

zápise sa použije iba nižší bajt. Argument stream predstavuje smerník asociovaný so súborom (smerník vrátený funkciou fopen() pri otváraní súboru). Funkcia fputc() vracia znak zapísaný do súboru v prípade úspechu, alebo EOF v prípade chyby. Symbolická konštanta EOF je definovaná tiež v súbore STDIO.H a jej hodnota je -1. Pretože žiadny reálny znak nemôže nadobúdať takúto hodnotu, EOF je použitý na indikáciu chyby.

Ak použijeme stdout ako skutočný parameter pre argument stream, funkcia fputc() je identická s funkciou putchar(), s ktorou sme sa oboznámili v lekcii V.

Na zápis reťazca do súboru (prúdu) slúži knižničná funkcia fputs(), ktorej prototyp sa nachádza v hlavičkovom súbore STDIO.H. Táto funkcia pracuje rovnako ako funkcia puts(), s ktorou sme sa oboznámili v lekcii XII. Rozdiel je iba v tom, že pri fputs() špecifikujeme prúd údajov, do ktorého zapisujeme dáta. Ďalší rozdiel oproti puts() je, že fputs() nepridáva na koniec reťazca znak nového riadku ’\n’. Prototyp funkcie je nasledovný:

int fputs(const char *s, FILE *stream); Argument s predstavuje smerník na reťazec ukončený znakom ’\0’, ktorý sa má zapísať do súboru stream.

Argument stream predstavuje smerník na typ FILE, ktorý vrátila funkcia fopen() pri otvorení súboru. Ako skutočnú hodnotu parametra možno predať aj niektorý zo štandardných výstupných prúdov (pozri tabuľku 17-1). Funkcia fputs() zapíše do súboru stream reťazec, na ktorý ukazuje smerník s (bez znaku ’\0’). Návratová hodnota funkcie je nezáporné číslo v prípade úspechu alebo EOF v prípade chyby.

Priamy vstup/výstup do/zo súboru Priamy vstup/výstup do/zo súboru používame najčastejšie vtedy, keď potrebujeme uložiť údaje, ktoré neskôr

opäť načítame programom jazyka C. Priamy I/O sa používa iba s binárnymi súbormi. Pomocou priameho výstupu zapíšeme blok údajov z pamäti do súboru. Priamy vstup zo súboru je opačný proces: Blok údajov sa načíta zo súboru na disku do pamäti.

Napr. volaním jednej funkcie priameho výstupu do súboru zabezpečíme zapísanie celého poľa reálnych čísel na disk, a volaním jednej funkcie priameho vstupu zo súboru zabezpečíme načítanie celého poľa z disku späť do pamäti. Funkciami priameho vstupu/výstupu zo/do súboru sú fread() a fwrite() s nasledovnými prototypmi (obidva sa nachá-dzajú v hlavičkovom súbore STDIO.H):

int fread(void *kam, int veľkosť, int počet, FILE *súbor); int fwrite(void *odkiaľ, int veľkosť, int počet, FILE *súbor);

kde jednotlivé formálne parametre majú tento význam: kam – adresa pamäti, kam sa bude ukladať prečítaný blok údajov odkiaľ – adresa pamäti, odkiaľ sa bude brať zapisovaný blok údajov veľkosť – dĺžka jednej položky z bloku údajov; pre zistenie veľkosti je vhodné použiť operátor sizeof() počet – počet údajových položiek (nie bajtov!), ktoré sa čítajú resp. zapisujú súbor – premenná pre prácu so súborom

Obidve funkcie vracajú počet úspešne zapísaných/prečítaných položiek ⇒ chybu testujeme pomocou != počet.

93

Presmerovanie štandardného vstupu a výstupu Ako už bolo skôr spomenuté, prúd pre štandardný vstup (stdin) predstavuje vstup z klávesnice a prúd pre štan-

dardný výstup (stdout) predstavuje výstup na obrazovku. Obidva tieto I/O prúdy je možné v mnohých operačných systémoch (UNIX, MS-DOS, …) jednoducho zmeniť pomocou presmerovania (angl. redirection), napr. na vstup zo súboru alebo výstup do súboru/na tlačiareň, bez zásahu do vlastného programu.

Napr. ak v DOSe spustíme náš program P17.EXE príkazom: A:\>P17

program sa spustí v interaktívnom režime (vstupy očakáva z klávesnice a výstupy vypisuje na obrazovku). Pokiaľ ale použijeme rovnaký program a spustíme ho príkazom:

A:\>P17 > vystup.txt nebude program P17.EXE vypisovať nič na obrazovku, ale celý svoj výstup zapíše do súboru vystup.txt,

ktorý sám vytvorí, otvorí a nakoniec uzavrie.

Podobne, ak spustíme program pomocou príkazu: A:\>P17 < vstup.txt

Nebude program očakávať vstup z klávesnice, ale vstup sa načíta zo súboru vstup.txt, ktorý sa pri spustení otvorí a po ukončení automaticky zavrie. V našom uvažovanom príklade musí súbor vstup.txt obsahovať na samostatnom riadku príkaz „/q“ alebo „/quit“, aby sa program korektne ukončil.

Teda pomocou symbolov operačného systému > a < môžeme presmerovať štandardný vstup alebo výstup bez zásahu do programu. Použitím týchto symbolov v skutočnosti meníme zariadenia, s ktorými sú zviazané prúdy stdin a stdout (viď. tabuľka 17-1).

Použitie štandardného chybového výstupu – stderr Jeden z preddefinovaných prúdov v jazyku C je stderr (standard error). Chybové správy programu sú tradične

posielané na stderr a nie na stdout. Prečo je tomu tak? Ako sme sa v predchádzajúcom naučili, výstup na stdout môže byť presmerovaný na iné zariadenie než je

obrazovka. Ak je stdout presmerovaný, používateľ nemusí byť upozornený na chybové správy zasielané na stdout. Na rozdiel od stdout štandardný chybový výstup stderr nemôže byť presmerovaný a je vždy pripojený ku obrazovke (aspoň v DOSe; systémy UNIX dovoľujú presmerovať aj stderr). Smerovaním chybových správ na stderr zabezpečíte, že používateľ ich vždy uvidí.

V našom príklade sme vo funkcii do_suboru() zasielali všetky chybové hlásenia pomocou funkcie fprintf() na štandardný chybový výstup, čím sme zaistili, že pri presmerovaní štandardného výstupu sa správy vypíšu na obra-zovku (viď. riadky 188, 189, 196, 213, 223 a 224).

Cvičenia 1. Ošetrite funkciu do_suboru() tak, že pokiaľ súbor fname2 existuje, neprepíše sa novým, ale upozorní na túto

skutočnosť používateľa a spýta sa, či si želá súbor prepísať. Ak áno, cieľový súbor sa prepíše, ináč funkcia skončí. Pomôcka: Testovanie na existenciu súboru vykonajte pomocou pokusu otvoriť súbor na čítanie.

Riešenie: program c17-1.cpp

2. Overte na príklade funkcie print_sym(), že fprintf(stdout, ...) je identická s funkciou printf(...).

Riešenie: program c17-2.cpp

3. Na programe P17.CPP overte pomocou zmeny konštanty LINE_LEN, že pokiaľ fgets() nenačíta celý riadok zo súboru (tzn. načíta max. LINE_LEN - 1 znakov), tak nové čítanie pokračuje od miesta, kde fgets() zastavil čítanie v predchádzajúcej obrátke cyklu while. Tzn., že zmeňte konštantu LINE_LEN napr. na 20, vytvorte súbor s názvom priklady.txt, do ktorého napíšte na samostatné riadky niekoľko matematických výrazov, pričom

94

aspoň jeden riadok by mal presahovať dĺžku LINE_LEN - 1 znakov, a spustite program z príkazového riadku: "P17.exe priklady.txt vystup.txt".

Riešenie: program c17-3.cpp

4. Napíšte, ako by ste obmedzili načítavanie riadku z klávesnice v procedúre interaktiv() na max. dĺžku (LINE_LEN - 1) znakov. Znak '\n' odstráňte z načítaného riadku.

Riešenie: súbor c17-4.cpp

5. Overte na príklade funkcie vypocitaj(), že fputc(znak, stdout) je ekvivalentné s putchar(znak).

Riešenie: program c17-5.cpp

6. Overte rozdiel medzi funkciami puts() a fputs() na príklade programu P17.CPP, a to tak, že zameníte volania puts(...) za fputs(..., stdout).

Riešenie: program c17-6.cpp

7. Overte funkciu štandardného chybového výstupu (stderr) spustením programu P17.EXE z príkazového riadku DOS-u nasledujúcim spôsobom:

A:\>p17 ~~tmp.txt nieco.tmp > vystup.txt Podobne skúste overiť nemožnosť presmerovania stderr tak, že nastavíte atribút READ-ONLY pre súbor P17.CPP a program P17.EXE spustíte z príkazového riadku DOS-u nasledovným spôsobom:

A:\>p17 p17.cpp p17.cpp > vystup.txt 8. a) Prepíšte funkciu print_sym() na funkciu s prototypom void print_sym(SYM_OP sym, FILE *fout);

ktorá všetky výstupy na obrazovku presmeruje do súboru/prúdu fout.

Riešenie: program c17-8a.cpp

b) Prepíšte funkciu vypocitaj() na funkciu s prototypom int vypocitaj(char string[], FILE *fw); ktorá všetky svoje výstupy na stdout presmeruje do súboru fw. Pri volaní funkcie print_sym() z predchádzajúceho cvičenia naplňte formálny parameter fout skutočným parametrom fw. Pozor na rozdiel medzi puts() a fputs()!

Riešenie: program c17-8b.cpp

9. Napíšte funkciu s prototypom void spracuj_subor(FILE *fr, FILE *fw); do ktorej presuňte cyklus while z funkcie do_suboru(), t.j. cyklus, v ktorom sa načítavajú riadky zo vstupného súboru fr a zapisujú sa do výstupného súboru fw. Za ošetrením príkazu, ktorým sa zapisuje pôvodný výraz do súboru fw, napíšte volanie funkcie vypocitaj() z predchádzajúceho cvičenia so skutočnými parametrami line (načítaný riadok) a fw (výstupný prúd). Funkciu spracuj_subor() volajte z funkcie do_suboru() so skutočnými parametrami otvorených súborov fr a fw z miesta, kde bol pôvodne umiestnený cyklus while.

Riešenie: program c17-9.cpp

10. Implementujte funkciu zo_suboru() tak, že otvorí súbor s názvom uloženým v argumente fname1, zavolá funkciu spracuj_subor() s argumentom smerníka na otvorený súbor a prúdom štandardného výstupu (stdout) a nakoniec uzavrie otvorený súbor. Funkcia zo_suboru() teda vykonáva čítanie matematických výrazov zo súboru s názvom uloženým v reťazci fname1[] a „vypočítané“ výrazy vypisuje na obrazovku, zatiaľ čo funkcia do_suboru() vykonáva čítanie matematických výrazov zo súboru s názvom fname1[] a „vypočítané“ výrazy zapisuje do súboru s názvom fname2[].

Riešenie: program c17-10.cpp

95

XVIII. lekcia: Zostavenie programu kalkulátor – I. časť V tejto lekcii sa oboznámite s knižničnou funkciou, ktorá slúži na okamžité ukončenie vykonávania programu.

Ďalej sa naučíte, čo je to syntaktický analyzátor a vyhodnocovač v prípade programu kalkulátor. Obidve uvedené časti programu sa využijú pri spracúvaní zadaných matematických výrazov. Doleuvedený výpis programu realizuje proces syntaktickej analýzy a kalkulátor už vie v tomto štádiu vyhodnocovať jednoduché matematické výrazy, ktoré obsahujú reálne čísla, operácie +, –, *, / a zátvorky na určenie priority.

Výstupom tejto lekcie bude program kalkulátor, ktorý dokáže počítať už aj matematické výrazy obsahujúce funkciu absolútnej hodnoty. Navyše bude kalkulátor ošetrovať zle zadané matematické výrazy, a to konkrétne také, ktoré sa končia operáciou +, –, * alebo /. Pretože uvedené matematické operácie sú binárne, výrazy, ktoré obsahujú tieto operácie, musia končiť operandom. Vašou úlohou v cvičeniach bude implementácia týchto súčastí kalkulátora.

Výpis programu P18.CPP – vychádza z programu C17-10.CPP; program je doplnený o syntaktický analyzátor s vyhodnocovaním výrazu, ktorý predstavujú funkcie: vyraz(), term() a faktor(); rozšírená funkcia vypocitaj()oproti verzii C17-10.CPP.

1: #include <stdio.h> 2: #include <conio.h> 3: #include <string.h> 4: #include <stdlib.h> 5: #include "funkcie.cpp" 6: 7: /* --- definicia symbolickych konstant --- */ 8: /* dlzka jedneho riadku vstupneho suboru resp. riadku zadaneho z klavesnice */ 9: #define LINE_LEN 255 10: /* pocet rezervovanych slov = mohutnost mnoziny RW */ 11: #define NORW 21 12: 13: /* --- definicia vymenovanych typov --- */ 14: typedef enum {errsym = -1, 15: plussym, minussym, mulsym, divsym, 16: lparen, rparen, 17: factsym, nsdsym, minsym, maxsym, 18: roundsym, ceilsym, floorsym, 19: sqrtsym, sqrsym, pwrsym, rootsym, 20: modsym, abssym, truncsym, 21: /* symbol oddelovaca ';' */ 22: semicolon, 23: /* a teraz dva specialne symboly: cislo a koniec retazca '\0' */ 24: number, 25: endsym 26: } SYM_OP; /* symboly funkcii a operatorov (+ cisla a oddelovaca) */ 27: 28: /* --- globalne premenne --- */ 29: char * RW[] = { /* rezervovane slova pre funkcie a operatory */ 30: "+", 31: "-", 32: "*", 33: "/", 34: "(", 35: ")", 36: "FACT", 37: "NSD", 38: "MIN", 39: "MAX", 40: "ROUND", 41: "CEIL", 42: "FLOOR", 43: "SQRT",

96

44: "SQR", 45: "PWR", 46: "ROOT", 47: "MOD", 48: "ABS", 49: "TRUNC", 50: ";" 51: }; 52: char * ptr = NULL; /* smernik na aktualny znak v nacitanom retazci */ 53: SYM_OP sym; /* prvy symbol - vrateny z nacitaneho retazca pomocou getSymbol() */ 54: double cislo; /* ak je sym cislo, tak premenna cislo obsahuje hodnotu cisla */ 55: FILE * matherr; /* subor/vystupny prud, kam sa zapisuju chybove spravy matematic- 56: * kych operacii v procedurach term() a faktor() */ 57: 58: /* --- prototypy funkcii - deklaracie funkcii --- */ 59: void interaktiv(void); 60: int vypocitaj(char string[], FILE *fw); 61: void getSymbol(); 62: void zo_suboru(char fname1[]); 63: void do_suboru(char fname1[], char fname2[]); 64: void spracuj_subor(FILE *fr, FILE *fw); 65: void pouzitie(); 66: void print_sym(SYM_OP sym, FILE *fout); 67: void vyraz(); 68: void term(); 69: void faktor(); 70: 71: /* --- hlavna funkcia --- */ 72: void main(int argc, char *argv[]) 73: { 74: switch (argc) 75: { 76: case 1: interaktiv(); /* interaktivny vypocet formul */ 77: break; 78: case 2: if (stricmp(argv[1], "-h") == 0) 79: pouzitie(); 80: else 81: zo_suboru(argv[1]); 82: break; 83: case 3: do_suboru(argv[1], argv[2]); 84: break; 85: default: pouzitie(); /* vypis navod na pouzitie */ 86: } 87: } 88: 89: /* --- definicie uzivatelskych funkcii --- */ 90: void interaktiv(void) 91: { 92: int i = 0; /* pocitadlo prikazov */ 93: char string[LINE_LEN]; /* retazec nacitany z klavesnice */ 94: 95: clrscr(); 96: 97: while(1) 98: { 99: printf("%d> ", ++i); 100: gets(string); 101: 102: if (strnicmp("/q", string, 2) == 0) 103: return; 104: else 105: /* samotne spracovanie nacitaneho vyrazu */ 106: vypocitaj(string, stdout);

97

107: } 108: } 109: 110: int vypocitaj(char string[], FILE *fw) 111: { 112: if (!skontroluj_zatvorky(string)) 113: { 114: fputs("Chyba: Nespravny pocet zatvoriek alebo ich poradie.\n", fw); 115: return(-1); 116: } 117: 118: /* Nastavenie globalneho smernika ptr na prvy znak retazca a vynulovanie * 119: * vysledku -> inicializacia premennych pred vypoctom. Funkcia vypocitaj() * 120: * sa vola v cykle while(1) ..., takze v kazdom prechode sluckou vykonaj: */ 121: ptr = string; 122: cislo = 0; 123: 124: #if debug /* vypis sluzi iba na testovacie ucely */ 125: /* Pomocny vypis nacitanych symbolov */ 126: while (getSymbol(), sym != endsym && sym != errsym) 127: { 128: print_sym(sym, fw); 129: fputc('\n', fw); 130: } 131: #else 132: /* nastavenie vystupneho prudu, kde sa zapisuju chybove spravy 133: matematickych operacii a funkcii. matherr je globalna premenna */ 134: matherr = fw; 135: 136: /* nacitanie prveho symbolu a vyhodnotenie vyrazu */ 137: getSymbol(); 138: vyraz(); 139: #endif 140: 141: if (sym != endsym) 142: { 143: fputs("Chyba vo vyraze alebo neznama funkcia!\n", fw); 144: return(-1); 145: } 146: 147: #ifndef debug 148: /* zapisanie vysledku do suboru / na standardny vystup */ 149: if (fprintf(fw, "%.15lG\n", cislo) == EOF && fw != stdout) 150: { 151: fprintf(stderr, "Nastala chyba pri zapise do suboru!\n\n"); 152: exit(1); 153: } 154: #endif 155: 156: return(0); 157: } 158: 159: /* Funkcia getSymbol() - lexikalny analyzator */ 160: void getSymbol() 161: { 162: int i; /* index do pola RW */ 163: int len; /* dlzka retazca z RW, o ktoru treba posunut smernik ptr */ 164: 165: while (*ptr == ' ' || *ptr == '\t') 166: ptr++; 167: 168: if (*ptr == '\0') 169: {

98

170: sym = endsym; 171: return; 172: } 173: 174: if (isdigit(*ptr) || *ptr == '.') /* je to cislo */ 175: { 176: cislo = str2d(ptr, &ptr); 177: sym = number; 178: return; 179: } 180: 181: for (i = 0; i < NORW; i++) 182: if (strnicmp(ptr, RW[i], len=strlen(RW[i])) == 0) 183: { 184: sym = (SYM_OP) i; 185: ptr += len; 186: return; 187: } 188: 189: /* ak presiel az sem, tak ide o chybny operator */ 190: sym = errsym; 191: } 192: 193: /* procedura vypise navod na pouzitie */ 194: void pouzitie() 195: { 196: puts("\nHelp k programu calc\n--------------------"); 197: puts("Spustenie programu: calc.exe [subor1] [subor2]"); 198: puts("bez parametrov - spusti kakulator v interaktivnom mode"); 199: puts("subor1 - vstupny subor, v ktorom su ulozene vyrazy"); 200: puts("subor2 - volitelne meno vystupneho suboru, v ktorom budu vypocitane vyrazy"); 201: puts(" ak nie je zadane, vysledky sa vypisu na obrazovku\n"); 202: } 203: 204: void zo_suboru(char fname1[]) 205: { 206: FILE *fr; /* subor na citanie */ 207: 208: /* otvorenie vstupneho suboru fname1 na citanie */ 209: if ((fr = fopen(fname1, "rt")) == NULL) 210: { 211: fprintf(stderr, "Nemozem otvorit subor '%s'!\n", fname1); 212: fprintf(stderr, "Skontrolujte meno suboru.\n\n"); 213: return; 214: } 215: 216: /* volanie funkcie na spracovanie suboru */ 217: spracuj_subor(fr, stdout); 218: 219: /* uzavretie suboru */ 220: fclose(fr); 221: } 222: 223: void do_suboru(char fname1[], char fname2[]) 224: { 225: FILE *fr, *fw; /* subor na citanie (fr) a na zapis (fw) */ 226: char odpoved = 'N'; /* odpoved na otazku, ci sa ma subor prepisat */ 227: 228: /* otvorenie suboru fname1 na citanie */ 229: if ((fr = fopen(fname1, "rt")) == NULL) 230: { 231: fprintf(stderr, "Nemozem otvorit subor '%s'!\n", fname1);

99

232: fprintf(stderr, "Skontrolujte meno suboru.\n\n"); 233: return; 234: } 235: 236: /* test na existenciu suboru fname2 */ 237: if ((fw = fopen(fname2, "r")) != NULL) 238: { 239: fprintf(stderr, "Subor s nazvom %s uz existuje!\n", fname2); 240: fprintf(stderr, "Mam ho prepisat (A/N)? "); 241: 242: odpoved = getche(); 243: 244: if (toupper(odpoved) != 'A') 245: return; 246: } 247: 248: /* otvorenie suboru fname2 na zapis */ 249: if ((fw = fopen(fname2, "wt")) == NULL) 250: { 251: fprintf(stderr, "Nemozem vytvorit subor '%s'!\n\n", fname2); 252: fclose(fr); 253: return; 254: } 255: 256: spracuj_subor(fr, fw); 257: 258: /* uzavretie suborov */ 259: fclose(fr); 260: 261: if (fclose(fw)) 262: { 263: fprintf(stderr, "Nemozem zatvorit subor '%s'!\n", fname2); 264: fprintf(stderr, "Nemusia byt zapisane vsetky vysledky.\n\n"); 265: return; 266: } 267: } 268: 269: /* Procedura print_sym vypise slovny nazov symbolu sym do prudu fout. */ 270: void print_sym(SYM_OP sym, FILE *fout) 271: { 272: if (sym > errsym && sym < number) 273: fprintf(fout, "%s", RW[sym]); 274: else 275: switch(sym) 276: { 277: case errsym : fprintf(fout,"\nChyba vo vyraze!\n"); break; 278: case number : fprintf(fout,"cislo %lG",cislo); break; 279: case endsym : fprintf(fout,"koniec vyrazu"); break; 280: default : fprintf(fout,"\nNeznamy symbol vo vyraze!\n"); 281: } 282: } 283: 284: /* procedura nacita matematicke vyrazy zo vstupneho suboru fr, 285: * vypocita ich a vysledky zapise na vystup fw (subor/stdout). 286: */ 287: void spracuj_subor(FILE *fr, FILE *fw) 288: { 289: char line[LINE_LEN]; /* jeden riadok vstupneho suboru */ 290: 291: /* citanie a spracovavanie vyrazov */ 292: /* nacitanie riadku zo vstupneho suboru */ 293: while (fgets(line, LINE_LEN, fr) != NULL) 294: {

100

295: /* odstranenie koncoveho \n, ak sa nacitalo. 296: posledny riadok nemusi byt ukonceny \n */ 297: if (line[strlen(line) - 1] == '\n') 298: line[strlen(line) - 1] = '\0'; 299: 300: /* zapisanie povodneho vyrazu do suboru so zaciatocnym znakom > */ 301: if (fprintf(fw, "> %s\n", line) == EOF) 302: { 303: fprintf(stderr, "Nastala chyba pri zapise so suboru!\n\n"); 304: return; 305: } 306: 307: /* samotne spracovanie (vypocet) nacitaneho vyrazu */ 308: vypocitaj(line, fw); 309: } 310: } 311: 312: /* syntakticky analyzator + vyhodnotenie vyrazu (vypocet) */ 313: void vyraz() 314: { 315: double cislo1; /* hodnota 1. operandu (scitanec alebo mensenec) */ 316: SYM_OP op; /* typ operacie (+|-) */ 317: 318: term(); /* vyhodnotenie symbolu ako term */ 319: 320: while (sym == plussym || sym == minussym) 321: { 322: op = sym; /* uschovanie typu operacie */ 323: cislo1 = cislo; /* uschovanie hodnoty cisla */ 324: 325: getSymbol(); /* nacitanie dalsieho symbolu */ 326: term(); /* a jeho vyhodnotenie ako term */ 327: 328: /* podla typu operacie daj do cislo vysledok scitania/odcitania * 329: * cislo teraz obsahuje 2. operand (scitanec alebo mensitel) */ 330: cislo = (op == plussym ? cislo1 + cislo : cislo1 - cislo); 331: } 332: } 333: 334: void term() 335: { 336: double cislo1; /* hodnota 1. operandu (delenec alebo cinitel) */ 337: SYM_OP op; /* typ operacie (*|/) */ 338: 339: faktor(); /* vyhodnotenie symbolu ako faktor */ 340: 341: while (sym == mulsym || sym == divsym) 342: { 343: op = sym; /* uschovanie typu operacie */ 344: cislo1 = cislo; /* uschovanie hodnoty 1. operandu */ 345: 346: getSymbol(); /* nacitanie dalsieho symbolu */ 347: faktor(); /* a jeho vyhodnotenie ako faktor */ 348: 349: if (op == mulsym) /* podla typu operacie vykonaj nasobenie|delenie */ 350: cislo *= cislo1; 351: else 352: /* cislo teraz obsahuje delitel, nasleduje osetrenie */ 353: if (cislo == 0) 354: { 355: fputs("Delenie nulou!\n", matherr); 356: return; 357: }

101

358: else 359: cislo = cislo1 / cislo; 360: } 361: } 362: 363: void faktor() 364: { 365: /* faktor nemoze zacinat symbolmi: +, -, *, / */ 366: if (sym == plussym || sym == minussym || sym == mulsym || sym == divsym) 367: { 368: fputs("Chyba! Neocakavany operator '", matherr); 369: print_sym(sym, matherr); 370: fputs("'...\n", matherr); 371: return; 372: } 373: 374: /* vyhodnotenie faktoru */ 375: if (sym == number) /* ak je symbol cislo, premenna cislo obsahuje hodnotu */ 376: getSymbol(); /* nacitanie dalsieho symbolu */ 377: else 378: if (sym == lparen) /* ak je symbol '(' */ 379: { 380: getSymbol(); /* nacitaj dalsi symbol */ 381: vyraz(); /* vyhodnot ho ako vyraz */ 382: if (sym == rparen) /* ocakava sa ukoncujuca ')' */ 383: getSymbol(); 384: else 385: fputs("Ocakavam pravu zatvorku...\n", matherr); 386: } 387: else 388: if (sym == rparen) /* ')' => boli zatvorky bez vyrazu */ 389: fputs("Chyba! Prazdny vyraz ()...\n", matherr); 390: }

Vysvetlivky: exit() – funkcia na okamžité ukončenie vykonávania programu

Funkcia exit Funkcia exit() má podobný význam ako príkaz return, s ktorým sme sa oboznámili v lekcii II. Rozdiel je v tom,

že ak je funkcia exit() volaná z ktorejkoľvek funkcie, ukončí program okamžite, bez návratu do volajúcej funkcie (return ukončí iba vykonávanie funkcie, a ak je ňou main(), tak ukončí zároveň program).

Pred ukončením programu exit() vykoná nasledovné činnosti: • uzavrie všetky otvorené súbory • zapíše všetky údaje nachádzajúce sa v bufferoch – tzv. flush (viď. predchádzajúcu lekciu) • zavolá funkcie registrované ako “exit functions” (registrované pomocou funkcie atexit()) Prototyp funkcie sa nachádza v hlavičkovom súbor STDLIB.H. Funkcia má jeden vstupný parameter, ktorým je

celé číslo typu int, ktoré sa vráti volajúcemu procesu (najčastejšie operačný systém) ako stav ukončenia programu. Najčastejšie 0 znamená normálne ukončenie programu a nenulová hodnota indikuje chybu.

V našom programe sme vo funkcii vypocitaj() použili funkciu exit() (riadok 152), ktorá ukončí vykonávanie celého programu v prípade neúspešného zápisu do súboru – zlyhanie funkcie fprintf() v riadku 149. Nemohli sme použiť jednoducho príkaz return, lebo ten by ukončil iba túto funkciu.

102

Syntaktický analyzátor s vyhodnocovaním výrazov Funkcie vyraz(), term() a faktor() predstavujú syntaktický analyzátor, ktorý zároveň vyhodnocuje jedno-

duché matematické výrazy obsahujúce reálne čísla, a z funkcií iba sčítanie, odčítanie, násobenie, delenie a zátvorky, ktoré určujú prioritu vyhodnocovania.

Výpočet matematických výrazov začína na riadku 138 volaním funkcie vyraz(). Funkcia vypocitaj() bola oproti predchádzajúcej verzii (program C17-10.CPP) modifikovaná nasledovne:

• pridaný príkaz cislo = 0; (riadok 122), ktorým sa anuluje globálna premenná cislo medzi jednotlivými volaniami funkcie vypocitaj() – funkcia sa volá v nekonečnom cykle while(1) funkcie interaktiv() a spracuj_subor(). Premenná uchováva výsledok výpočtu celého výrazu ako aj jeho častí (viď. ďalej).

• časť programu, ktorá vypisuje načítané symboly (riadky 125 až 130), bola „zakomentovaná“ pomocou makier slúžiacich na podmienený preklad (viď. lekcia XI). Na vykonanie týchto príkazov stačí zadefinovať symbolickú konštantu debug pomocou makra #define na začiatku súboru. Na hodnote tejto konštanty nezáleží.

• pridaná nová globálna premenná matherr typu smerník na FILE (riadok 55). Táto premenná sa využíva v procedúrach term() a faktor() na výpis chýb matematických operácií a funkcií (riadky 355, 368 až 370, 385 a 389). Pred použitím je táto premenná inicializovaná (riadok 134) na príslušný otvorený súbor resp. štandardný výstup v závislosti od toho, odkiaľ je funkcia vypocitaj() volaná (riadok 308 alebo 106).

• pridaný príkaz na načítanie prvého symbolu (riadok 137) zo vstupného reťazca, ktorý obsahuje matematický výraz, identifikovaného globálnou premennou ptr pomocou funkcie getSymbol(), a štart vyhodnocova-nia výrazu pomocou funkcie vyraz() (riadok 138).

• vypísanie výsledku celého vypočítaného výrazu na štandardný výstup alebo do súboru, v prípade ktorého nasleduje ošetrenie na úspešný zápis (riadky 149 až 153). Po dokončení vykonávania funkcie vyraz() premenná cislo obsahuje výsledok celého výrazu v prípade bezchybného vyhodnotenia výrazu.

Ako už bolo spomenuté, funkciu syntaktického analyzátora a zároveň vyhodnocovača matematických výrazov (t.j. samotný kalkulátor) zabezpečujú funkcie vyraz(), term() a faktor(), z ktorých prvá začína vyhodnocova-nie. Funkcia vyraz() je rekurzívna a využíva nepriamu rekurziu – je volaná z funkcie faktor() (riadok 381). Volanie jednotlivých funkcií je schematicky znázornené nasledovným obrázkom:

Obr. 18-1 Volanie funkcií syntaktického analyzátor a vyhodnocovača matematických výrazov. Funkcia faktor() rekurzívne volá funkciu vyraz().

Funkcia vyraz() je rekurzívne volaná z funkcie faktor() vždy po symbole ľavej okrúhlej zátvorky “(“, čím sa dosiahne vyhodnotenie obsahu medzi symbolmi “(“ a “)“ ako výrazu (zátvorky majú najvyššiu prioritu zo všetkých matematických operácií a preto výraz v nich uvedený sa rekurzívne vyhodnocuje!).

Úlohou syntaktického analyzátora je posúdiť syntaktickú správnosť zadaného matematického výrazu, tzn. určiť či sú správne zapísané matematické funkcie, napr.: za reťazcom “abs“ musí nasledovať ľavá okrúhla zátvorka “(“, za ňou ľubovoľný výraz a potom pravá okrúhla zátvorka “)“. Na reprezentáciu matematických funkcií, operátorov, oddeľovačov a čísiel sme si zvolili symboly vymenovaného typu SYM_OP. Reťazce jednotlivých matematických funkcií a operátorov interpretuje funkcia getSymbol() pomocou symbolických konštánt tohto typu a ukladá ich do globálnej premennej sym (viď. analýzu a implementáciu funkcie v lekcii XVI). V tomto kroku tvorby programu

faktor()

vyraz() term()

103

kalkulátor nie je ešte jasne vidieť úplnú funkciu syntaktického analyzátora, snáď až na časť (riadky 378 až 386) tela funkcie faktor() (viď. ďalej).

Úlohou vyhodnocovača je vyhodnotiť matematické výrazy reprezentované pomocou symbolov, tzn. vykonať vlastný výpočet výrazu (spracovanie sémantiky). Program kalkulátor v tomto štádiu dokáže počítať iba výrazy, ktoré obsahujú reálne čísla, operátory +, –, *, / a zátvorky na určenie priority. Unárny operátor (+ alebo –) nie je povolený ani pred výrazom ani pred číslom, hoci funkcia str2d() súboru FUNKCIE.CPP rozpoznáva aj čísla so znamienkom predchádzajúcim hodnote.

Analýza funkcie vyraz() (riadky 313 až 332)

Predtým, než začneme vysvetľovanie jednotlivých funkcií syntaktického analyzátora a vyhodnocovača zároveň, zadefinujeme si pojmy výraz, term a faktor používané v našom programe kalkulátor.

Výraz je postupnosť termov, medzi ktorými sú iba matematické operácie sčítania (+) resp. odčítania (–). Výraz môže predstavovať aj samostatný term. Schematicky znázornený výraz je napr.:

term1 + term2 – term3 – ... + ... – ... + termN-1 + termN Term je postupnosť faktorov, medzi ktorými sú iba matematické operácie násobenia (*) resp. delenia (/). Term

môže predstavovať aj samostatný faktor. Schematicky znázornený term je napr.: faktor1 / faktor2 * faktor3 * ... / ... * ... * faktorM-1 / faktorM Faktor predstavujú: 1. číslo 2. všetky matematické funkcie z množiny/poľa RW okrem prvých 6 prvkov a symbolu oddeľovača “;“ 3. ( výraz )

Takže napríklad výraz “2.3 + 6*abs(-5) – 5/2*NSD(15;-25) + (9 – 4)“ obsahuje: – termy: 2.3, 6*abs(-5), 5/2*NSD(15;-25), (9 – 4), 9, 4 – faktory: 2.3, 6, abs(-5), 5, 2, NSD(15;-25), (9 – 4), 9, 4

Po zavolaní funkcie vyraz() z riadku 138 máme v globálnej premennej sym načítaný prvý symbol zo zada-ného matematického výrazu. Po vstupe do funkcie vyraz() najskôr vyhodnotíme tento symbol ako term (riadok 318), čím zabezpečíme, že operátory násobenia a delenia majú vyššiu prioritu ako operátory sčítania a odčítania. Po vyhodnotení termu máme v globálnej premennej sym ďalší symbol zo vstupného reťazca predstavujúceho matematický výraz.

V riadkoch 320 až 331 máme cyklus, ktorý plní funkciu vyhodnocovača výrazu (viď. definíciu hore), tzn. že pokiaľ máme vo vstupnom reťazci symboly matematických operácii + alebo – (riadok 320), nasledujúce riadky (telo cyklu) vyhodnocujú termy (riadok 326) a vykonávajú vlastné sčítanie/odčítanie (riadok 330). Aby sme nemuseli písať podmienku if, ktorou by sme písali zvlášť kód pre sčítanie a zvlášť pre odčítanie, zadefinovali sme si premennú op vymenovaného typu SYM_OP (riadok 316), do ktorej sme si uchovali (riadok 322) typ operácie (sčítanie resp. odčítanie). V globálnej premennej cislo máme zatiaľ hodnotu termu (z riadku 318), ktorú použijeme ako prvý operand pre danú operáciu, t.j. sčítanec alebo menšenec (riadok 323). Následne sme do sym načítali ďalší symbol (riadok 325) a vyhodnotili ho ako term (riadok 326) → výsledok je opäť v premennej cislo a sym obsahuje ďalší symbol. Nakoniec v riadku 330 vykonáme operáciu sčítania resp. odčítania v závislosti od uchovaného typu operácie (lokálna premenná op). Tento proces sa opakuje, kým máme v zadanom výraze symbol + alebo – (podmienka v riad-ku 320).

Ak sme na vstupe (z klávesnice alebo v súbore) zadali korektný matematický výraz, po ukončení cyklu while by mal sym obsahovať symbol konca reťazca endsym. Program opúšťa cyklus takisto pri ľubovoľnom inom symbole (v prípade nerozpoznanej operácie sym obsahuje errsym – viď. getSymbol()). Hodnota premennej sym sa po opustení funkcie vyraz() testuje vo funkcii vypocitaj() na symbol konca reťazca (riadok 141). Ak sym neucho-váva hodnotu endsym, ide buď o nesprávne zapísaný matematický výraz (napr. “23.8 (4 + 1)“) alebo faktor() nevyhodnotil funkciu (napr. teraz kalkulátor vie počítať iba s operátormi +, –, *, / a ( ), no lexikálny analyzátor vie rozpoznať všetky matematické funkcie uvedené v množine/poli RW – riadky 29 až 51) prípadne bola na vstupe zadaná mat. funkcia, ktorú kalkulátor nepodporuje.

104

Analýza funkcie term() (riadky 334 až 361)

Po vstupe do funkcie term() najskôr vyhodnotíme symbol sym ako faktor (riadok 339) a výsledok uložíme do lokálnej premennej cislo1 (riadok 344). Kým sa v sym nachádza symbol * alebo / (riadok 341), vykonávame v tele cyklu while (riadky 342 až 360) vyhodnocovanie faktorov (riadok 347) a v závislosti od uchovanej operácie (riadok 343) vykonávame násobenie resp. delenie (riadok 350 alebo 359). Pred operáciou delenia je ešte vykonaný test na nulovú hodnotu deliteľa (riadok 353). Program opúšťa funkciu pri ľubovoľnom inom symbole ako je mulsym (operátor *) alebo divsym (operátor /).

Analýza funkcie faktor() (riadky 363 až 390)

Po vstupe do funkcie faktor() najskôr vykonávame test, či sym neobsahuje jeden z nasledovných symbolov matematických operácií: +, –, * alebo / (riadok 366). Tento test sa vykonáva kvôli tomu, že ak na vstupe zadáme výraz “1+ +4“, obidva znaky ’+’ sa vyhodnotia ako operátory sčítania a medzi nimi musí byť (podľa definície výrazu) aspoň jeden term.

Po prevedení testu nasleduje vyhodnotenie faktoru (riadky 375 až 389). Ak sym obsahuje symbol čísla (riadok 375), globálna premenná cislo už obsahuje jeho hodnotu (viď. analýzu funkcie getSymbol() v lekcii XVI), načíta sa ďalší symbol zo vstupného reťazca. Toto bol prvý bod definície faktora (viď. hore).

Ak sym obsahuje symbol ľavej okrúhlej zátvorky (riadok 378), vykoná sa syntaktická analýza spolu s vyhodno-tením výrazu v zátvorkách ’(’ a ’)’ (riadky 379 až 386). Po zistení symbolu ľavej zátvorky sa načíta ďalší symbol zo vstupného reťazca (riadok 380). Počnúc týmto symbolom sa začína vyhodnocovanie výrazu (riadok 381). Riadok 382 predstavuje čisto syntaktický analyzátor, ktorý testuje, či za výrazom nasleduje ukončujúca pravá okrúhla zát-vorka ’)’ (napr. pre zadaný vstup “1 + (2 4)“ sa pred číslom 4 očakáva koniec zátvorky). Ak je vstupný reťazec syntakticky správny, tzn. že sym obsahuje očakávaný symbol pravej zátvorky, vykoná sa načítanie ďalšieho symbolu (riadok 383), ináč sa zapíše do prúdu matherr chybová správa. Toto bol tretí bod definície faktora (viď. hore).

Riadok 388 predstavuje opäť syntaktický analyzátor, ktorý testuje, či za symbolom ľavej zátvorky bezprostredne nasleduje symbol pravej zátvorky. Na pochopenie tohto testu je veľmi výhodné použiť ladiaci prostriedok (debugger) prekladača jazyka C, ktorým „odkrokujeme“ jednotlivé funkcie syntaktického analyzátora pri spracovávaní výrazu napr. “1 + (2) * ()“. Pri spracovávaní posledných dvoch zátvoriek sa po symbole násobenia začne vyhodnocovať ďalší symbol (znak ľavej zátvorky, t.j. symbol lparen) ako faktor (riadok 347). Vo funkcii faktor() sa splní podmienka na riadku 378 a načíta sa ďalší symbol (riadok 380) zo vstupného reťazca, ktorým je rparen (znak pra-vej zátvorky nachádzajúci sa bezprostredne za znakom ‘(’). Na riadku 381 sa začne vyhodnocovanie očakávaného výrazu, ktorý tu však chýba (sym obsahuje teraz symbol rparen). Rekurzívnym volaním funkcie vyraz() vykoná-vanie programu pokračuje volaním funkcie term() (riadok 318) a následne volaním funkcie faktor() (riadok 339). Po vstupe do funkcie faktor() sa vykoná test, či rparen nepredstavuje náhodou operátor +, –, *, alebo /. Potom začína samotné vyhodnocovanie faktoru a zhoda nastane až v riadku 388, kde sa zistí chyba vo výraze, teda že medzi zátvorkami ‘(’ a ‘)’ chýba výraz.

Zhrnutie: Táto lekcia vás oboznámila s veľmi dôležitou časťou programu kalkulátor, a to so syntaktickým analyzátorom

s vyhodnocovačom matematických výrazov. Túto súčasť kalkulátora využijeme ďalej v nasledujúcej lekcii, v ktorej iba rozšírime program kalkulátor o syntaktickú analýzu a vyhodnocovanie zvyšných matematických funkcií, ktoré sú uvedené v poli RW. V tejto lekcii ste sa taktiež zoznámili s funkciou exit(), ktorú v programoch využívame na okam-žité ukončenie vykonávania programu.

105

Cvičenia 1. Vyskúšajte funkciu programu P18.CPP pomocou debuggera s „krokovaním“ do jednotlivých funkcií – hlavne

vyraz(), term() a faktor() – na nasledujúcich výrazoch: "1 + 1/2" "4 - (8 * 3)" "1++7" "1(4)" "1+2+3+" "1 + (2) + ()" "1 - abs(-3)"

2. V súčasnosti nie je zabezpečené, že za symbolom + prípadne - musí nasledovať term/faktor (viď. funkciu vyraz() programu P18.CPP). Ak napr. zadáte na vstupe "1+2+3+", kalkulátor vypočíta hodnotu výrazu ako 12 (=1+2+3+6) – posledné číslo je súčtom predchádzajúcich troch. Ošetrite funkciu vyraz() tak, že výraz nemôže končiť symbolom + alebo - (ináč sa vypíše chyba a funkcia skončí).

Pomôcka: V cykle while po načítaní ďalšieho symbolu treba vykonať test na koniec reťazca (symbol endsym).

Riešenie: program c18-2.cpp

3. Podobne ako v cvičení 2 ošetrite funkciu term() tak, že výraz nemôže končiť symbolom * alebo /.

Riešenie: program c18-3.cpp

4. a) Definujte funkciu s prototypom void err_lparen(SYM_OP sym, FILE *fout); ktorá pre vstupný parameter sym vypíše do súboru/prúdu fout chybovú správu: "Ocakavam lavu zatvorku '(' po volani funkcie SYM...\n", kde SYM je slovný opis symbolu sym získaný pomocou funkcie print_sym().

Riešenie: súbor c18-4a.cpp

b) Podobne definujte funkciu s prototypom void err_rparen(SYM_OP sym, FILE *fout); ktorá pre vstupný parameter sym vypíše do súboru/prúdu fout chybovú správu: "Ocakavam pravu zatvorku ')' vo volani funkcie SYM(...\n", kde SYM je slovný opis symbolu sym získaný pomocou funkcie print_sym().

Riešenie: súbor c18-4b.cpp

5. Rozšírte funkciu faktor() o syntaktickú analýzu a výpočet funkcie ABS (absolútna hodnota). Funkcia ABS má mať nasledujúci všeobecný tvar: abs(výraz) tzn. prvý je symbol pre funkciu absolútnej hodnoty, za ním nasleduje ľavá zátvorka, potom ľubovoľný výraz a symbol pravej zátvorky.

Výsledok vyhodnotenia funkcie abs() uložte do globálnej premennej cislo. V prípade chýbajúceho symbolu ľavej resp. pravej okrúhlej zátvorky použite príslušnú funkciu z predchádzajúceho cvičenia (4a alebo 4b).

Riešenie: program c18-5.cpp

Poznámka: Keďže ešte nemáte definovaný prostriedok na interpretáciu unárneho + alebo - pred výrazom alebo číslom, implementovanú funkciu absolútnej hodnoty nemôžete vyskúšať priamo napr. na výraze "abs(–3)", ale iba napr. na výraze "abs(1 – 4)" prípadne "abs(0 – 3)".

106

XIX. lekcia: Zostavenie programu kalkulátor – II. časť V tejto lekcii sa oboznámite do detailov so syntaktickým analyzátorom implementovaným v programe kalku-

látor a po jeho pochopení by ste mali zvládnuť samostatne dopísať program tak, aby „vedel“ počítať všetky funkcie definované v poli RW. Výpis programu P19.CPP predstavuje kalkulátor, ktorý dokáže počítať výrazy obsahujúce reálne čísla, binárne operácie +, –, *, /, unárne operátory + a –, matematickú funkciu ABS, štatistické funkcie MAX a MIN a zátvorky na určenie priority.

Výstupom tejto lekcie bude cieľový program kalkulátor, na vývoji ktorého ste sa podieľali od prvej lekcie. Celý kurz, t.j. všetky cvičenia boli venované výstavbe kalkulátora a v jednotlivých lekciách ste sa zoznámili so všetkými dôležitými príkazmi, funkciami a konštrukciami jazyka C. V prílohe sú uvedené témy, ktoré sme v tomto kurze ne-prebrali a pre znalosť jazyka C na veľmi dobrej úrovni je potrebné si tieto témy doštudovať.

Výpis programu P19.CPP – vychádza z programu C18-5.CPP; program je doplnený o interpretáciu unárnych operátorov + a – pred výrazom; funkcia faktor() je rozšírená o syntaktickú analýzu a vyhodnotenie funkcií MIN a MAX.

317: /* syntakticky analyzator + vyhodnotenie vyrazu (vypocet) */ 318: void vyraz() 319: { 320: double cislo1; /* hodnota 1. operandu (scitanec alebo mensenec) */ 321: SYM_OP op; /* typ operacie (+|-) */ 322: short unary_op; /* unarne +|- pred vyrazom */ 323: 324: if (sym == plussym || sym == minussym) /* unarne +|- */ 325: { 326: unary_op = (sym == plussym ? +1 : -1); /* zistenie unarneho operatora */ 327: 328: getSymbol(); /* nacitanie dalsieho symbolu */ 329: term(); /* a jeho vyhodnotenie ako term */ 330: 331: cislo *= unary_op; /* prenasobenie vysledku unarnym +|- */ 332: } 333: else 334: term(); /* vyhodnotenie symbolu ako term */ 335: 336: while (sym == plussym || sym == minussym) 337: { 338: op = sym; /* uschovanie typu operacie */ 339: cislo1 = cislo; /* uschovanie hodnoty cisla */ 340: 341: getSymbol(); /* nacitanie dalsieho symbolu */ 342: if (sym == endsym) 343: { 344: fputs("Chyba! Neocakavany koniec vyrazu...\n", matherr); 345: return; 346: } 347: term(); /* a jeho vyhodnotenie ako term */ 348: 349: /* podla typu operacie daj do cislo vysledok scitania/odcitania * 350: * cislo teraz obsahuje 2. operand (scitanec alebo mensitel) */ 351: cislo = (op == plussym ? cislo1 + cislo : cislo1 - cislo); 352: } 353: } 354: 355: void term() 356: { 357: double cislo1; /* hodnota 1. operandu (delenec alebo cinitel) */ 358: SYM_OP op; /* typ operacie (*|/) */ 359:

107

360: faktor(); /* vyhodnotenie symbolu ako faktor */ 361: 362: while (sym == mulsym || sym == divsym) 363: { 364: op = sym; /* uschovanie typu operacie */ 365: cislo1 = cislo; /* uschovanie hodnoty 1. operandu */ 366: 367: getSymbol(); /* nacitanie dalsieho symbolu */ 368: if (sym == endsym) 369: { 370: fputs("Chyba! Neocakavany koniec vyrazu...\n", matherr); 371: return; 372: } 373: faktor(); /* a jeho vyhodnotenie ako faktor */ 374: 375: if (op == mulsym) /* podla typu operacie vykonaj nasobenie|delenie */ 376: cislo *= cislo1; 377: else 378: /* cislo teraz obsahuje delitel, nasleduje osetrenie */ 379: if (cislo == 0) 380: { 381: fputs("Delenie nulou!\n", matherr); 382: return; 383: } 384: else 385: cislo = cislo1 / cislo; 386: } 387: } 388: 389: void faktor() 390: { 391: /* faktor nemoze zacinat symbolmi: +, -, *, / */ 392: if (sym == plussym || sym == minussym || sym == mulsym || sym == divsym) 393: { 394: fputs("Chyba! Neocakavany operator '", matherr); 395: print_sym(sym, matherr); 396: fputs("'...\n", matherr); 397: return; 398: } 399: 400: /* vyhodnotenie faktoru */ 401: if (sym == number) /* ak je symbol cislo, premenna cislo obsahuje hodnotu */ 402: getSymbol(); /* nacitanie dalsieho symbolu */ 403: else 404: if (sym == lparen) /* ak je symbol '(' */ 405: { 406: getSymbol(); /* nacitaj dalsi symbol */ 407: vyraz(); /* vyhodnot ho ako vyraz */ 408: if (sym == rparen) /* ocakava sa ukoncujuca ')' */ 409: getSymbol(); 410: else 411: fputs("Ocakavam pravu zatvorku...\n", matherr); 412: } 413: else 414: if (sym == rparen) /* ')' => boli zatvorky bez vyrazu */ 415: fputs("Chyba! Prazdny vyraz ()...\n", matherr); 416: else 417: if (sym == abssym) /* absolutna hodnota */ 418: { 419: getSymbol(); /* nacitanie dalsieho symbolu */ 420: if (sym == lparen) /* a jeho test na '(' */ 421: { 422: getSymbol(); /* nacitanie dalsieho symbolu */

108

423: vyraz(); /* a jeho vyhodnotenie ako vyrazu */ 424: 425: cislo = abs(cislo); /* vlastny vypocet abs. hodnoty */ 426: 427: if (sym == rparen) /* za vyrazom musi nasledovat ')' */ 428: getSymbol(); 429: else 430: err_rparen(abssym, matherr); 431: } 432: else /* za funkciou ABS nenasleduje '(' */ 433: err_lparen(abssym, matherr); 434: } 435: else 436: if (sym == maxsym || sym == minsym) 437: { 438: SYM_OP op = sym; /* uchovanie symbolu operacie max|min */ 439: 440: getSymbol(); /* nacitanie dalsieho symbolu */ 441: if (sym == lparen) /* a jeho test na ocakavanu '(' */ 442: { 443: double x; /* prvy operand funkcie max|min */ 444: 445: getSymbol(); /* nacitanie dalsieho symbolu */ 446: vyraz(); /* a jeho vyhodnotenie ako vyrazu */ 447: x = cislo; /* uchovanie hodnoty 1. operandu */ 448: 449: if (sym == semicolon) /* za 1. operandom sa ocakava ';' */ 450: { 451: getSymbol(); /* nacitanie dalsieho symbolu */ 452: vyraz(); /* a jeho vyhodnotenie ako vyrazu, */ 453: 454: /* podla typu operacie (max|min) volanie prislusnej funkcie */ 455: cislo = (op == maxsym ? max(x, cislo) : min(x, cislo)); 456: 457: if (sym == rparen) /* za 2. operandom musi nasledovat ')' */ 458: getSymbol(); 459: else 460: err_rparen(op, matherr); 461: } 462: else /* chyba bodkociarka za 1. operandom */ 463: err_semicolon(op, 1, matherr); 464: } 465: else 466: err_lparen(op, matherr); /* chyba '(' za symbolom MAX|MIN */ 467: } 468: } 469: 470: /* procedura zapise do fout chybovu hlasku pre ocakavany symbol sym */ 471: void err_lparen(SYM_OP sym, FILE *fout) 472: { 473: fputs("Ocakavam lavu zatvorku '(' po volani funkcie ", fout); 474: print_sym(sym, fout); 475: fputs("...\n", fout); 476: } 477: 478: /* procedura zapise do fout chybovu hlasku pre ocakavany symbol sym */ 479: void err_rparen(SYM_OP sym, FILE *fout) 480: { 481: fputs("Ocakavam pravu zatvorku ')' vo volani funkcie ", fout); 482: print_sym(sym, fout); 483: fputs("(...\n", fout); 484: } 485:

109

486: /* procedura zapise do fout chybovu hlasku daneho typu * 487: * pre ocakavany symbol sym */ 488: void err_semicolon(SYM_OP sym, short typ, FILE *fout) 489: { 490: fputs("Vo funkcii ", fout); 491: print_sym(sym, fout); 492: 493: switch (typ) 494: { 495: case 1 : fputs("(x;y)", fout); break; /* obidva operandy su realne cisla */ 496: case 2 : fputs("(x;n)", fout); break; /* x - realne; n - cele cislo */ 497: case 3 : fputs("(m;n)", fout); break; /* obidva operandy su cele cisla */ 498: default: fputs("\n\nChyba vo volani procedury err_semicolon()!\n\n", fout); 499: } 500: 501: fputs(" ocakavam bodkociarku...\n", fout); 502: }

Doplnenie funkcie vyraz() o interpretáciu unárnych operátorov

Oproti predchádzajúcej verzii programu bola funkcia vyraz() rozšírená (riadky 322 až 332) o interpretáciu unárnych operátorov + a – pred termom, číslom, funkciou, prípadne výrazom uvedeným v zátvorkách. Znamienko + resp. – sa považuje za unárny operátor iba v prípade, že nenasleduje za matematickými operáciami: +, –, * a /, tzn. že pokiaľ chceme vypočítať napr. výraz “1/-2“, musíme ho zapísať ako “1/(-2)“.

Implementácia unárnych operátorov do kalkulátora začína riadkom 322, v ktorom sme zadefinovali premennú unary_op typu short int, ktorá nadobúda hodnotu +1 v prípade unárneho plus alebo -1 v prípade unárneho mínus. Po vstupe do funkcie vyraz() zisťujeme (riadok 324), či globálna premenná sym, uchovávajúca aktuálny symbol (vrátený pomocou funkcie getSymbol()) vstupného reťazca predstavujúceho matematický výraz, nadobúda symbol znamienka + alebo –. Ak je podmienka splnená, znamienko sa vyskytuje na začiatku výrazu, a ide teda o unárny operátor a nie o operáciu sčítania/odčítania, ktorá je ošetrená riadkom 336.

Hodnotu premennej unary_op inicializujeme na +1 resp. –1, v závislosti od zisteného unárneho operátora (riadok 326). Následne sa načíta ďalší symbol zo vstupného reťazca (riadok 328) a začína jeho vyhodnocovanie pomocou funkcie term() (riadok 329). Po vyhodnotení termu máme v globálnej premennej cislo výslednú hodno-tu, ktorú ešte násobíme hodnotou unárneho operátora (riadok 331), čím vlastne aplikujeme unárny operátor na term.

V prípade, že na začiatku nebol zistený symbol znamienka + alebo –, vyhodnocuje sa symbol sym ako term (riadok 334) tak isto, ako to bolo v predchádzajúcej verzii programu.

Rozšírenie funkcie faktor() o syntaktickú analýzu a vyhodnotenie funkcií MAX a MIN

Funkcia faktor() bola oproti predchádzajúcej verzii programu rozšírená o syntaktickú analýzu a vyhodnotenie štatistických funkcií MAX a MIN. Funkcie majú všeobecný tvar: max(číslo1 ; číslo2) a min(číslo1 ; číslo2)

Namiesto čísiel sa môžu vyskytovať aj matematické výrazy – nemusia byť uvedené v zátvorkách. Funkcia MAX vráti väčšiu hodnotu zo svojich dvoch argumentov, a naopak MIN vráti menšiu zo svojich dvoch hodnôt. Takže napríklad výraz “max(4 – 8*4 + abs(-3) ; -20 * min(7;1))“ vyhodnotí kalkulátor ako –20, pretože výsledok druhého argumentu funkcie MAX je vyšší ako hodnota prvého výrazu.

Ako ste si už asi všimli, funkcia faktor() už implementuje aj druhý bod definície faktora (viď. predchádzajú-cu lekciu), pretože realizuje syntaktický analyzátor s vyhodnocovaním pre matematické funkcie: ABS (absolútna hodnota – riadky 417 až 434), MAX (maximálna hodnota – riadky 436 až 467) a MIN (minimálna hodnota – detto ako pri MAX).

Matematickú funkciu ABS analyzuje a vyhodnocuje funkcia faktor() nasledovne. Ak sym obsahuje symbol absolútnej hodnoty (riadok 417), načíta sa ďalší symbol zo vstupného reťazca (riadok 419) a otestuje sa, či predstavuje symbol ľavej okrúhlej zátvorky ‘(’ (riadok 420), pretože funkcia ABS má syntax: abs(výraz), tzn. že po symbole ABS musí bezprostredne (samozrejme biele znaky sa vyskytovať môžu) nasledovať ľavá okrúhla zátvorka, za ňou číslo alebo výraz a nakoniec pravá okrúhla zátvorka. Ak za symbolom ABS nenasleduje ‘(’, vypíše

110

sa chybová správa (riadok 433), ináč sa načíta ďalší symbol (riadok 422) a vyhodnotí sa ako výraz (riadok 423). Po vyhodnotení výrazu globálna premenná cislo obsahuje jeho hodnotu a aplikuje sa na ňu samotná funkcia abs() (riadok 425). sym teraz (po vyhodnotení výrazu) obsahuje nasledujúci symbol zo vstupného reťazca, ktorý sa testuje, či zodpovedá symbolu „koniec zátvorky“, t.j. pravej okrúhlej zátvorke ‘)’ (riadok 427). Ak áno, načíta sa ďalší symbol (riadok 428) ináč sa vypíše príslušné chybové hlásenie (riadok 430).

Syntaktická analýza funkcie MAX resp. MIN je oproti funkcii ABS o trochu „zložitejšia“, pretože obidve funk-cie sú dvojargumentové na rozdiel od ABS, ktorá má iba jeden argument. Argumenty (operandy) sú od seba oddelené bodkočiarkou ‘;’, ktorá je reprezentovaná symbolom semicolon. Keďže obe funkcie sú z hľadiska syntaxe totožné, rozdiel je iba vo funkcii, ktorá sa na ne aplikuje (⇒ sémantika je odlišná), časť funkcie faktor(), ktorá vykonáva syntaktickú analýzu je spoločná. Nasledujúci odsek vysvetľuje syntaktickú analýzu a vyhodnocovanie obidvoch funkcií.

Ak sym obsahuje symbol funkcie MAX alebo MIN (riadok 436), do lokálnej premennej op sa uchová tento symbol (riadok 438). Načíta sa ďalší symbol zo vstupného reťazca (riadok 440) a testuje sa na symbol ľavej zátvorky ‘(’ (riadok 441), ktorá má podľa syntaxe nasledovať za symbolom MAX resp. MIN. Ak zátvorka chýba, vypíše sa príslušná chybová správa (riadok 466), ináč sa načíta ďalší symbol (riadok 445), počnúc ktorým sa začne vyhodno-covanie výrazu (riadok 446). Výraz predstavuje prvý operand (argument) funkcie MAX alebo MIN a jeho hodnota sa uchováva do lokálnej premennej x (riadok 447), ktorá bola definovaná v tom istom bloku (riadok 443). Po vyhodno-tení výrazu obsahuje sym nasledujúci symbol (symbol za 1. argumentom funkcie), a ten by mal byť podľa syntaxe oddeľovač, ktorým je očakávaná bodkočiarka ‘;’ (riadok 449). Ak bodkočiarka chýba, vypíše sa chybová správa (riadok 463). Na tento účel slúži funkcia err_semicolon() (riadky 488 až 502), ktorá vypíše do súboru fout pre zadaný symbol sym chybovú správu daného typu. Ak sú argumenty funkcie MAX prípadne MIN od seba oddelené bodkočiarkou, načíta sa ďalší symbol (riadok 451) a začne sa vyhodnocovať ako výraz (riadok 452). Výsledok výrazu predstavuje druhý argument a jeho hodnota sa nachádza v globálnej premennej cislo. Podľa uchovaného symbolu operácie sa zavolá príslušná funkcia MAX alebo MIN so skutočnými parametrami x (1. argument) a cislo (2. argument), a jej výsledok sa uloží späť do globálnej premennej cislo (riadok 455). Po vyhodnotení výrazu (riadok 452) obsahuje sym symbol nasledujúci za 2. argumentom funkcie, ktorým by mal byť podľa syntaxe symbol ‘)’ (riadok 457). Ak ním nie je, vypíše sa chybová správa (riadok 460), ináč sa načíta ďalší symbol zo vstupného reťazca (riadok 458), ktorý už predstavuje prvý symbol za volaním matematickej funkcie MAX(x;y) alebo MIN(x;y).

Cvičenia 1. Otestujte syntaktický analyzátor programu P19.CPP na nasledovných výrazoch:

"1 + abs(-3)*8" "1 + ABS ( - 3 ) * 8" "1 + abs -3 * 8" "1 + abs(-3 *)8" "1 + abs(-3 2) * 8" "max4;2" "max(4;2;1)" "min(4 2)"

2. Rozšírte funkciu faktor() pridaním ďalšej vetvy else-if o syntaktický analyzátor s vyhodnocovačom pre výpo-čet matematickej funkcie druhá mocnina, ktorej syntax je nasledovná: SQR ( vyraz ) Sémantika je jasná z názvu funkcie, teda funkcia vráti druhú mocninu svojho argumentu, ktorým môže byť ako číslo tak aj výraz. Na aplikáciu funkcie druhá mocnina použite vami definované makro s parametrom SQR(x) z knižnice FUNKCIE.CPP. Zimplementovanú funkciu otestujte na nasledovných výrazoch (výsledky sú uvedené v hranatých zátvorkách):

"1 + sqr(2) - 8" [-3] "1 + sqr 2 - 8" [Ocakavam lavu zatvorku '(' po volani funkcie SQR...] "1 + sqr ( 2 - ) 8" [Chyba! Prazdny vyraz ()...] "1 + sqr ( 2 4) -8" [Ocakavam pravu zatvorku ')' vo volani funkcie SQR(...]

Riešenie: program c19-2.cpp

111

3. Podobne ako v cvičení 2. rozšírte funkciu faktor() o syntaktický analyzátor s vyhodnocovačom pre výpočet druhej odmocniny. Syntax: SQRT ( vyraz ) Na realizáciu sémantiky použite funkciu root() z FUNKCIE.CPP, ktorej druhý argument bude číslo 2. Pozor! Pred volaním root() otestujte, či nie je pod odmocninou záporné číslo. Ak je, zapíšte

do matherr správu: "MATH Error: Zaporne cislo pod odmocninou\n" Syntaktický analyzátor pre funkciu SQRT otestujte na rovnakých výrazoch ako v predchádzajúcom cvičení, len namiesto funkcie SQR použite SQRT. Výsledky by mali byť rovnaké okrem 1. výrazu, ktorého výsledok je -5.58578643762691

Riešenie: program c19-3.cpp

4. Všimnite si, že všetky doteraz implementované jednoargumentové matematické funkcie (ABS, SQR a SQRT) majú rovnakú syntax (okrem samotného symbolu mat. funkcie) – funkcia ( vyraz ) – a teda syntaktický analyzátor má rovnakú kostru pre všetky tri funkcie. Rozdiel je iba v samotnej funkcii, ktorú aplikujeme na hodnotu výrazu (argumentu funkcie), teda v sémantike príkazu. Poznámka: Pri funkcii SQRT je navyše ošetrený prípad, keď sa pod odmocninou nachádza záporné číslo

(hodnota argumentu < 0). Prepíšte funkciu faktor() z predchádzajúceho cvičenia tak, že na jednoargumentové funkcie použijete iba jednu vetvu konštrukcie if-else a sémantiku ošetríte príkazom switch. To znamená, že analyzátor syntaxe bude spoločný a vykonanie (aplikácia) príslušnej funkcie bude riešené pomocou príkazu switch. (Použitie spoločného syntaktického analyzátora v jednej vetve príkazu if-else realizujte podobne ako je to pri funkciách MAX a MIN).

Riešenie: program c19-4.cpp

5. Rozšírte funkciu faktor() z predchádzajúceho cvičenia tak, aby kalkulátor „vedel“ počítať aj nasledovné jednoargumentové funkcie:

FACT(číslo), ROUND(číslo), CEIL(číslo), FLOOR(číslo) a TRUNC(číslo), pri ktorých sa môže namiesto čísla nachádzať aj výraz.

• Rozšírenie preveďte doplnením vetiev príkazu switch, v ktorom sa realizuje sémantika funkcií. • Na vyhodnotenie týchto matematických funkcií použite rovnomenné funkcie/makrá z knižnice

FUNKCIE.CPP [pre FACT(číslo) použite volanie funkcie faktorial()]. Sémantika funkcií: viď. predošlé lekcie.

• Pri volaní funkcie faktorial(cislo) využite implicitnú typovú konverziu, nakoľko formálny parameter (viď. prototyp funkcie faktorial()) vyžaduje celé číslo typu unsigned short.

• Pre funkciu FACT ošetrite prípad, ak hodnota argumentu je menšia ako 0, zapísaním chybovej správy "MATH Error: Nemozem vyratat faktorial pre zaporne cislo...\n" do matherr, a testujte návratovú hodnotu, ktorá indikuje pretečenie rozsahu čísla typu double. V prípade pretečenia zapíšte do matherr chybovú správu: "MATH Error: Number overflow pre FACT()...\n".

Riešenie: program c19-5.cpp

6. Otestujte program z predchádzajúceho cvičenia na nasledovných výrazoch. V hranatých zátvorkách sú uvedené očakávané výsledky.

"Fact(-5)" [MATH Error: Nemozem vyratat faktorial pre zaporne cislo...] "fact(abs(-5))" [120] "round(4.59)" [5] "round(4.19)" [4] "trunc(4.99)" [4] "ceil (4.13)" [5] "floor(2.713)" [2] "floor(2831.47e-2)" [28] "FACT(180)" [MATH Error: Number overflow pre FACT()...] "max( round(22/7) - fact(4) * sqr(4) + sqrt(256) ; -trunc(sqrt(13)) * abs(min(4.5E3/floor(77.779) ; 50/sqrt(2)) - 180*3.14159)) " [-365]

112

Korektný zápis do prúdu matherr odskúšajte tak, že tieto výrazy napíšte do súboru s názvom napr. “vyrazy.txt“ a spustite program z príkazového riadku nasledovnými dvoma spôsobmi: “A:\> c19-5.exe vyrazy.txt“ – spôsobí, že výstup pôjde na obrazovku “A:\> c19-5.exe vyrazy.txt vystup.txt“ – celý výstup by mal ísť do súboru vystup.txt

7. Syntaktický analyzátor pre dvojargumentové funkcie, pri ktorých sú argumenty od seba oddelené bodkočiarkou, už máte implementovaný (funkcie MAX a MIN). Rozšírte funkciu faktor() o vyhodnocovanie matematických funkcií najväčší spoločný deliteľ (NSD) a zvyšok po delení – modulo (MOD), ktorých syntax je nasledovná:

NSD(cislo1 ; cislo2) MOD(cislo1 ; cislo2)

pričom namiesto čísla môže byť uvedený aj výraz.

Pre vyhodnotenie týchto funkcií prepracujte vetvu príkazu if-else vo funkcii faktor() podobne, ako v cvi-čení 4, tzn. analýzu syntaxe dvojargumentových funkcií bude vykonávať vetva, ktorá v súčasnosti vyhodnocuje funkcie MAX a MIN, a sémantiku funkcií implementujte pomocou príkazu switch.

Samotné vyhodnotenie funkcie NSD realizujte volaním rovnomennej funkcie z knižnice FUNKCIE.CPP, pričom za skutočné parametre dosaďte absolútne hodnoty vyhodnotených výrazov predstavujúcich argumenty. Pretypovanie pomocou makra trunc(x) nie je potrebné, pretože sa vykoná implicitná typová konverzia.

Vyhodnotenie funkcie MOD realizujte pomocou štandardnej C-čkovskej funkcie „%“ so skutočnými parametrami pretypovanými na typ long (na pretypovanie použite makro s parametrom trunc() z knižnice FUNKCIE.CPP).

V prípade chýbajúceho oddeľovača argumentov, ktorým je bodkočiarka, volajte funkciu err_semicolon() so skutočným parametrom typ = 3. To znamená, že v prípade chýbajúcej bodkočiarky sa pre jednotlivé funkcie vypíšu nasledovné chybové správy:

MAX - "Vo funkcii MAX(x;y) ocakavam bodkociarku...\n" MIN - "Vo funkcii MIN(x;y) ocakavam bodkociarku...\n" NSD - "Vo funkcii NSD(m;n) ocakavam bodkociarku...\n" MOD - "Vo funkcii MOD(m;n) ocakavam bodkociarku...\n"

Poznámka: Volanie procedúry err_semicolon() realizujte pre každý typ iba raz, t.j. použite príkaz switch, v ktorom vetvy s case návestiami pre rovnaké typy chybovej správy nebudú ukončené príkazom break;

Riešenie: program c19-7.cpp

8. Otestujte program z predchádzajúceho cvičenia na nasledovných výrazoch. Výrazy napíšte aj do súboru a volajte program z príkazového riadku s parametrom názov súboru. Očakávané výsledky sú uvedené v hranatých zátvor-kách.

"nsd 2 4" [Ocakavam lavu zatvorku '(' po volani funkcie NSD...] "nsd (2;4;3)" [Ocakavam pravu zatvorku ')' vo volani funkcie NSD(...] "nsd(-25;15)" [5] "nsd(2 4)" [Vo funkcii NSD(m;n) ocakavam bodkociarku...] "max(2 4)" [Vo funkcii MAX(x;y) ocakavam bodkociarku...] "mod(5 3)" [Vo funkcii MOD(m;n) ocakavam bodkociarku...] "min(2 4)" [Vo funkcii MIN(x;y) ocakavam bodkociarku...] "mod ( 5 ; 3)" [2] "mod(5.9876;3.1234)" [2]

9. Rozšírte funkciu faktor() o vyhodnocovanie mat. funkcií n-tá mocnina (PWR) a n-tá odmocnina (ROOT), ktorých syntax je nasledovná: PWR(cislo1 ; cislo2) a ROOT(cislo1 ; cislo2) pričom namiesto čísla môže byť uvedený aj výraz.

• Sémantiku funkcií doplňte do vetvy príkazu switch z cvičenia číslo 7. • Vyhodnotenie funkcií realizujte volaním rovnomenných funkcií z knižnice FUNKCIE.CPP.

113

• Funkciu ROOT ošetrite tak, že pod odmocninou sa nemôže nachádzať záporné číslo (ani v prípade nepárneho exponentu n). Ak sa taký prípad vyskytne, zapíšte do matherr správu: "MATH Error: Zaporne cislo pod odmocninou\n" Otestujte exponent odmocniny n, či je prirodzené číslo. Ak nie je, zapíšte do matherr správu: "MATH Error: Nemozem vyratat n-tu odmocninu pre n <= 0\n"

• V prípade chýbajúceho oddeľovača argumentov (symbol ‘;‘) volajte funkciu err_semicolon() so skutočným parametrom typ = 2. To znamená, že v prípade chýbajúcej bodkočiarky sa pre funkcie PWR a ROOT vypíše príslušná chybová správa v tvare:

PWR - "Vo funkcii PWR(x;n) ocakavam bodkociarku...\n" ROOT - "Vo funkcii ROOT(x;n) ocakavam bodkociarku...\n"

Riešenie: program calc.cpp

10. Otestujte program z predchádzajúceho cvičenia na výrazoch, ktoré sú zapísané v dodanom súbore “priklady.txt“

Riešenie príkladu z cvičenia 9 predstavuje výsledný produkt nášho snaženia – program kalkulátor. Je to finálna verzia programu a dá sa výhodne použiť na počítanie matematických výrazov, ktoré obsahujú aj neštandardné funkcie, ako sú napr. MAX, MIN, NSD, rôzne typy zaokrúhľovania, atď.

114

Príloha Nasledujú témy z oblasti jazyka C, ktoré sa v tomto kurze neprebrali resp. boli vysvetlené iba čiastočne. Pre

úplné pochopenie jazyka C a zvládnutie techník používaných pri programovaní v C je nevyhnutné ich doštudovať. Uvedené témy neboli v tejto príručke vysvetlené najmä z dôvodu, že sa nepoužili pri výstavbe programu kalkulátor. Akiste si ale viete predstaviť, že by sa dali použiť pri vytváraní kalkulátora, no text by v tomto prípade presiahol rámec príručky. Uvedené témy si môžete naštudovať v knihách/učebniciach uvedených v zozname literatúry.

• Priority vyhodnocovania operátorov • Príkaz skoku – goto • Oddelený preklad súboru • Podmienený preklad riadený hodnotou konštantného výrazu • Pragma direktívy a ostatné direktívy preprocesora jazyka C • Dynamické prideľovanie pamäti • Pamäťové triedy • Typové kvalifikátory • Konverzia smerníkov • Smerníky na funkcie • Operátor sizeof • Dynamické a viacrozmerné polia • Štruktúry a uniony • Bitové operácie a bitové polia

Témy, ako napr. pohyb v binárnych súboroch, režimy otvárania súborov, funkcie pre prácu s reťazcami, funkcie s premenlivým počtom argumentov a tak ďalej, neboli v príručke podrobne vysvetlené, prípadne boli úplne vylúčené, pretože ide o štandardné knižničné funkcie a ich vysvetlenie môžete nájsť v referenčných príručkách alebo manu-áloch k prekladaču jazyka C.

115

Literatúra

1. Aitken, P., Bradley, L. Jones: Sams Teach Yourself C in 21 Days, Fourth edition Indianapolis: Macmillan Computer Publishing, 1997. ISBN 0-672-31069-4 http://www.samspublishing.com/detail_sams.cfm?item=0672310694

2. Herout, P.: Učebnice jazyka C, 3. vyd. České Budějovice: Kopp, 1996. ISBN 80-85828-21-9 3. Horovčák, P., Podlubný, I.: Úvod do programovania v jazyku C, 1998

http://www.tuke.sk/podlubny/C 4. Molnár, Ľ., Češka, M., Melichar, B.: Gramatiky a jazyky, 1. vyd. Bratislava: Alfa – Praha: SNTL, 1987 5. Staugaard, Andrew C. Jr.: Structured and Object–Oriented Techniques; An Introduction Using C++, Second

edition New Jersey: Prentice-Hall, 1997. ISBN 0-13-488736-0 6. Šaloun, P.: Programovací jazyk C pro zelenáče, 1. vyd. Praha: Neokortex s.r.o., 1999. ISBN 80-86330-02-X

116

Register

#

#define · 42 #else · 43–44 #endif · 43–44 #ifdef · 43–44 #ifndef · 43–44 #include · 8, 42 #undef · 43

A

adresný operátor & · 54 argc · 69 argv · 69 atexit() · 101

B

biele znaky · 17 blok · 9 break · 27

C

case · 70 casting · 39 const · 30, 48, 62 continue · 27

D

debugger · 43 default · 70 dereferenčný operátor · 54 do–while · 30–31

E

enumerated type · 80 exit() · 101

F

faktor · 103 fclose() · 89 fflush() · 89 fgetc() · 91

fgets() · 91, 92 flush · 89 flushall() · 89 fopen() · 89 for · 33–34 fprintf() · 90 fputc() · 92 fputs() · 92 fread() · 92 fscanf() · 91 Funkcia · 7, 11

argumenty · 7, 11 definícia · 11 deklarácia · 11 hlavička · 11 parametre · 11, 13 rekurzívna · 36 volanie · 9 volanie hodnotou · 13, 56 volanie odkazom · 11, 13, 55–56

Funkcie knižničné · 7, 9 užívateľom definované · 7, 9 vnorené · 11

Funkčný prototyp · 8, 12 fwrite() · 92

G

getc() · 91 getch() · 24 getchar() · 23 getche() · 24 gets() · 49, 91

H

hlavičkový súbor · 8

I

if-else · 19–20

K

komentáre · 9

L

lazy evaluation · 24 lexikálny analyzátor · 74

117

M

macro processing · 41 main() · 8, 69

N

NULL · 55

O

Operátor čiarky · 81 porovnania · 26

P

pointer · 52 polia

dvojrozmerné · 63 jednorozmerné · 47–48

Premenná · 8 definícia · 14 globálna · 14 lokálna · 14

preprocesor · 41 presmerovanie · 93 pretypovanie · 39 príkaz · 8 printf() · 15–16, 17 procedúry · 13 prúdy dát · 88 putc() · 92 putchar() · 24, 92 puts() · 49

R

referenčný operátor · 52 Rekurzia · 36

nepriama · 36 priama · 36

return · 8, 12, 13, 101

S

scanf() · 16–17 setvbuf() · 88 sizeof() · 92 smerník · 52 static · 14 stdaux · 90 stderr · 90, 93 stdin · 90 stdout · 90 stdprn · 90 stream · 88 strlen() · 64 strnicmp() · 49 strtod() · 73 switch · 70 syntaktický analyzátor · 102

T

term · 103 ternárny operátor · 20 typecasting · 39 typedef · 79 Typová konverzia · 38

explicitná · 38, 39 implicitná · 38–39

typové modifikátory · 33

U

unárne operátory · 46, 47

V

výraz · 103

W

while · 26