.pl 60 .po 12 .mt 2 .mb 2 .pn 57 KAPITOLA 4: FUNKCE A STRUKTURA PROGRAMU --------------------------------------- Funkce drobi velke vypocetni ulohy na mensi a umoznuji lidem stavet na tom, co ostatni jiz udelali, misto toho, aby zacinali od zacatku. Vhodne funkce mohou casto skryt detaily casti programu, kde o nich neni treba nic vedet a tak vyjasnit celek a usnadnit pripadne zmeny v programu. Jazyk C byl navrzen tak, aby bylo mozne snadno uzivat funkce. Programy v jazyku C sestavaji obecne z mnozstvi malych funkci spise nez z nekolika velkych. Program muze byt obsazen v jednom nebo vice zdrojovych souborech libovol- nym zpusobem. Zdrojove soubory mohou byt prekladany oddelene a linkovany dohromady spolu s drive prelozenymi funkcemi z knihoven. Nebudeme zde o techto problemech hovorit, protoze jsou zavisle na pouzitem operacnim systemu. Vetsina programatoru je jiz seznamena s "knihovnimi" funkcemi pro vstup a vystup /getchar, putchar/ a pro nume- ricke vypocty /sin, cos, sqrt/. V teto kapitole ukazeme vice o pouzivani novych funkci. 4.1. Zaklady ------------ Pro zacatek napiseme program, ktery tiskne kazdou radku ze vstupu, ktera ma jistou vlastnost nebo obsahuje urcity retezec znaku. /To je specialnim pripadem obsluzneho programu grep v systemu UNIX./ Napr. hledejme radky, ktere obsahuji retezec "the" v souboru radku Now is the time for all good men to come to the aid of their party. vysledkem bude: Now is the time men to come to the aid of their party. zakladni strukturu muzeme popsat ve trech castech while (je jeste dalsi radka) if (radka obsahuje dany retezec) vytiskni ji Prestoze by bylo jiste mozne napsat tento program jako celek, lepsi cestou je vyuzit prirozene struktury a pouzit pro kazdou cast oddelenou funkci. Se tremi mensimi useky se lepe zachazi nez s jednim velkym celkem, protoze nepodstat- ne detaily mohou byt do nich "utopeny" a mohou byt minimali- zovany nezadouci interakce. Jednotlive useky take mohou Š.po 3 byt uzitecne pro svou cinnost. "Je jeste dalsi radka" je funkce getline, ktera je po- psana v kap. 1 a "vytiskni ji" je funkce printf, kterou jiz pro nas nekdo napsal. To znamena, ze musime pouze napsat funkci, ktera rozhoduje, zda radka obsahuje urcity retezec znaku. Tento problem muzeme vyresit tim, ze ukradne- me ideu z jazyka PL/1; funkce index (s,t) vraci pozici v retezci s, kde zacina retezec t, nebo -1, kdyz t neni obsazeno v s. Pro zacatek v retezci s pouzijeme spise 0 nez 1, protoze pole v jazyku C zacinaji rovnez nulou. Kdyz pozdeji budeme potrebovat dokonalejsi vyhledavani, staci pouze zmenit funkci index; zbytek programu zustane nezmenen. Rozhodneme-li se pro tento postup, lze program primo napsat. V programu je zretelne videt, jak spolu jeho casti souvisi. Pro tentokrat bude retezec, ktery hledame, znakovou konstantou v argumentu funkce index, coz neni uplne obecny zpusob. Kratce se vratime k tomu, jak inicializovat znakova pole a v kap. 5 ukazeme, jak muzeme nastavit pozadovany retezec pro vyber, kdyz je program vyvolan. Popsana je rov- nez nova verze funkce getline. Porovnejte ji s funkci z kap. 1. # define MAXLINE 1000 main() /*najdi vsechny radky obsahujici retezec */ { char line [MAXLINE]; while (getline(line, MAXLINE) > 0) if (index(line, "the") >= 0) printf ("%s",line); } getline(s,lim) /*nacti radku do s, vrat delku*/ char s[]; int lim; { int c, i; i=0; while(--lim > 0 && (c=getchar()) != EOF && c != '\n') s[i++]=c; if (c == '\n') s[i++]=c; s[i]='\0'; return(i); } index(s,t) /* vrat index vyskytu t v s */ char s[], t []; { int i,j,k; for (i=0; s[i] != '\0'; i++) { for(j=i, k=0; t[k] != '\0' && s[j] == t[k]; j++,k++) ; if(t[k] == '\0') Š.po 12 return(i); } return(-1); } Kazda funkce ma strukturu nazev (seznam argumentu, jsou-li nejake) deklarace argumentu, jsou-li nejake { deklarace a prikazy, jsou-li nejake } Jak je naznaceno, ruzne casti mohou byt vynechany; mini- malni funkce vypada takto: dummy () {} a nedela nic. /Funkce, ktera nedela nic je uzitecna proto, ze "drzi misto" nove funkci pri vyvoji programu./ Pred jmenem funkce muze byt rovnez uveden typ, kdyz funkce vraci neco jineho nez celociselnou hodnotu; o tom budeme hovorit v teto sekci. Program je jen soubor definic jednotlivych funkci. Komunikace mezi funkcemi je /v tomto pripade/ pomoci argu- mentu a hodnot jimi vracenymi; muze byt rovnez provadena externimi promennymi. Funkce se ve zdrojovem souboru mohou objevit v libovolnem poradi. Zdrojovy program muze byt roz- delen do mnoha souboru, pokud neni nektera funkce roztrzena. Prikazem return vraci volana funkce hodnotu jednotce volajici. Za prikazem return se muze objevit libovolny vyraz: return (vyraz) Volajici jednotka muze dle libosti hodnotu ignorovat. Navic po prikazu return nemusi nasledovat zadna hodnota: v tomto pripade neni zadna hodnota vracena. Rizeni se rovnez volajici jednotce vraci tehdy, dospela-li volana funkce "na konec", tj. k prave uzavirajici slozene zavorce. Obecne neni zakazano, aby funkce z jednoho mista hodnotu vracela a z jineho ne, ale muze to zpusobit potize. V pripade, ze funkce zadnou hodnotu nevraci, je "hodnota" funkce nahodna, nedefinovana, program LINT jazyka C lokali- zuje takoveto chyby. Zpusob, jak prekladat a sestavovat program v jazyku C, ktery je v nekolika zdrojovych souborech, se lisi system od systemu. Napr. v systemu UNIX je provadeno prikazem cc, jak jsme ukazali v kap. 1. Predpokladejme, ze tri funkce jsou ve trech souborech nazvanych main.c, getline.c, index.c. Potom prikaz cc main.c getline.c index.c prelozi tyto tri soubory a ulozi premistitelny kod do souboru main.o, getline.o, index.o a sestavi je vsechny do pro- veditelneho programu nazvaneho a.out. Š.po 3 Jestlize se napr. v main.c vyskytne chyba, tak muze byt pozdeji prelozen sam a vysledek sestav spolu s drive prelozenymi soubory cc main.c getline.o index.o Prikaz cc uziva ".c" a ".o" k odliseni zdrojovych a premisti- telnych souboru. C v i c e n i 4 - 1. Napiste funkci rindex (s,t), ktera vra- ci pozici posledniho vyskytu retezce t v s, nebo -1, kdyz s neobsahuje t. 4.2. Funkce, ktere nevraceji cela cisla --------------------------------------- Doposud v zadnem nasem programu nebyla pouzita deklarace funkce. To proto, ze kazda funkce je implicitne deklarovana podle toho, v jakem vyrazu nebo prikazu se vyskytuje. jako napr. while (getline(line, MAXLINE)>0) Jestlize se jmeno, ktere nebylo dosud deklarovano, objevi ve vyrazu a bezprostredne za nim nasleduje leva zavorka, tak je podle kontextu definovano jako jmeno funkce. Navic implicitne se predpoklada, ze funkce vraci hodnotu int. Protoze char je ve vyrazech brano jako int, tak neni potreba deklarovat funkci jako char. Tyto predpoklady pokryvaji vetsinu pripadu a zahrnuji rovnez nase priklady. Co se ale stane, ma-li funkce vracet hodnoty jineho typu? Mnoho numerickych funkci jako jsou sqrt, sin a cos vraci hodnotu typu double; jine specialni funkce vraceji jine typy. Abychom si to ilustrovali napisme funkci atof(s), ktera konvertuje retezec s na ekvivalentni cislo typu double. Funkce atof je rozsirenim funkce atoi, kterou jsme v kapitolach 2 a 3 vytvorili v nekolika verzich. Pracuje se znamenkem a desetinnou teckou a cela a desetinne casti mohou nebo nemusi byt uvedeny. /Neni to konverzni program vysoke kvality./ Za prve: funkce atof musi sama deklarovat typ, ktery vraci, protoze to neni typ int. Protoze float je konvertovano ve vyrazech na double, neni namitek proti tomu, ze vraci float; stejne ale muzeme vyuzit zvysene presnosti a proto deklarovat funkci jako double. Prikaz typu je uveden pred jmenem funkce, jako double atof(s) /*konvertovan retezec s na double */ char s[]; { double val, power; int i, sign; for (i=0; s[i] == ' ' || s[i] == '\n' || s[i] == '\t'; i++) Š.po 12 ; /*preskoc oddelovac*/ sign = 1; if (s[i] == '+' || s[i] == '-') /*znak*/ sign = (s[i++] == '+') ? 1 : -1; for (val = 0; s[i] >= '0' && s[i] <= '9'; i++) val = 10 * val + s[i] - '0'; if (s[i] == '.') i++; for (power = 1; s[i] >= '0' && s[i] <= '9';i++) { val = 10 * val s[i] - '0'; power *= 10; } return (sign * val / power); } Za druhe: ve volajici jednotce je nutno urcit, ze atof vraci necelociselnou hodnotu. Tato deklarace je ukazana v nasledujicim programu simulujicim primitivni stolni kalkulator, ktery cte jedno cislo z radky a secita je a tiskne predbezne soucet pred kazdym vstupem. #define MAXLINE 100 main() /*primitivni stolni kalkulator*/ { double sum atof (); char line[MAXLINE]; sum = 0; while (getline(line,MAXLINE)>0) printf ("\t%.2f\n", sum += atof(line)); } Deklarace double sum, atof(); rika, ze sum je promenna s dvojnasobnou delkou a atof je funkce, ktera vraci hodnotu double. Jako mnemoniku doporu- cujeme, aby sum a atof byly oboje typu double. Jestlize je atof explicitne deklarovana na obou mistech, prekladac jazyka C predpoklada, ze vraci hodnotu integer, a vysledek bude nesmyslny. Jestlize funkce atof a program main jsou ve stejnem souboru a typ funkce same a volani v jednotce main sobe neodpovidaji, tak predkladac bude hlasit chybu. Ale jestlize /coz je castejsi pripad/ atof byla predkladana oddelene, nesoulad nebude detekovan, atof bude vracet hodnotu double, se kterou program main bude pracovat jako s promennou integer a dostaneme nesmyslny vysledek /obsluzny program LINT odhali tuto chybu/. Mame-li vytvorenou funkci atof, muzeme napsat funkci atoi, ktera konvertuje retezec znaku na cislo tytu int: atoi(s) /*konvertuje retezec s na integer*/ char s[]; { double atof (); Š.po 3 return (atof(s)); } Vsimnete si struktury deklaraci a prikazu return. Hodnota vyrazu v return (vyraz) je vzdy konvertovana na typ funkce predtim, nez je prikaz return vykonan. Proto hodnota atof, ktera je typu double, je automaticky konvertovana na int, kdyz se objevi v prika- zu return, protoze funkce atoi vraci int. /Konverze cisla s pohyblivou radovou carkou na cele cislo orezava desetinnou cast, jak jsme ukazali v kap. 2./ C v i c e n i 4 - 2. Rozsirte funkci atof tak, aby umela zpracovavat cisla ve vedecke notaci, jako napr. 123.45e-6 kde cislo s pohyblivou desetinnou teckou muze byt nasledovano pismenem e nebo E a exponentem se znamenkem /ktere nemusi byt uvedeno/. 4.3. Vice o argumentech funkci ------------------------------ V kapitole 1 jsme uvedli skutecnost, ze argumenty funkci jsou predavany hodnotou, coz znamena, ze volana funkce obdrzi "soukromou", prechodnou kopii kazdeho argumentu a ne adresu. To znamena, ze funkce nemuze zmenit puvodni argument ve vola- jici jednotce. Ve funkci je argument ve skutecnosti lokalni promenna, ktera je inicializovana na hodnotu, kterou je funkce vyvolana. Kdyz se jako argument funkce objevi identifikator pole, je predano misto zacatku tohoto pole, prvky pole nejsou kopirovany. Funkce muze menit hodnoty prvku pole. Pole jsou tedy predavany adresou. V kapitole 5 budeme hovorit o pou- ziti pointru, ktere umoznuji funkcim menit take jednotlive promenne. Mimochodem, neexistuje uspokojivy zpusob, jak napsat obecnou funkci, ktera by mohla mit promenny pocet argumentu, protoze volana funkce nemuze urcit, kolik argumentu bylo skutecne pri vyvolani predano. Proto nemuzete opravdu napsat obecnou funkci, ktera bude pocitat maximum libovolneho mnozstvi argumentu jako to je u funkci MAX ve FORTRANU nebo PL/1. Bezpecne muzeme nakladat s promennym mnozstvim parametru, kdyz volana funkce nepouziva ty argumenty, ktere ji nebyly ve skutecnosti dodany a kdyz typy sobe odpovidaji. Funkce printf, ktera je nejobecnejsi funkci jazyka C pouzivajici promenny pocet argumentu, uziva prvni parametr k urceni poctu a typu argumentu. Skonci to ovsem spatne, kdyz volaji- ci jednotka nedoda dostatecne mnozstvi argumentu, nebo kdyz Š.po 12 neodpovidaji jejich typy. Je take neprenosna a musi byt modifikovana pro ruzne typy pocitacu. Jestlize jsou typy argumentu zname, je mozne oznacit konec seznamu argumentu nejakym dohodnutym zpusobem, napr. pouzit specialni hodnotu /casto se uziva nuly/, ktera ukoncu- je seznam argumentu. 4.4. Externi promenne --------------------- Program v jazyku C je slozen z mnoziny exernich objektu, ktere jsou bud promenne nebo funkce. Pridavne jmeno "externi, vnejsi" je uzito v kontrastu se slovem "interni, vnitrni", ktere popisuje argumenty a automaticke promenne uvnitr funkci. Externi promenne jsou definovany mimo funkce a jsou potenci- nalne dosazitelne mnoha funkcim. Samotne funkce jsou vzdy externi, protoze jazyk C nedovoluje definovat funkci uprostred jine funkce. Implicitne jsou externi promenne take "globalni", takze odkazy na tyto promenne jsou mozne i z funkci, ktere jsou prelozeny oddelene. V tomto smyslu jsou externi promenne analo- gii bloku COMMON ve FORTRANu nebo EXTERNAL v PL/1. Uvidime pozdeji, jak je mozne definovat externi promenne, ktere nejsou obecne dosazitelne, ale jsou dosazitelne pouze z jednoho zdro- joveho souboru. Protoze externi promenne jsou vseobecne dosazitelne, je mozne je pouzivat pro komunikaci mezi funkcemi namisto seznamu argumentu. Libobolna funkce muze dosahnout externi promennou odkazem na jeji identifikator, jestlize tento identifikator byl nejak deklarovan. Jestlize musi byt funkcemi sdilen velky pocet promennych, tak pouziti externich promennych je vyhodnejsi a efektivnejsi nez dlouhe seznamy argumentu. Avsak jak jsme poznamenali v kap. 1. musi byt tento zpusob pouzivan opatrne, protoze pusobi spatne na strukturu programu a vede k programum, ktere maji mnoho datovych spojeni mezi jednotlivymi funkcemi. Druhy duvod, proc pouzivat externi promenne je problem inicializace. Externi pole mohou byt na rozdil od automatic- kych /lokalnich/ poli inicializovany. O tom budeme hovorit skoro na konci teto kapitoly. Tretim duvodem pro pouzivani externich promennych je je- jich obor pusobnosti a zivotnost. Automaticke promenne jsou vnitrni, interni, dane funkci; zacnou existovat pri vyvolani funkce a zmizi, kdyz skonci cinnost funkce. Externi promenne jsou stale. Nevznikaji, nekonci a udrzuji hodnotu od volani jedne funkce do volani funkce dalsi. Proto, jestlize dve funkce musi sdilet urcita data a jedna nevola druhou, je casto pohodlnejsi, kdyz jsou tyto promenne uvedeny jako externi nez predavat tato data sem a tam argumenty. Vyzkousejme tyto navrhy na vetsim prikladu. Napiseme ji- ny programovy kalkulator, ktery bude lepsi nez predchozi. Bude dovolovat operatory +,-,*,/ a = /pro vytisteni vy- sledku/. Kalkulator bude pouzivat obracenou polskou notaci /dale RPN/ namisto notace infix, protoze je snadneji imple- mentovatelna. /RPN pouzivaji napr. kalkulatory firmy Hewlett- Packard./ V RPN kazdy operator nasleduje za operandy; vyraz Š.po 3 v infixove notaci jako (1-2) * (4+5) = je v RPN napsan takto 1 2 - 4 5 + * = neni nutne pouzivat zavorky. Implementace je opravdu jednoducha. Kazdy operand je ulozen do zasobniku; kdyz se ve vyrazu vyskytne operator, tak odpovidajici pocet operandu /dva pro binarni operace/ je vytazen ze zasobniku, operator je na ne aplikovan a vysledek je ulozen do zasobniku. Napr. v predchozim priklade jsou 1 a 2 ulozeny do zasobniku a potom jsou nahrazeny jejich rozdilem. Dale 4 a 5 jsou ulozeny a potom nahrazeny jejich souctem. Nasobek -1 a 9, ktery je -9 je v zasobniku nahradi. Operand = zobrazi vrchni prvek zasobniku, aniz ho vyjme /takto muzeme kontrolovat mezivysledky./ Operace pro ukladani a vyjimani ze zasobniku jsou jedno- duche, ale kdyz jsou pridana chybova hlaseni, tak jsou dosta- tecne dlouha na to, aby byly soustredeny do funkci namisto opakovani v programu. Rovnez by mela byt pouzita oddelena funkce pro nacteni dalsiho vstupniho operatoru nebo operandu. Proto struktura programu bude nasledujici: while (dalsi vstup neni konec souboru) if (cislo) uloz je else if (operator) vyjmi operandy proved operaci uloz vysledek else chyba Hlavnim rozhodnutim pri navratu, coz jsme zatim nedisku- tovali, je to, kde je zasobnik ulozen a ktera funkce k nemu ma primy pristup. Jednou z moznosti je drzet jej v jednotce main a predavat jeho pozici funkcim, ktere s nim pracuji. Ale v jednotce main neni treba urcovat promenne, ktere ridi cinnost ukladani a vyjimani ze zasobniku, mely by se pouzit pouze operace "uloz" a "vyjmi". Takze se rozhodneme, ze zasobnik a jemu pridruzene promenne budou externi a dosazi- telne jen funkcim push - pop. Tento postup je snadno naprogramovatelny. Jednotka main bude velky prikaz switch, ktery pracuje s operatory a operan- dy. To bude typictejsi priklad pro pouziti prikazu switch, nez jaky byl uveden v kap. 3. #define MAXOP 20 /*maximalni mnozstvi operandu, operatoru*/ #define NUMBER '0' /*symbol pro cislo*/ #define TOOBIG '9' /*symbol pro prilis dlouhy rete- zec*/ main () /* RPN kalkulator*/ Š.po 12 { int type; char s[MAXOP]; double op2(), pop(), push(); while ((type = getop(s,MAXOP)) != EOF) switch (type) { case NUMBER: push (atof(s)); break; case '+': push (pop() + pop()); break; case '-': op2 = pop(); push (pop() - op2); break; case '/': op2 = pop(); if (op2 != 0.0) push (pop() / op2); else printf("zero divisor popped\n"); break; case '=': printf("\t%\n", push (pop())); break; case 'c': clear(); break; case TOOBIG: printf("%.20s ... is too long\n", s); break; default: printf("unknown command %c\n", type); break; } } #define MAXVAL 100 /*maximalni velikost zasobniku*/ int sp = 0; /*ukazatel zasobniku*/ double val[MAXVAL]; /*zasobnik cisel*/ double push(f) /*uloz f do zasobniku*/ double f; { if (sp < MAXVAL) return (val[sp++] = f); else { printf ("error: stack full\n"); clear (); return (0); } Š.po 3 } double pop () /*vyjmi vrsek zasobniku*/ { if (sp > 0) return (val [--sp]; else { printf ("error: stack empty\n"); clear (); return (0); } } clear () /*vymaz zasobnik*/ { sp = 0; } Prikaz c vymaze zasobnik. Uziva k tomu funkci clear, kterou rovnez pouzivaji funkce push a pop v pripade chyby. Za chvili se vratime k funkci getop. Jak bylo uvedeno v kap. 1, promenna je externi, je-li definovana mimo tela funkce. Proto musi byt ukazatel zasob- niku, ktery je sdilen funkcemi push, pop a clear, definovan mimo tyto funkce. Ale jednotka main se na tento ukazatel zasobniku neodkazuje - jeho reprezentace je skryta. Proto cast programu pro operator = musi byt napsana takto push (pop()); abychom dostali pouze hodnotu vrcholu zasobniku bez jejiho vyjmuti. Uvedomte si rovnez, ze + a - jsou komutativni operatory a nezalezi na poradi, v jakem jsou operandy vyjimany. Avsak pro operandy - a / musi byt operandy rozliseny. C v i c e n i 4 - 3. Mame-li napsan zakladni ramec kalkulato- ru, je snadne jej rozsirit. Pridejte operatory % pro zbytek po deleni a unarni minus. Pridejte prikaz "erase", ktery vymaze vrchol zasobniku. Pridejte prikazy pro pouzivani promennych. /26 jednoduchych promennych muzete snadno pridat./ 4.5. Pravidla pole pusobnosti ------------------------------ Funkce a externi promenne, ktere tvori program v jazyku C nemusi byt prekladany najednou; zdrojovy text programu muze byt v nekolika souborech a drive prelozene funkce mohou byt pripojovany z knihoven. Dve hlavni otazky, ktere klademe, jsou: - Jakym zpusobem jsou napsany deklarace, aby promenne byly radne deklarovany v prubehu prekladu? - Jak jsou deklarace udelany, aby vsechny casti programu Š.po 12 byly radne spojeny, kdyz je program sestavovan? P o l e p u s o b n o s t i identifikatoru promenne je cast programu, ve kterem je identifikator definovan. Pro automaticke promenne deklarovane na zacatku funkce je pole pusobnosti fun- kce, ve ktere je tato promenna deklarovana. Promenne stejneho jmena v ruznych funkcich nemaji nic spolecneho. Totez plati pro argumenty funkci. Pole pusobnosti externich promennych zacina v miste, ve kterem jsou deklarovany ve zdrojovem souboru a konci na konci tohoto souboru. Jestlize napr. val, sp, push, pop a clear jsou definovany v jednom souboru v tomto poradi tj. int sp = 0; double val[MAXVAL]; double push (f) (...) double pop () ( ...) clear () (...) potom promenne val a sp mohou byt pouzity ve funkcich push, pop a clear jednoduse jmenem; neni potreba je najak deklaro- vat. Je-li na druhe strane potreba pouzit externi promennou predtim, nez je definovana, nebo kdyz je definovana v jinem zdrojovem souboru nez v tom, kde je pouzivan, potom je nezbyt- na deklarace extern. Je dulezite rozlisovat mezi d e k l a r a c i externi promenne a jeji d e f i n i c i. Deklarace popisuje vlast- nosti promenne /tj. typ, rozmer, atd./; definice teto promenne vyhrazuje ale take misto v pameti. Jestlize se radky int sp; double val[MAXVAL]; uvedou mimo funkce, potom d e f i n u j i externi promenne sp a val a vyhrazuji jim mista v pameti a zaroven slouzi jako deklarace az do konce zdrojoveho souboru. Na druhe strane radky extern int sp; extern double val []; d e k l a r u j i pro zbytek zdrojoveho textu promennou sp jako int a val jako pole typu double /jehoz velikost je nekde jinde definovana/, ale nevytvareji promenne ani pro ne nevyhra- zuji mista v pameti. Ve vsech souborech, ktere tvori dany program, musi byt jen jedna definice externi promenne; ostatni soubory mohou obsahovat pouze deklaraci extern. /Deklarace extern muze byt rovnez v souboru, ktery obsahuje definici externi pro- menne./ Inicializace externich promennych je mozna jen v definici. Velikost pole musi byt specifikovana v definici a v deklaraci extern ji uvest muzeme nebo nemusime. Prestoze takovato organizace neni pro tento program pravdepodobna, predstavme si, ze val a sp jsou definovany a inicializovany v jednom souboru a funkce push, pop a clear jsou definovany v jinem souboru. Potom nasledujici definice Š.po 3 a deklarace jsou nezbytne: V souboru 1 int sp = 0; /*ukazatel zasobniku*/ double val[MAXVAL]; /*zasobnik*/ V souboru 2 extern int sp; extern double val []; double push (f) (...) double pop () (...) clear () (...) Protoze deklarace extern je v souboru 2 uvedena drive a mimo funkce, tak plati pro tyto funkce; tedy v souboru 2 staci jen jedna deklarace. Pro vetsi program je mozne pouzit prikazu #include /pro vkladani souboru, podrobne o tom pozdeji v teto kapito- le/ a tak mit jen jednu deklaraci extern pro cely program a tu vkladat timto prikazem jen pri prekladu. Nyni obratme pozornost k implementaci funkce getop, ktera nacita dalsi operator nebo operand. Zakladni funkce je jasna: preskoc mezery, tabelatory a symboly pro novou radku. Jestlize nacteny znak neni ani cislo ani desetinna tecka, vrat jej. Jinak nacti retezec cislic /kde rovnez muze byt desetinna tecka/ a vrat NUMBER, coz je signal pro to, ze bylo nacteno cislo. Podprogram je ponekud komplikovany, protoze musi spravne fungovat, je-li nactene cislo prilis dlouhe. Funkce getop cte cislice /i s desetinnou teckou/. Jestlize nedoslo k precteni vraci NUMBER a retezec cislic. Jestlize cislo bylo prilis dlouhe, getop ignoruje zbytek vstupu, takze uzivatel muze prepsat radku od mista chyby; bylo-li cislo prilis dlouhe, vraci TOOBIG. getop (s, lim) /*nacten operator nebo operand*/ char s[]; int lim; { int i, c; while ((c=getch())==' '||c=='\t'||c=='\n') ; if (c != '.' && (c < '0' || c > '9')) return (c); s [0] = c; for (i = 1;(c=getch()) >= '0' && c<='9';i++) if (i < lim) s [i] = c; if (c == '.') /*desetinna cast*/ { if (i < lim) s [i] = c; for(i++; (c=getch()) >= '0' && c <= '9';i++) if (i < lim) s [s] = c; } Š.po 12 if ( i< lim) /*cislo je ok*/ { ungetch (c); s [i] = '\'; return (NUMBER); } else /*je prilis dlouhe, preskoc zby- tek radky*/ { while (c != '\' && c != EOF) c = getch(); s [lim-1] = '\0'; return (TOOBIG); } } Co delaji funkce getch a ungetch? Casto se vyskytne situ- ace, ze program nacitajici vstup nemuze rozhodnout, zdali jiz nacetl dost a pritom nenasel vice nez je potreba. Jednim z ta- kovych pripadu je cteni znaku, ktere tvori cislo: dokud nebyl nacten znak, ktery neni cislici, tak cislo neni kompletni. Po- tom ale program precetl jeden znak navic. Problem by byl snadno vyresen, kdyby bylo mozno tento znak "vratit zpatky". Tedy vzdy kdyz program nacte o znak vice, muze tento znak vratit zpatky do vstupu, takze se tento znak pro zbytek programu jevi tak, jako kdyby nebyl nikdy nacten. Nastesti je tento problem snadno resitelny pomoci dvojice doplnujicich se funkci. Funkce getch nacita dalsi znak ze vstupu; ungetch jej vraci zpet do vstupu, takze pri dalsim volani funkce getch vezme opet tento znak. Jejich spoluprace je jednoducha a ungetch vrati znak do spolecne sdileneho bufferu - znakoveho pole. getch cte z tohoto pole tehdy, kdyz v nem neco je, v opacnem pripade zavola getchar. Musi rovnez existovat index, ktery urcuje pozici znaku v bufferu. Protoze buffer a index jsou sdileny funkcemi getch a ungetch a musi uchovavat svoji hodnotu mezi vyvolanim techto funkci musi byt definovany jako externi pro obe funkce.. Potom muzeme napsat getch a ungetch zakto: # define BUFSIZE 100 char buf[BUFSIZE]; /*definice bufferu*/ int bufp = 0; /*volna pozice v buf*/ getch () /*nacteni vraceneho znaku*/ { return((bufp > 0) ? buf[--bufp] : getchar()); } ungetch (c) /*vrat znak zpet do vstupu*/ int c; { if ( bufp > BUFSIZE) printf("ungetch: too many characters\n"); else buf [bufp++] = c; } Š.po 3 Pro vracene znaky jsme pouzili pole misto jednoho znaku pro obecnost a pozdejsi pouziti. C v i c e n i 4 - 4. Napiste funkci ungets (s), ktera vraci cely retezec zpatky do vstupu. Musi ungets neco vedet o poli buf a indexu bufp nebo staci pouzit pouze ungetch? C v i c e n i 4 - 5. Predpokladejme, ze nikdy nebude potreba vracet vice nez jeden znak. Modifikujte odpovidajicim zpusobem funkce getch a ungetch. C v i c e n i 4 - 6. Nase funkce getch a ungetch nefunguji spravne, je-li nacteno EOF. Rozhodnete, co by se melo stat, je-li EOF vraceno zpet a zduvodnete a implementujte svuj nazor. 4.6. Staticke promenne ----------------------- Staticke promenne jsou tretim druhem promennych vedle externich a automatickych promennych, se kterymi jsme se jiz seznamili. Staticke promenne mohou byt bud i n t e r n i nebo e x t e r n i. Interni staticke promenne jsou lokalni, mistni, dane funkci tak jako automaticke promenne, ale na rozdil od nich existuji trvale. To znamena, ze interni staticke promenne tvori stalou a privatni "pamet" funkce. Znakove retezce, ktere se vyskytuji uvnitr funkce jsou interni a sta- ticke /napr. argument funkce printf/. Externi staticke promenne maji platnost ve zbytku zdrojo- veho souboru, ve kterem jsou deklarovany, ale v jinych soubo- rech jiz platnost nemaji. Externi staticke promenne slouzi tedy k uschovani jmen jako buf a bufp ve funkcich getch a ungetch. Tato jmena musi byt externi, aby je bylo mozno sdilet, ale nemela by byt pristupna uzivateli funkci getch a ungetch, aby nevznikl konflikt. Jestlize jsou tyto dve funkce a tyto dve promenne prekladany soucasne v jednom souboru jako static char buf[BUFSIZE]; /*guffer pro ungetch*/ static int bufp = 0; /*dalsi volna pozice v buf*/ getch () (...) ungetch (c) (...) potom zadna dalsi funkce nemuze pracovat s promennymi buf a bufp; tato jmena tedy vlastne nebudou kolidovat se jmeny stej- nymi v jinych souborech tehoz programu. Staticka pamet, at jiz interni nebo externi, je speciali- zovana slovem s t a t i c , ktere se uvede pred normalni de- klaraci. Tyto promenne jsou externi, jsou-li definovany mimo funkce a interni, jsou-li definovany uvnitr nejake funkce. Funkce samy jsou vlastne externi objekty: jejich jmena maji obecnou platnost. Je ovsem take mozne, aby funkce byla defino- vana jako staticka. Potom ma platnost pouze v tom souboru, kde je deklarovana. "Staticke" neznamena v C pouze stalost, ale take vlastnost, ktera muze byt nazvana "privatnost". Interni staticke promenne patri jen jedne funkci, externi staticke objekty /tj. promenne nebo funkce/ maji platnost pouze ve zdrojovem souboru, kde jsou Š.po 12 definovany a jejich jmena nemaji zadnou souvislost se stejnymi jmeny v jinych souborech. E x t e r n i s t a t i c k e promenne a funkce umoz- nuji uschovavat data a staticke funkce, ktere s nimi manipuluji tak, ze nemuze dojit ke kolizi s jinymi funkcemi. Napr. getch a ungetch tvori "modul" pro vstup a navraceni znaku; promenna buf a bufp by mely byt staticke, aby nebyly dosazitalne z ven- ku. Funkce push a pop a clear vytvareji stejnym zpusobem "modul" pro operace se zasobnikem a tak promenne val a sp jsou definovany jako externi staticke. 4.7. Promenne typu registr -------------------------- Ctvrty a posledni typ promenne je nazyvan r e g i s t r . Deklarace register rika prekladaci, ze promenna bude hodne vyuzivana. Kdyz je to mozne, tak jsou tyto promenne ulozeny primo do registru pocitace, coz se muze projevit zmensenim a zrychlenim programu. Deklarace register ma nasledujici formu register int x; register char c; atd. Slovo int muze byt vynechano. Typ register muze byt pouzit pouze pro automaticke promenne a pro formalni parametry funk- ci. V druhem pripade vypada deklarace takto f (c,n) register int c,n; { register int i; ... } Ve skutecnosti pro tyto promenne plati urcita omezeni, ktera zavisi na pouzitem pocitaci. Pouze nekolik promennych ve funkci muze byt tohoto typu a rovnez plati omezeni pro typ promenne. Slovo register je ignorovano pokud bylo nesprav- ne aplikovano nebo pokud pocet techto promennych prekracuje urcitou mez. Neni mozne rovnez urcit adresu promenne typu register /o tom vice v kapitole 5/. Omezeni se meni s typem pocitace. Napr. na PDP-11 jsou brany v uvahu pouze prvni tri deklarace register. Typ promennych musi byt int, char nebo pointer. 4.8. Blokove struktury ---------------------- Jazyk C neni jazykem blokovych struktur v tom smyslu slova jako ALGOL nebo PL/1, v nemz funkce nemohou byt definovany uvnitr jinych funkci. Na druhe strane mnohou byt ale promenne definovany do blokovych struktur. Za deklaracemi promenych Š.po 3 /vcetne inicializace/ muze nasledovat leva zavorka, ktera uvadi l i b o v o l n y slozeny prikaz, ne pouze ten prikaz kterym zacina funkce. Promenne deklarovane timto zpusobem pre- kryvaji stejne nazvane promenne ve vnejsim bloku a zustavaji v platnosti az do vyskytu prave zavorky. Napr. v if (n>0) { int i; /*definice nove promenne*/ for (i = 0; i 0); /*deleni*/ while (--i >= 0) putchar (s[i]); } Druha verze pouziva rekurzi. Funkce printd pri kazdem Š.po 12 vyvolani nejprve vola sama sebe. printd (n) /* tisk n(rekurzivne)*/ int n; { int i; if (n < 0) { putchar ('-'); n = -n; } if ((i = n / 10) != 0) printd (i); putchar (n % 10 + '0'); } Kdyz funkce vola sebe sama, tak pokazde ma "cerstvou" sadu automatickych promennych, ktere jsou uplne nezavisle na pred- chozi sade. Tak pri printd (123) ma prvni printd n = 123. Ta vola druhou printd s n = 12 a potom tiskne 3. Stejnym zpu- sobem druha printd predava 1 treti printd /ta ji vytiskne/ a potom sama tiskne 2. Obecne vzato, rekurze nesetri pamet, protoze nekde musi existovat zasobnik, kam se promenne ukladaji. Navic neni ani rychlejsi. Rekurze je ale zato mnohem kompaktnejsi a umoznuje snazsi zapis a lepsi porozumeni. Rekurze je specialne vyhodna pro rekuzivne definovane struktury dat jako jsou stromy. O tom vice v kapitole 6. C v i c e n i 4. 7. Pouzijte idei z printd a napiste novou funkci itoa, tj. prevedte integer na retezec znaku pouzitim rekurze. C v i c e n i 4. 8. Napiste rekurzivni verzi funkce reverse(s), ktera obraci retezec s. 4. 11. Preprocesor jazyka C --------------------------- Jazyk C umoznuje rozsireni jazyka uzitim jednoduchych makroinstrukci. Prikaz #define je jednou z nejrozsirenejsich. Dalsi makroinstrukci je vkladani obsahu jinych souboru v pru- behu prekladu. V k l a d a n i s o u b o r u ------------------------------- Kazda radka, ktera ma nasledujici tvar #include "filename" je nahrazena obsahem souboru filename. /Uvozovky jsou povinne/. V souboru se na zacatku casto objevuji jeden dva takove radky. Je tim vkladan common, prikazy #define a deklarace extern pro globalni promenne. Vkladany soubor muze obsahovat Š.po 3 dalsi #include. Prikaz #include je doporucovan pro propojeni deklaraci velkeho programu. Zarucuje totiz, ze vsechny zdrojove soubory budou obsahovat shodne definice a deklarace promennych a tak se eliminuje moznost osklivych chyb. Zmeni-li se ovsem obsah vkladaneho souboru, vsechny soubory na nem zavisle musi byt znovu prelozeny. M a k r o i n s t r u k c e --------------------------- Definice ve tvaru #define YES 1 je tou nejjednodussi formou makroinstrukce - nahrazuje jmeno retezcem znaku. Jmena v definici #define maji stejnou formu jako identifikatory v jazyku C. Text, kterym jsou nahrazovany je libovolny. Normalne je text cely zbytek radky. Dlouhe defi- nice mohou pokracovat na dalsim radku, maji-li jako posledni znak \. "Obor pusobnosti" jmena definovanoho #define je od bodu definice do konce souboru. Jmena mohou byt definovana znovu a definice mohou pouzivat definice predchozi. Jmena nejsou nahrazovana jestlize se vyskytuji v uvozovkach. Napr. je-li YES definovane jmeno, tak v prikazu printf ("YES") nebude nahrazeno. Protoze implementace prikazu #define je makroinstrukce a neni soucasti prekladace, neni mnoho grama- tickych omezeni. Napr. vyznavaci ALGOLU mohou definovat: #define then #define begin { #define end ; } a psat if (i > 0) then begin a = 1; b = 2 end Rovnez je mozne definovat makra s argumenty, takze nahrada je zavisla na zpusobu volani. Jako priklad uvedeme makro max: #define max(A, B ) ((A) > (B) ? (A) : (B)) Potom radka x = max (p+q,r+s); bude nahrazena radkou x = ((p+q) > (r+s) ? (p+q) : (r+s) ; Je to funkce, ktera ze dvou promennych vybira vetsi. Nakladame Š.po 12 -li s argumenty konstantne, tak funkce muze mit argumenty nejruznejsiho typu. Neni nutne mit ruzne funkce max pro ruzne typy dat, jak by tomu bylo pri volani funkce. Zamyslime-li se nad funkci max nahore, uvedomime si jiste nedokonalost. Vyraz je vycislovan dvakrat. To je neprijemne pouzivame-li vedlejsi efekty jako je volani funkci a prirustko- ve operatory /++, --/. Rovnez je treba spravne pouzivat za- vorky, aby poradi vycisleni bylo spravne. /Uvazujte, co se stane, je-li makro #define square(x) x * x vyvolano takto: square(z + 1). /Existuji take ciste lexikalni problemy: nesmi byt mezera mezi nazvem makro a levou zavorkou, ktera obsahuje seznam argumentu. Prese vsechno je pouzivani makra vyhodne. Jednim z prak- tickych prikladu je standardni knihovna vstupu a vystupu, ktera bude popsana v kapitole 7, kde getchar a putchar jsou defi- novany jako makra. Dalsi moznosti jsou popsany v priloze A. C v i c e n i 4 - 9. Definujte makro swap (x, y), ktere vy- menuje sve dva argumenty. /Pomuze vam blokova struktura./