CATEGORII DOCUMENTE |
Bulgara | Ceha slovaca | Croata | Engleza | Estona | Finlandeza | Franceza |
Germana | Italiana | Letona | Lituaniana | Maghiara | Olandeza | Poloneza |
Sarba | Slovena | Spaniola | Suedeza | Turca | Ucraineana |
DOCUMENTE SIMILARE |
|
TERMENI importanti pentru acest document |
|
UČEBNICE PROGRAMOVACÍHO JAZYKA C
PŘEDMLUVA
C je univerzalni programovací jazyk, který charakterizují usporni výrazy a moderní řízení běhu struktur údajové bohatství operátoru. C není jazyk na vysoké úrovni ani není 'velky není specializovatá na pouití pouze v určité oblasti.Táto obecnost jazyka C je účine vhodnějí ß efektivnějí při mnoha úloháchjiné 'mocnějí jazyky. Autorem jazyka je Denis Ritchie, který původně koncipoval tento jazyk pro operační systém UNIX na počitači DEC PDP-11. Operačni system, prekladač jazyka C a vlastně vsechny aplikační programy systemu UNIX (i vekeré programové vybaveni připravené pro tuto knihu), jsou napsány v jazyku C. Učinné překladače existují pro dalí počítače, napr. IBM System 1370, Honeywell 6000 a Interdata 8/32. Jazyk C není svázan s určitým konstruk- čním řeením počitaču. Tato kniha je koncipovana tak, aby pomohla naučit se čtenaři programovat v jazyku C. Obsahuje učebni úvod, umonujici uziva- telum co nejrychlejsi zacatek, samostatne kapitoly tykajici se nejdulezitejsich rysu jazyka a prehled literatury. Uspech pri studiu bude zajisten zejmena ctenim, psanim a opakovanim uvede- nych prikladu, nez pouhym ucenim zakonitosti. Vsechny uvedene priklady tvori uplne, skutecne programy /nikoliv pouze casti/. Jsou spojeny s pruvodnim textem a jsou napsany ve vhodne poci- tacove forme. Krome ukazek, jak ucinne tento jazyk pouzivat, jsme se pokusili na vhodnych mistech ukazat uzitecne algoritmy a vhodny programovaci styl. Tato kniha neni uvodni programovaci priruckou, presto i no- vacek bude po prostudovani schopny se v jazyku C vyznat a orientovat, zvlaste kdyz mu pomuze zkusenejsi kolega. V nasich zkusenostech se jazyk C projevil jako prijemny, vy- razny a mnohostranny jazyk se sirokym pouzitim v ruznych pro- blemech. Snadno se uci a cim vice porostou vase zkusenosti s nim, tim lepe vam bude slouzit. Doufame, ze kniha vam pomuze uzivat C dobre a prospesne. Brian W. Kernighan
Denis M. Ritchie
KAPITOLA 0: ÚVOD
C je univerzalni programovaci jazyk. Je uzce spjat s opera- cnim systemem UNIX, protoze byl vyvinut v tomto systemu a take operacni system UNIX a jeho programove vybaveni je napsano v jazyku C. Jazyk C neni vsak vazan k urcitemu operacnimu systemu nebo urcitemu typu pocitace, prestoze je nazyvan 'systemovym programovacim jazykem' a je pouzitelny pro psani operacnich systemu. Je stejne dobre pouzitelny i pro tvoreni programu numerickeho charakteru, programu pro zpracovani textu a hroma- dnych dat. Jazyk C je jazyk relativne nizke urovne. Tento jeho rys ne- snizuje vsak jeho vyznam; je tim receno, ze C pracuje se stej- nou tridou objektu jako vetsina pocitacu tj. se znaky, cisly a adresami. To muze byt kombinovano s obvyklymi aritmetickymi a logickymi operatory implementovanymi na konkretnim poci- taci. Jazyk C nema operace zpracovavajici primo slozene objekty jako jsou retezce znaku, seznamy nebo pole uvazovane jako ce- lek. Neni zde napr. analogie s operacemi jazyka PL/1, ktere zpracovavaji cela pole znaku. Jazyk umoznuje pouze staticke definice obsazeni pameti, neni zde moznost dynamickeho obsazeni pameti a obsazeni volnych mist jako v jazyku ALGOL 68. Konecne, C nema vybaveni pro vstupni a vystupni operace. Nema prikazy READ a WRITE a primy pristup k souborum. Vsechny tyto mecha- nizmy zname z vyssich programovacich jazyku musi byt vykonany explicitne volanim funkci. Jazyk C umoznuje pouze prime jednoduche rizeni behu progra- mu: testy, cykly podprogramy. V tomto jazyce neni mozne uvazo- vat o multiprogramovani, paralelnich operacich nebo synchroni- zaci. Presto,ze nepritomnost techto moznosti muze vypadat jako vazny nedostatek /'To mi chcete rici, ze musim zavolat funkci, kdyz chci porovnat dva retezce znaku?'/, tak udrzeni jazyka na nizsi urovni prinasi opravdu znacne vyhody. Protoze jazyk C je relativne maly, muze byt popsan na male plose a je snadne se mu naucit. Prekladac jazyka C muze proto byt jednoduchy a kompaktni a muze byt snadno vytvoren. Pouzitim soucasnych pos- tupu muze tvorba prekladace pro novy pocitac trvat pouze neko- lik mesicu, protoze 80% prekladace je shodnych jiz s existuji- cimi prekladaci. To je umozneno vysokym stupnem prenositelnosti jazyka. Protoze typy dat a struktury, jake jsou v jazyku C pou- zivany, jsou zajistovany vetsinou pocitacu, tak implementace knihoven je jednoducha. Napr. na PDP 11 obsahuje pouze pod- programy pro nasobeni a deleni 32-bitovych slov a podprogramy pro volani funkci a navrat z nich. Ovsem kazda implementace pocita s kompaktni knihovnou pro vstupy a vystupy, obsluhovani retezcu a operace s pameti. Protoze jsou vsak volany pouze explicitne, mohou byt pripojeny jen kdyz je treba. Rovnez mohou byt napsany v jazyku C. Programy v jazyku C jsou dostatecne efektivni a neni treba misto nich psat programy v assembleru. Jednim z takovych prikladu je operacni system UNIX, ktery je temer cely napsan v jazyku C. Navic veskery aplikacni software sys- tému UNIX je psán v jazyku C. Prevaná větina uivatelu syste- mu UNIX (včetně autoru teto knihy) nezná assembler počitačeŐ.po 12 PDP - 11. Prestoe jazyk C pracuje na mnoha počitačich, je nezavislý na architekture daného počitače, a tak je moné s trochou peče psát 'přenositelné' programy. V naem oddělení je software, který je vyvinut pod systémem UNIX, přenaen na počitače HONEYWELL, IBM a Interdata systém. Ve skutečnosti jsou prekla- dače a prostředky jazyka C na těchto čtyrech pocitacich pod- statne kompatibilnejsi nez napr. ANSI standart FORTRAN. Pro programatory, kteri pracuji s jinymi jazyky, muze byt uzitecne pro srovnani se dozvedet o historickych, technickych a filozo- fickych aspektech jazyka C. Vetsina nejdulezitejsich myslenek jazyka C pochazi z dosti stareho, ale staleho ziveho jazyka BCPL, ktery byl vyvinut Martinem Richardsem. Vliv BCPL na C se uskutecnil neprimo jazy- kem B, ktery napsal Ken Thompson v r. 1970 pro prvni system UNIX na PDP-11. Prestoze jazyk C ma s BCPL mnoho spolecnych znaku, neni v zadnem pripade jeho kopii. Jazyky BCPL a B jsou jazyky bez 'typu'. Jediny typ dat je slovo pocitace a pristup k ostatnim druhum je pomoci specialnich operatoru nebo funkci. V jazyku C jsou zakladnimi datovymi typy znaky, cela cisla ruznych delek a cisla s pohyblivou desetinou carkou. Navic je zde hierarchie odvozenych datovych typu vytvorenych pointry, poli, strukturami, uniony a funkcemi. C umoznuje zak- ladni konstrukce pro rizeni behu, pozadovane pro dobre struk- turovane programy: shlukovani prikazu, rozhodovani /if/, cykly s testem na ukonceni nahore /while,for/ nebo dole /do/, klic vyberu /switch/. Vse jiz bylo implementovano v jazyku BCPL, ale s ponekud jinou syntaxi; BCPL dlouha leta ocekaval prichod 'strukturovaneho programovani'. Jazyk C umoznuje pouzivani pointru a aritmeticke adresy. Argumenty funkci jsou pri predani kopirovany a neni mozne, aby volana funkce zmenila hodnotu aktualniho parametru ve volajici funkci. Pokud pozadujeme 'predavani adresou', muze byt predan pointer, a volana funkce muze zmenit objekt, na ktery pointer ukazuje. Pole jsou predavana adresou pocatku pole. Funkce mohou byt volany rekurzivne a lokalni promenne jsou prevazne 'automaticke', tj. jsou vytvoreny znovu pri kaz- dem vyvolani. Definice funkci nemohou byt vkladany do sebe, ale promenne mohou byt deklarovany blokovou strukturou. Funkce jazyka C mohou byt prekladany samostatne. Promenne mohou byt interni dane funkci, externi a zname jen v jednom zdrojovem souboru a nebo uplne globalni. Interni vnitrni promenne mohou byt automaticke nebo staticke. Automaticke promenne mohou byt ukladany pro zvyseni efektivity do registru pocitace, ale prikaz register je pouze pokyn pro prekladac a ne primo pro pocitacovy registr. Jazyk C neni tak jednoznacny jazyk ve smyslu Pascalu nebo Algolu 68. Umoznuje datove verze, ale neprovadi automaticke konverze dat s velkou prehlizivosti jazyka PL/1. Dosud vytvo- rene prekladace nekontroluji pri vypoctu meze poli, typy argu- mentu atd. Pro situace, kdy je nezbytne kontrolovat typy dat, se pouzi- va specialni verze prekladace. Tento program se nazyva LINT. LINT negeneruje kod, ale misto toho prisne kontroluje vse, co je mozne kontrolovat pri behu a zavadeni programu. Nalezne Ő.po 3 nesouhlas typu, nekonzistentni pouziti argumentu, nepouzite nebo ocividne neinicializovane promenne atd. Program, ktery projde pres LINT si muze uzivat /s nekolika vyjimkami/ svobodu hlaseni chyb tak jako napr. programy v Algolu 68. O dalsich vlastnostech programu LINT se zminime, az k tomu bude prile- zitost. Jazyk C ma stejne jako ostatni jazyky svoje nedostatky. Nektere operatory maji nespravnou prioritu, nektera cast syn- taxe by mohla byt lepsi; existuje mnoho verzi jazyka, lisicich se od sebe. Nicmene se ukazalo, ze jazyk C je velice efektivni a vyrazny pro celou skalu aplikaci. Kniha je organizovana nasledujicim zpusobem: kapitola 1 tvori uvod do vyuky stredni partie jazyka C. Ucelem je zacit co nejrychleji, protoze pevne verime tomu, ze novy jazyk se nejlepe naucime, budeme-li v nem psat programy. Predpokladame zakladni znalost programovani. Neni zde vysvetlovano, co je to pocitac, prekladac ani co znamena vyraz n = n + 1. Prestoze jsme se pokouseli ukazat uzitecnou techniku programovani vsude, kde to jen bylo mozne, tak si nemyslime, ze tato kniha bude priruckou datovych struktur a algoritmu. Vzdy, kdyz jsme si mohli vybrat, jsme se soustredili na jazyk. V kapitolach 2 az 6 jsou probrany detailneji vlastnosti jazyka C. Duraz je stale kladen na tvorbu kompletnich uzitec- nych programu. Kapitola 2 pojednava o zakladnich typech dat, operatoru a vyrazu. V kapitole 3 se hovori o rizeni behu: if-else, while, for atd. Kapitola 4 popisuje funkce a struk- turu programu - externi promenne, oblast platnosti promennych atd. V kapitole 5 je diskutovano pouziti pointru a aritmetic- kych adres. Kapitola 6 obsahuje popis struktur a unionu. Kapitola 7 popisuje standardni knihovnu vstupu a vystupu, ktera zajistuje styk s operacnim systemem. Tato kniha je do- davana na vsechny typy pocitacu, kde pracuje jazyk C, takze programy mohou byt snadno prevadeny z jednoho systemu do dru- heho temer beze zmeny. KAPITOLA 1: ÚVOD K VÝUCE
Zacneme strucnym uvodem do jazyka C. Nasim cilem je ukazat zakladni prvky jazyka na skutecnych programech s tim, ze nebu- dou vynechavany detaily, formalni pravidla ani vyjimky. V tom- to okamziku se nesnazime o kompletnost. Snazime se umoznit cte- narum psat uzitecne programy tak rychle, jak jen je mozne. Soustredime se na tyto zakladni pojmy: promenne a konstanty, aritmetika, vetveni programu, funkce a zaklady vstupu a vystu- pu. Umyslne v teto kapitole vynechavame vlastnosti jazyka C, ktere jsou dulezite pro psani vetsich programu. Jedna se o pointry, struktury, vetsinu z bohateho rejstriku operatoru ja- zyka C, vetsinu prikazu pro podminene vetveni programu a dalsi podrobnosti. Tento pristup ma ovsem sve nevyhody. Zvlastnosti je to, ze kompletni popis urcite vlastnosti jazyka neni na jednom miste. Vzhledem k tomu, ze neni mozne pouzivat od zacatku vsechny moz- nosti jazyka C, priklady nejsou tak strucne a elegantni jak by mohly byt. Snazili jsme se tuto nevyhodu minimalizovat, ale presto na to ctenare upozornujeme. Dalsi nevyhodou je to, ze se budeme v nekolika clancich opa- kovat. Myslime si vsak, ze opakovani vam spise pomuze v uceni, nez ze vas bude obtezovat. Zkuseni programatori by meli byt v kazdem pripade schopni si z teto kapitoly vybrat to, co potrebuji. Zacatecnikum dopo- rucujeme, aby si studium teto kapitoly dopnili napsanim ma- lych programu, ktere jsou modifikaci programu uvedenych.
Zaciname
Jediny zpusob, jak se naucit novy programovaci jazyk, je psat programy v tomto jazyku. Prvni program, ktery napiseme, je stejny pro vsechny jazyky:
Vytiskni slova
h e l l o , w o r l d
To je zakladni prekazka; k tomu, abychom ji prekonali, musime byt schopni nekde vytvorit text programu, uspesne ho prelozit, sestavit a spustit, a potom zkontrolovat, zda vytvoril pozado- vany vystup. Vse ostatni je snadne. Program pro vytisteni textu 'hello, world' vypada v jazyku C takto: main ()
Jak nalozit s timto programem zalezi na tom, jaky system pouzivate. Napr. v operacnim systemu UNIX je treba vytvorit zdrojovy text programu do souboru, jehoz jmeno ma priponu '.c'Ő.po 3 /napr. hello.c/ prelozit ho pouzitim prikazu cc hello.c Pokud jste neco nepokazili, preklad probehne bez chyb a vytvori se soubor a.out, ktery je mozno spustit prikazem a.out Vystupem bude text
hello, world
Na jinych systemech budou ovsem platit jina pravidla a ty je treba konzultovat s mistnim odbornikem.
C v i c e n i 1-1. Spustte tento program na vasem systemu. Zkousejte vynechavat nektere jeho casti a pozorujte chybova hlaseni.
Nyni neco o programu samem. Program v jazyce C, at je jeho velikost jakakoliv, sestava vzdy z jedne nebo vice 'funkci', ktere maji byt vykonany. Funkce v jazyce C jsou obdobne fun- kcim a podprogramum v jazyce FORTRAN nebo proceduram v jazy- ce PASCAL, PL/1 atd. V nasem pripade je takovou funkci m a i n. Obycejne muzeme davat funkci libovolne nazvy, avsak nazev main je specialni nazev - vas program zahajuje svoji cinnost vzdy na zacatku funkce main. To znamena, ze kazdy program musi obsa- hovat jednotku main. Main se obvykle odvolava na ostatni fun- kce; nektere jsou primo obsazeny v programu, nektere se pouzi- vaji z knihoven jiz drive vytvorenych funkci. Jednou z metod vzajemne komunikace mezi funkcemi je preda- vani dat argumenty. Zavorky nasledujici za nazvem funkce ohra- nicuji seznam argumentu. V nasem prikladu je main funkci, ktera nema parametry. To je znazorneno symboly ( ) - prazdnym seznamem argumentu. Slozene zavorky zdruzuji prikazy, ktere vytvareji telo funkce. Jsou analogicke prikazum DO - END v ja- zyku PL/1 nebo prikazum begin - end v ALGOLU, PASCALU atd. Funkce je vyvolavana nazvem, za kterym nasleduje seznam argu- metu v kulatych zavorkach. Nepouziva se prikaz CALL jako ve FORTRANU nebo v PL/1. Zavorky musi byt uvedeny, i kdyz neob- sahuji seznam argumentu.
Programova radka
printf('hello, worldn');
je volanim funkce, ktera se jmenuje printf a jedinym argumentem je retezec znaku 'hello, worldn'. Printf je knihovni funkce, ktera zobrazuje vystup na terminal /jestlize neni specifikovano jine medium/. V tomto pripade zob- razi retezec znaku, ktery je jejim argumentem. Souvisla rada libovolneho mnozstvi znaku vlozena do uvo- zovek '.' je nazyvana znakovy retezec nebo retezcova konstanta. V teto chvili budou znakove retezce pouzivany jako argumenty funkci jako je napr. funkce p r i n t f. Dvojice znaku n v retezci je v jazyku C symbolem pro znak nove radky, ktery kdyz je nalezen, posune kurzor na levy okraj nove radky. Kdybychom znaky n neuvedli /coz mu- zeme udelat jako pokus/, uvidime, ze vystup neni ukoncen pre- sunem na novou radku. Uvedeni dvojice znaku n je jediny zpusob prechodu na novou radku. Kdyz budete zkouset neco po-Ő.po 12 dobneho jako
printf('hello, world
');
tak prekladac jazyka C bude hlasit chybu /chybejici prave uvo- zovky/. Funkce printf nikdy nevykonava presun na novou radku automa- ticky, proto pro vytvoreni pozadovaneho vystupu muze byt pouzi- to vicenasobne vyvolani funkce printf. Nas prvni program muze tedy vypadat takto
main()
Vystup bude stejny jako v predeslem prikladu. Je treba si uvedomit, ze dvojice znaku n reprezentuje pouze jeden znak; znak zmeny, (escape) oznaceny znakem , umoznuje obecny a rozsiritelny mechanizmus pro reprezenta- ci tezko zobrazitelnych nebo neviditelnych znaku. Napr. t je symbol pro tabelator, b pro zpetny posun, ' pro uvozovky a pro obracene lomitko samo.
C v i c e n i 1-2. Pokuste se zjistit co se stane, kdyz retezec znaku, ktery je argumentem funkce printf, obsahuje x, kde x je nejaky znak, ktery jsme vyse neuvedli.
Proměnné a aritmetika
Nasledujici program tiskne prevodni tabulku mezi stupni Fahrenheita a stupni Celsia za pouziti vztahu C = (5/9) . (F - 32) 0 -17,8
20 -6.7
40 4.4
60 15.6
.
260 126.7
280 137.8
300 148.9
Program vypada takto:
/*tisk prevodni tabulky Fahrenheit - Celsius
pro f=0, 20, , 300*/
int lower, upper, step;
main ();
}
Prvni dve radky programu
/*tisk prevodni tabulky Fahrenheit - Celsius
pro f = 0,20,.., 300 */
jsou komentar, ktery v tomto pripade ve strucnosti vysvetluje cinnost programu. Libovolne znaky mezi /* a */ jsou prekladacem ignorovany. Komentare mohou a maji byt pouzivany pro zprehled- neni programu. Je dovoleno je pouzivat pro zprehledneni pro- gramu vsude tam, kde se jinak muze objevit mezera nebo novy ra- dek.
V jazyku C musi byt vsechny promenne deklarovany pred prv- nim pouzitim, obvykle na zacatku funkce pred prvnim vykonnym prikazem. Kdyz zapomenete nejake promenne deklarovat, prekla- dac to bude hlasit jako chybu. Deklarace sestava z urceni typu a seznamu promennych.
int lower, upper, step;
float fahr, celsius;
Typ i n t znamená, e vechny uvedené proměnné budou celočí- selné proměnné ; f l o a t znamená, e se jedná o proměnné s pohyblivou řádovou čárkou. Přesnost a rozsah obou typů je zá- vislý na pouitém typů počítače. Napr. na počitači PDP-11 je int 16-ti bitové číslo se znamínkem a leí v rozsahu -32768 a +32767. Číslo typu f l o a t je 32 bitové, má 7 významových číslic a leí v rozsahu 10 E-38 do 10 E+38. V kapitole 2 je uveden rozsah pro ostatní vybrané typy počítaču. Dalí typy proměnných v jazyce C jsou napr.:
char znak - jeden byte
short krátke celé číslo
long dlouhe celé číslo
double číslo s pohyblivou čárkou a dvo-
jitou přesností
Rozsah techto typu zavisi na pouzitem typu pocitace. Podrob- nosti jsou uvedeny v kapitole 2. Zakladni typy jsou rovnez pole, struktury, uniony, ukazatele na ne, a funkce, ktere s ni- mi pracuji. Se vsemi temito typy se postupne v textu shledame. Skutecny vypocet v programu pro vypocet prevodni ta- bulky mezi stupni Fahrenheita a stupni Celsia zaciname prirazenim
lower = 0;
upper = 300 ;
step = 20 ;Ő.po 12 fahr = lower ;
ktere nastavi pocatecni hodnoty promennych. Jednotlive pri- kazy jsou oddeleny strednikem. Protoze kazdy radek tabulky je pocitan ze stejneho vy- razu, muzeme pouzit cyklus, ktery je definovan prikazem w h i l e :
while (fahr <= upper)
Podminka v kulatych zavorkach je vyhodnocena. Jestlize je pravdiva /tj. promenna fahr je mensi nebo rovna pro- menne upper/, telo cyklu /tj. prikazy ohranicene slo- zenymi zavorkami / je vykonano. Potom je podminka znovu vyhodnocena a jestlize je opet pravdiva, telo cy- klu je opet vykonano. Jestlize je podminka nepravdiva /fahr je vetsi nez upper/ cyklus je ukoncen. Protoze jiz nejsou v nasem programu zadne prikazy, tak je pro- gram ukoncen. Telo prikazu w h i l e muze byt tvoreno jednim ne- bo vice prikazy vlozenymi do slozenych zavorek, jako je tomu v nasem programu, nebo jednim prikazem bez sloze- nych zavorek, napr.
while (i < j)
i = 2 * i;
V obou prikladech prikazy, ktere jsou podmineny prikazem
while, zacinaji jednim tabelatorem, takze je mozne na prv- ni pohled urcit, ktere prikazy jsou uvnitr tela cyklu. 'Zubova' struktura textu programu zduraznuje logickou strukturu programu. Prestoze v jazyku C nezalezi prilis na pozici prikazu v textu, je vhodne zduraznovat logic- kou strukturu a uzivat mezery, abychom zduraznili clene- ni programu. Doporucujeme psat pouze jeden prikaz na radku a nechavat mezery okolo operatoru. Poloha zavorek neni jiz tak dulezita; my jsme si vybrali jeden z mnoha popularnich zpusobu. Vyberte si i vy svuj zpusob, ktery vam vyhovuje, a ten pak stale pouzivejte. Vetsina cinnosti naseho programu je vykonana v tele smycky while. Teplota ve stupnich Celsia je vypocitana a prirazena promenne celsius prikazem
celsius = (5.0/9.0) * (fahr - 32.0);
Duvod pro pouziti 5.0/9.0 misto jednodussiho 5/9 je ten, ze v jazyku C, stejne v jako mnoha dalsich jazycich, je vysledek celociselneho deleni o r e z a n , takze dese- tinna cast je ztracena. Proto vysledek operace 5/9 je nula a vsechny teploty by byly take nula. Desetinna tec- ka v konstante indikuje, ze se jedna o cislo s pohyb- livou radovou carkou, a proto 5.0/9.0 = 0.555, coz je to, co jsme potrebovali.Ő.po 12 Rovnez piseme 32.0 misto 32, prestoze fahr je typu float a 32 by bylo automaticky zkonvertovano na float /na 32.0/ pred odecitanim. Je to vlastne jen otazka stylu, presto je vsak lepsi psat konstanty typu float s dese- tinnou teckou i kdyz maji celou hodnotu. Je tim zduraz- nen jejich typ i pro ctenare programu a zajistuje se i stejne chapani veci prekladacem. Podrobna pravidla pro to, zda budou celociselne kon- stanty konvertovany na typ float jsou uvedeny v kapitole 2. Nyni si vsimneme, ze prirazeni
fahr = lower;
a podminka
while (fahr <= upper)
funguje tak, jak ocekavame, tj. ze promenne typu i n t jsou konvertovany na typ f l o a t pred uskutecenim operaci. Tento priklad take podrobneji ukazuje, jak pracuje funkce printf. printf je vlastne obecne pouzitelna funkce pro formatovany vystup. Podrobne bude popsana v kapitole 7. Jejim prvnim argumentem je retezec znaku, ktery ma byt zobrazen a znak % urcuje, kam maji byt dalsi argumenty /druhy, treti/ umisteny a jakym zpusobem maji byt tis- teny. Napr. v prikazu:
printf ('%4.0f %6.1fn' , fahr, celsius);
specifikace %4.0f znamena, ze cislo typu float ma byt zobrazeno s delkou ctyr znaku a nema mit desetinnou cast. %6.1f urcuje, ze dalsi cislo bude v delce 6 znaku s jednou cislici za desetinnou teckou: tato specifikace je obdobou formatove specifikace f6.1 ve FORTRANU nebo specifikace f(6,1) v jazyku PL/1. Nektere casti specifikace nemusi byt uvedeny, napr. %6f urcuje, ze cislo ma byt alespon 6 znaku dlouhe; %.2f pozaduje dve mista za desetinnou teckou a cel- kovy pocet znaku neni omezen: %f specifikuje pouze tisk cisla typu float. Printf rovnez rozpoznava %d pro desetinne cele cis- lo, %o pro oktalovou reprezentaci, %x pro hexadecimalni repre- zentaci, %c pro znak, %s pro retezec znaku a %% pro % samo. Kazdy znak % v prvnim argumentu funkce printf je spojen jen s odpovidajici druhym, tretim, argumentem. Specifikace musi byt presne popsana cisly a typem, jinak dava program nes- myslne vysledky. Mimochodem funkce p r i n t f neni casti jazyka C. Sam ja- zyk C nema definovane operace vstupu nebo vystupu. Funkce printf neni obestrena nejakymi kouzly, je to jen uzitecna funk- ce, ktera je casti standartni knihovny funkci jazyka C. Abychom se mohli soustredit pouze na jazyk C nebudeme az do kapitoly 7 mnoho uvadet o vstupne-vystupnich operacich. Odlozime take do te doby pojednani o formatovanem vstupu. Kdyz budete chtit za- davat cisla jako vstup, prectete si o funkci s c a n f v kap. 7 odstavec 7.4. Scanf je funkce obdobna jako printf, az nato, ze cte vstup misto psani vystupu.
C v i c e n i 1-3. Upravte program pro konverzi teplot tak, aby vytiskl zahlavi tabulky.
C v i c e n i 1-4. Napiste program, ktery bude pocitat stupne Ő.po 12
Celsia v zavislosti na stupnich Fahrenheita.
Příkaz for
Jak je mozno ocekavat, je mnoho zpusobu, jak napsat program; uvedeme jinou variantu naseho programu pro konverzi teplot
main () /* tabulka prevodu Fahrenheit-Celsius/
Vysledek bude stejny, ale program vypada jinak. Jednou z hlav- nich zmen je zmenseni poctu promennych. Jedina promenna, ktera zustala, je celociselna promenna f a h r /celociselna proto, abychom mohli ukazat, jak pracuje konverze %d v printf/. Dolni a horni mez a hodnota kroku step se objevuji jenom jako kons- tanty prikazu for, ktery je pro nas novinkou. Vyraz pro vypocet prevodu se nyni vyskytuje na miste tretiho argumentu funkce Posledni zmena je prikladem obecneho pravidla jazyka C; v kteremkoliv miste, kde se muze vyskytnout promenna, je mozne pouzit vyraz stejneho typu. Protoze treti argument funkce printf ma byt typu float, aby jej bylo mozno zobrazit konverzi %6.1f, tak na jeho pozici se muze pouzit vyraz v pohyblive radove carce. Prikaz f o r je podmineny prikaz, ktery je zobecnenim prika- zu w h i l e. Jestlize jej porovname s prikazem while, tak jeho cinnost by nam mela byt jasna. Sestava ze tri casti, ktere jsou od sebe oddeleny stredniky.
Prvni cast, iniciace
fahr = 0;
je provedena jednou na zacatku. Druha cast je podminka, ktera ridi cyklus
fahr <= 300;
Podminka je vyhodnocena; jestlize je pravdiva, tak prikazy tela cyklu for jsou vykonany /v nasem pripade je to pouze jedno vy- volani funkce printf/. Potom je vykonana treti cast prikazu for, reinicializace,
fahr = fahr + 20
a znovu je vyhodnocena podminka v druhe casti prikazu for. Cy- klus je ukoncen, jestlize je podminka nepravdiva. Stejne jako u prikazu while muze telo cyklu tvorit jeden nebo skupina pri- kazu, ktere jsou uvnitr slozenych zavorek. Inicializace a reinicializacce mohou byt jednoduche vyrazy. Je lhostejne, pouzijeme-li v programu prikaz for nebo while, ale vzdy se snazime volit tu variantu, ktera vypada jasneji. Prikaz for je vhodny obycejne v takovych prikladech, kdy inicializace a reinicializace jsou jednoduche, logicky svazane prikazy. Prikaz for je tomto pripade kompaktnejsi, protoze prikazy pro rizeni cyklu jsou zde pohromade. Ő.po 3
C v i c e n i 1-5. Modifikujte program pro konverzi teplot
tak aby, tiskl tabulku v opacnem poradi tj. od 300 stup.F
do 0 stup. F.
Symbolické konstanty
Predtim, nez opustime nas program pro konverzi teplot, ucin- me jeste zaverecnou poznamku. Je spatne 'pohrbivat magicka cis- la' jako jsou cisla 300 a 20 v programu, protoze neposkytuji tem, kteri budou program cist nebo upravovat, systematickou in- formaci. Nastesti existuje v jazyku C zpusob, jak se temto 'magickym cislum' v programu vyhnout. Na zacatku programu je totiz mozne popisem # d e f i n e definovat retezce znaku jako symbolicka jmena nebo symbolicke konstanty. Potom prekladac nahradi tato jmena odpovidajicimi retezci znaku vsude tam, kde se objevi. Nahradou muze byt skutecne libovol- ny text, nejen cisla.
#define LOWER 0 /*dolni mez tabulky*/
#define UPPER 300 /*horni mez*/
#define STEP 20 /*hodnota kroku*/
main () /*tabulka prevodu Fahrenheit - Celsius/
Polozky LOWER,UPPER,STEP jsou konstanty a proto se neobjevuji
v deklaracich. Symbolicka jmena je vhodne psat velkymi pisme- ny, aby je bylo mozno jednoduse odlisit od nazvu promennych, ktera jsou psana pismeny malymi. Uvedomme si, ze na konci popisu #define neni strednik, protoze vse, co se objevi za symbolickym jmenem je za nej v programu dosazovano.
Výběr uitečných programů
Uvazujeme nyni o skupine programu, ktere budou vykonavat jednoduche operace se znaky. Zjistime potom, ze mnohe z pro- gramu jsou jen rozsirenim techto zakladnich programu.
Vstup a vystup znaku
Standardni knihovna obsahuje funkce pro cteni a vypsani znaku. Funkce g e t c h a r nacita vzdy dalsi znak pokazde, kdyz je vyvolana a jako hodnotu vraci tento znak. Takze po prikazu
c = getchar()
obsahuje promenna c dalsi znak ze vstupu. Znaky jsou obvykle zadavany z klavesnice, ale to nas az do 7. kapitoly nemusi zajimat.Ő.po 12
Funkce p u t c h a r je doplkem funkce getchar
putchar (c)
Tato funkce vytiskne obsah promenne c na nejake medium-obycejne na obrazovku. Volani funkce putchar a printf muzeme kombinovat; vystup se objevi v tomto poradi, jak byly funkce volany. Stejne jako v pripade funkce printf neni na funkcich getchar a putchar nic magickeho. Nejsou soucasti jazyka C, ale jsou z neho dosa- zitelne.
Kopirovani souboru
Znate-li funkce getchar a putchar, muzete napsat mnoho uzi- tecnych programu, aniz budete vedet neco dalsiho o operacich vstupu a vystupu. Nejjednodussim prikladem je program, ktery kopiruje vstup do vystupu po jednom znaku. Vyvojove schema vypada takto:
precti znak
while (znak neni symbol pro konec souboru)
vypis znak
precti dalsi znak
Program v jazyku C bude vypadat takto:
main () /*kopirovani vstupu na vystup: 1.verze*/
}
Operator != znamena 'nerovna se'. Hlavnim problemem je zjistit, byl-li nacten konec vstupu. Obvykle je dano konvenci, ze kdyz funkce getchar narazi na ko- nec vstupu, tak vraci hodnotu, ktera neni normalnim platnym znakem. Jediny, ale zanedbatelny problem je to, ze existuji dve konvence pro indikaci konce souboru. My jsme se teto neprijem- nosti vyhnuli tim, ze pouzivame symbolicke jmeno EOF pro hod- notu 'konec souboru', at uz je jakakoliv. V praxi je EOF bud rovna -1 nebo 0, a proto musi byt na zacatku programu definice
#define EOF -1
nebo
#define EOF 0
Pouzitim symbolickeho jmena EOF, ktere reprezentuje hodnotu funkce getchar pro nacteni konce vstupu, jsme si zajistili, ze jen jedina radka v programu zavisi na konkretni ciselne hodnote. Soucasne musime deklarovat promenou c typu int, ne c h a r, aby mohla obsahovat hodnotu jakou funkce getchar vraci. Jak potom uvidime v kapitole 2, tato funkce je skutecne typu int, protoze musi byt schopna vracet nejen znaky, ale take repre- zentaci symbolu EOF.Ő.po 3
Program pro kopirovani muze byt zkusenejsimi programatory v jazyku C napsan strucneji. V tomto jazyku muze byt kazdy pri- kaz, jako napr.
c = getchar()
pouzit ve vyrazu, jehoz hodnota je proste rovna hodnote, ktera je prirazovana leve strane vyrazu. Jestlize je prirazeni znaku promenne c vlozeno na pozici podminky v prikazu while, tak program pro kopirovani souboru muze byt napsan takto:
main /* kopirovani vstupu ve vystup 2.verze */
Program precte znak, priradi ho promenne c a testuje, zda tento znak byl priznakem konce souboru. Jestlize nebyl, tak telo cyklu while je vykonano. Testovani se znovu opakuje. Kdyz se narazi na konec souboru, prikaz while, stejne jako funkce main, ukonci cinnost. Tato verze programu soustreduje vstup na jedno misto - nyni je uz jen jedno volani funkce getchar - a zkracuje text programu. Vlozeni prirazovaciho prikazu do testovaciho vyra- zu je jednou z moznosti, kdy jazyk C umoznuje vyznamne zkrace- ni textu programu. /Je mozne se o tuto moznost nestarat a a vytvaret 'nevnorujici se' text programu, ale tomu se budeme snazit vyhybat./
Je dulezite si uvedomit, ze vlozeni prirazovaciho prikazu do zavorek je opravdu nezbytne. Priorita operatoru != je vyssi nez prirazeni = , z cehoz vyplyva, ze pri nepritomnosti zavo- rek bude prikaz != vykonan drive nez prirazeni =. Proto prikaz
c = getchar() != EOF
je shodny s prikazem
c = (getchar() != EOF)
To ma za nasledek to, ze promenne c bude prirazena hodnota 0 nebo 1 podle toho, zda funkce getchar narazila na konec sou- boru nebo ne /vice o teto problematice bude uvedeno v kapito- le 2/.
Pocitani znaku
Nasledujici program pocita znaky. Je to uprava programu pro kopirovani.
main() /*pocitani znaku na vstupu*/
Ő.po 12
Prikazem ++nc
uvadime novy operator, ++, ktery provadi zvetseni o jednicku. Muzeme ovsem rovnez napsat nc = nc + 1, ale ++nc je strucnejsi a mnohem efektivnejsi z vypocetniho hlediska. --je obdobny ope- rator pro odecitani jednicky. Operatory ++ a -- se mohou obje- vit bud pred promennou /++nc/ nebo za ni /nc++/. Tyto dva tvary maji ve vyrazech ruzny vyznam, jak uvidime v kap. 2, ale jak ++nc tak i nc++ zvetsuje promennou nc o jednicku. V teto chvili budeme psat tyto operatory pred promennymi. Program pro pocitani znaku scita znaky v promenne nc, ktera je typu l o n g i n t /promenna s dvojnasobnym poctem by- tu/. Maximalni mozna hodnota, kterou muze nabyt promenna typu int je na pocitaci PDP-11 32767 a muze se proto snadno pri po- citani znaku preplnit; na pocitacich IBM jsou promenne typu long a int identicke a mnohem vetsi. Vystupni konverze %1d umoznuje tisk promennych deklarovanych jako long int. Chceme-li pocitat s mnohem vetsimi cisly, muzeme pouzit promenne typu d o u b le /promennych s pohyblivou carkou, ktere maji proti promennym typu float dvojnasobnou delku/. Take pouzijeme prikaz for misto prikazu while, abychom ukaza- li jiny zpusob tvorby cyklu.
main() /*pocitani znaku na vstupu*/
Specifikace %f ve funkci printf je pouzivana jak pro promenne
typu float, tak i typu double. %.0f potlacuje tisk neexistujici desetinne casti cisla nc.
V tomto programu je telo cyklu for prazdne, protoze veskera cinnost je soustredena na testovani a reinicializaci. Ale gra- maticka pravidla jazyka C vyzaduji, aby telo cyklu for existo- valo. Izolovany strednik zde predstavuje prazdny prikaz, a tak je pravidlo splneno. V programu jej piseme na samostatnou rad- ku, abychom si ho snadneji vsimli. Predtim, nez program pro pocitani znaku opustime, vsimneme si, ze kdyz vstup neobsahuje zadne znaky, tak je podminka cyk- lu while nebo for nepravdiva hned pri prvnim vyhodnoceni a promenna nc je rovna nule, coz je spravny vysledek. Jednou z prijemnych vlastnosti prikazu while a for je to, ze podminka je testovana na zacatku cyklu, predtim, nez je vykonano telo cyklu /na rozdil od jazyka FORTRAN - pozn.prekl./. Program pocita spravne, i kdyz vstup nenabizi 'zadne znaky'.
Pocitani radek
Nasledujici program pocita radky na vstupu. Predpokladame, ze radky jsou od sebe oddeleny znakem n.
main() /*pocitani radek*/Ő.po 3
Telo cyklu while nyni obsahuje prikaz if, ktery ridi prirustek ++nl. Prikaz i f testuje podminku v zavorkach a jestlize je pravdiva, tak vykona prikaz /nebo skupinu prikazu/, ktere nasleduji. Znovu muzeme snadno ukazat, co je cim rizeno. Dvojnasobny znak == je v jazyku C zapis podminky 'je rovno' /jako .EQ. ve FORTRANU/. Dvojity symbol je pouzit proto, aby jej bylo mozno odlisit od prirazovaciho symbolu =. Protoze se prirazovaci prikaz v typickych programech jazyka C pouziva zhruba dvakrat casteji nez relacni operator pro rovnost, je logicke, ze ma polovicni pocet znaku /na rozdil od ALGOLU nebo PASCALU/. Je-li nektery znak psan mezi apostrofy, vysledkem je cisel- na hodnota tohoto znaku. To se nazyva znakovou konstantou. Tak napr. 'A' je znakova konstanta; v ASCII je jeji hodnota 65, coz je vlastne vnitrni reprezentace znaku A. Vzdy ale davame pred- nost psani 'A' pred 65: vyznam je jasnejsi a nezalezi na mistni reprezentaci znaku. Znaky uvedene za obracenym lomitkem jsou rovnez dovolene znakove konstanty a mohou se vyskytnout v podminkach i arimetickych vyrazech. 'n' se pouziva misto hodnoty znaku pro novou radku. Uvedomte si, ze 'n' je jeden znak a ve vyra- zu je roven cislu typu int a na druhe strane, 'n' je rete- zec znaku, ktery obsahuje jeden znak. Tato problematika je podrobne popsana v kap. 2.
C v i c e n i 1-6. Napiste program, ktery pocita mezery, tabe-
latory a znaky pro novou radku.
C v i c e n i 1-7. Napiste program, ktery kopiruje text ze
vstupu na vystup a nahrazuje jednu nebo vice mezer vzdy jen jednou mezerou.
C v i c e n i 1-8. Napiste program,vktery nahrazuje kazdy ta- belator radou tri znaku >, zpetny znak, -, ktere tiskne jako -> a kazdy zpetny znak obdobnou radou <-. To nam zviditelni znaky pro tabelator a backspace.
Pocitani slov
Ctvrty ze serie uzitecnych programu pocita radky, slova a znaky. Slovo je zde definovano jako skupina znaku, ktera neob- sahuje mezeru, tabelator nebo znak pro novou radku. /Tento pro- gram je kostrou obsluzniho programu wc v systemu UNIX./
#define YES 1
#define NO 0
main() /*zjisteni poctu radek, slov, znaku
ze vstupu*/
}
printf( '%d %d %dn', nl, nw, nc);
}
Pokazde, kdyz program narazi na prvni znak slova, zapocita slo- vo. Promenna inword indikuje, zda je program prave uprostred slova nebo ne; na zacatku 'neni uprostred slova', coz je vyja- dreno hodnotou NO. Davame prednost symbolickym konstantam YES a NO pred hodnotami 1 a 0, protoze je program srozumitelnejsi. Ovsem v tak malem programu, jako je nas, to prinasi spise po- tize, ale potom ve vetsich programech se nam toto usili, jez od zacatku vynakladame, bohate vrati. Poznate take, ze lze snadne- ji delat rozsahle zmeny v programu, kde se cisla vyskytuji pou- ze jako symbolicke konstanty.
Radka
nl = nw = nc = 0;
vynuluje vsechny promenne. Neni to specialni pripad, ale vysle- dek toho, ze prirazovani probiha zprava doleva. Je to jako bychom napsali
nc = ( nl = ( nw = 0 ));
Operator || znamena nebo a tak radka
if (c == ' ' || c == 'n' || c == 't')
znamena: 'jestlize c je mezera, nebo c je znak pro novy radek nebo c je tabelator'. && je obdobny operator pro logicky soucin. Vyrazy obsahujici operatory || nebo && jsou vyhodnoco- vany odleva doprava a je zajisteno, ze vyhodnocovani se ukonci tehdy, jestlize je uz znamo, ze vyraz je pravdivy nebo neprav- divy. Proto obsahuje-li promenna c mezeru, neni treba dale tes- tovat zda obsahuje tabelator, ci znak pro novou radku. V kratkem programu to neni dulezite, ale na vyznamu to nabyva v komplikovanejsich situacich, jak brzy uvidime.
Nas program take ukazuje cinnost prikazu e l s e, ktery urcuje alternativni cinnost, ktera ma byt konana, kdyz podmin- ka prikazu if neni splnena. Obecny tvar je
if (podminka)
prikaz-1
else
prikaz-2
Za teto situace bude vykonan prave jeden ze dvou prikazu spoje-Ő.po 3
nych s prikazem if. Jestlize je podminka pravdiva, bude vykonan prikaz-1; jestlize ne, bude vykonan prikaz-2. Kazdy prikaz muze byt ve skutecnosti slozen zase z prikazu. V programu pro pocitani slov je za else dalsi prikaz if, ktery podminuje dal- si dva prikazy ve slozenych zavorkach.
C v i c e n i 1-9. Jak budete testovat program pro pocitani
slov. Jaka jsou omezeni?
C v i c e n i 1-10. Napiste program, ktery tiskne slova ze vstupu vzdy na novou radku.
C v i c e n i 1-11. Upravte definici slova v programu pro pocitani slov. Napr. slovo je posloupnosti pismen, cislic a apostrofu, ktera zacinaji pismenem.
Pole
Napisme program, ktery bude zjistovat pocet vyskytu kazdeho z cisel, oddelovacu /tj. mezer tabelatoru a znaku nove radky/ a vsech ostatnich znaku. Je to umele vykonstruovany priklad, ale umozni nam ilustrovat ruzne vlastnosti jazyka C v jednom programu. Na vstupu se muze objevit dvanact druhu znaku, a proto spi- se nez jednotlive promenne pouzijeme s vyhodou pole, ktere bude obsahovat pocet vyskytu kazdeho cisla. Zde je jedna z verzi programu:
main() /*pocitani cisel, oddelovacu, ostatnich*/
Popis
int ndigit[10];
deklaruje pole ndigit o delce 10 celociselnych promenych. Inde- xy pole zacinaji v jazyku C vzdy s nulou /oproti FORTRANU nebo PL/1, kde pole zacinaji indexem 1/, a tak prvky pole jsou ndigit [0], ndigit [1] ., ndigit [9]. To se projevuje v cyk-Ő.po 12
lu for, ktery inicializuje a tiskne pole ndigit.
Indexem pole muze byt libovolny celociselny vyraz, ve kterem mohou byt jen celociselne promenne nebo celociselne konstanty. Tento zvlastni program se spoleha na znakovou reprezentaci ci- sel. Napr. podminka
if(c >= '0' && c <= '9')
urcuje, zda je znak c cislici. Jestlize cislici je, tak nume- ricka hodnota teto cislice je
c - '0'
Tento postup funguje jen tehdy, kdyz '0', '1', jsou kladna cisla usporadana vzestupne a jestlize mezi '0' a '9' jsou jen cislice. Nastesti je to splneno pro vetsinu konvencnich soubo- ru znaku. V aritmetickych operacich, kde se vyskytuji promenne typu char a int, jsou pred vypoctem promenne typu char preve- deny na celociselne promenne a jsou tedy shodne co do obsahu s promennymi typu int. Je to vyhodne a zcela prirozene; napr. c - '0' je celociselny vyraz, jehoz hodnota lezi mezi 0 a 9 v odpovidajicim poradi jako znaky '0' a '9' a je tedy platnym indexem pole ndigit.
Rozhodnuti, zda je znak cislici, oddelovacem nebo necim jinym je dano posloupnosti
if (c >= '0' && c <= '9')
++ndigit [c-'0'];
else if (c == ' ' || c == 'n' || c == 't')
++nwhite;
else
++nother;
Konstrukce
if (podminka)
prikaz
else if (podminka)
prikaz
else
prikaz
se casto v programech objevuje jako zpusob vicenasobneho rozho- dovani. Prikazy jsou jedoduse vykonavany odshora dolu. Kdyz je nektera podminka splnena, tak je odpovidajici prikaz vykonan a sekvence prikazu je ukoncena. Prikazem muze byt samozrejme ne- kolik prikazu ve slozenych zavorkach. Jestlize neni zadna z po- dminek splnena, prikaz nasledujici posledni prikaz else je vy- konan, pokud je ovsem uveden. Jestlize je zaverecny prikaz else a prikaz vynechan /jako je tomu v nasem programu pocitani slov/, neprovede se nic. V teto konstrukci muze byt mezi prv- nim if a poslednim else libovolny pocet skupin
else if (podminka)
prikaz
Je moudrejsi formovat tyto konstrukce tak, jak jsme ukazali,Ő.po 3
aby se dlouhy rozhodovaci prikaz prilis neblizil prave stra- ne papiru /pouzivame-li zvyraznene logicke struktury odsazo- vanim prikazu/.
Prikaz s w i t c h, ktery bude popsan va kapitole 3, umoznuje dalsi zpusob mnohonasobneho rozhodovani. Je vhodny pro zjistovani, ze ktereho souboru je dane cislo nebo znakovy vyraz.
C v i c e n i 1-12. Napiste program, ktery bude tisknou histo- gram delek nactenych slov. Nejsnadnejsi je nakreslit histogram horizontalne, vertikalni usporadani je slozitejsi.
Funkce
Funkce jazyka C jsou obdobou podprogramu a funkci jazyka FORTRAN a procedur v PL/1, PASCALU atd. Funkce umoznuje vhodnym zpusobem soustredit urcite vypocty do 'cerne skrinky', kterou muzeme pouzit, aniz se starame o to, co je uvnitr. Uziti funkci je jedinym zpusobem, jak preklenout slozitost velkeho programu. Mame-li spravne nadefinovane funkce, tak potom nas nemusi zaji- mat, jak jsou udelany; staci vedet co delaji. Jazyk C je navr- zen tak, aby pouzivani funkci bylo snadne, vyhodne a ucinne. Casto se setkame s funkci, ktera ma jen nekolik radek, a je jen jednou volana. Je pouzita proto, ze zprehlednuje program. Zatim jsme pouzivali funkce printf, getchar a putchar, kte- re jiz drive nekdo vytvoril; nyni je cas napsat nekolik vlast- nich funkci. Protoze v jazyku C vubec neni operator mocneni /jako je ** ve FORTRANU ne PL/1/, vysvetleme si zpusob vy- tvoreni funkce a napisme funkci p o w e r (m,n), ktera umoc- nuje cele cislo m na cele cislo . Napr. hodnota power(2,5) je 32. Tato funkce nenahrazuje zcela operator **, protoze pracu- je pouze s malymi celymi cisly, ale je vhodne zacinat jednodu- chou ukazkou. Nasleduje funkce power a hlavni program main, ktery ji pro- veri, takze je mozne videt celou strukturu programu najednou.
main() /*testovani funkce power*/
power(x, n) /*funkce power*
int x, n;
Ő.po 12
Vsechny funkce maji stejnou strukturu:
jmeno (seznam argumentu, pokud jsou nejake)
deklarace argumentu, pokud jsou nejake;
Funkce se mohou umistit v textu programu libovolne, a to jak v jednom nebo ve dvou souborech. Ovsem kdyz je text pro- gramu ve dvou souborech, je treba zmenit prikazy pro kompila- ci a sestaveni, ale to je jiz zalezitost operacniho systemu a ne jazyka C. Pro zacatek budeme predpokladat, ze obe funkce jsou v jednom souboru, takze plati vse, co jste se zatim o spousteni programu v jazyku C naucili.
Funkce power je vyvolana v jednom radku dvakrat
printf ('%d %d %dn', i, power (2,i), power (-3,i));
Pri kazdem vyvolani funkce power jsou ji predavany dva argu- menty a hodnotou funkce je cele cislo a to je zformatovano a vytisteno. Ve vyrazu je power (2,i) cele cislo zrovna tak, jako 2 a i /ne vsechny funkce vraci cele cislo - blize o tom v kapitole 4/.
Ve funkci power je treba argumenty nadeklarovat.
To je provedeno v radce
int x, n;
ktera nasleduje za radkou se jmenem funkce. Deklarace argumen-
tu je umistnena mezi seznam argumentu a prvni slozenou zavor- ku; kazda deklarace je zakoncena strednikem. Jmena, ktera se pro argumenty pouzivaji ve funkci power jsou ciste mistni a ostatni funkce k nim nemaji pristup. To zna- mena, ze ostatni funkce mohou pouzivat stejna jmena pro jine promenne. To plati i pro promenne i a p: promenna i ve funkci power nema zadny vztah promenne i v hlavnim programu main. Hodnota funkce power je vracena programu main prikazem return stejnym zpusobem jako v jazyku PL/1. V zavorkach se zde muze objevit libovolny vyraz. Funkce nemusi ale vracet hodnotu; samotny prikaz return predava pouze rizeni volajici jednotce a nepredava hodnotu.
C v i c e n i 1-13. Napiste program, ktery prevadi pismena ze vstupu na mala pismena a pouziva pri tom funkci lower. Funkce lower vraci c, kdyz c neni pismeno, a hodnota maleho pisme- na c, jestlize je c pismeno.
Argumenty - volaní hodnotou
Jedna z vlastnosti jazyka C muze byt nekterym programatorum, kteri pouzivaji FORTRAN a PL/1 atd., neznama. V jazyce C jsou vsechny argumenty /vyjma poli/ predavany 'hodnotou'. To zname- na, ze volana funkce pracuje s docasnymi promennymi, ktere jsouŐ.po 3 kopiemi skutecych argumentu /ve skutecnosti jsou v zasobniku/. Tento zpusob je odlisny oproti FORTRANU a PL/1, kde jsou argu- menty predavany adresou a nikoli hodnotou. Hlavni rozdil je v tom, ze funkce v jazyku C nemuze menit hodnoty promennych volajici funkce; muze pouze menit hodnoty vlastnich, docasnych promennych. Volani hodnotou je kladnou strankou a nikoliv omezenim. Obvykle je mozne vytvaret kompak- tnejsi programy s mensim mnozstvim pridavnych promennych,pro- toze s argumenty je mozno ve volani funkci nakladat jako s bez- nymi mistnimi promennymi. Jako priklad uvedeme variantu funkce power, ktera tohoto faktu vyuziva.
power(x, n) /*umocneni x na n; n>0; verze 2*/
int x, n;
Argument n je pouzit jako docasna promena a je zmensovan az do nuly. Jiz nemusime pouzivat promennou i. Je lhostejne jake hod- noty promenna n ve funkci power nabude - nebude to mit zadny vliv na argument, s nimz byla funkce power volana. Kdyz je potreba, tak funkce muze menit hodnoty promenych ve volajici jednotce. Volajici funkce musi predat adresu pro- menne /coz se nazyva ukazatel, pointer na promennou/ a volana funkce musi tento argument deklarovt jako ukazatel a odkazovat se na promennou timto ukazatelem. Tato problematika bude diskutovana v kapitole 5.
Je-li jako argument uvedeno jmeno pole, tak predavana hod- nota je ve skutecnosti pozice nebo adresa zacatku tohoto po- le. /Prvky pole se nekopiruji!/ Funkce ma tedy pristup ke kteremukoliv prvku pole. O tomto budeme hovorit v nasledu- jicim odstavci.
Znaková pole
Pravdepodobne nejrozsirenejsimi typy poli v jazyku C jsou znakova pole. Abychom si ilustrovali pouziti techto poli a cinnost funkci, ktere s nimi pracuji, napiseme program, ktery nacita radky a vytiskne tu nejdelsi. Hruby diagram je velmi jednoduchy:
while /existuje dalsi radka/
if(je delsi nez predchozi nejdelsi)
uchovej ji a jeji delku
vytiskni nejdelsi radku
Tento diagram objasnuje cinnost programu a umoznuje nam rozde-
lit ho na jednotlive casti. Jedna cast nacita radku, druha cast ji testuje, dalsi uchovava a zbytek ridi cinnost.Ő.po 12 Protoze je diagram uplne rozdelen, bude treba napsat program stejnym zpusobem. Napisme tedy nejprve funkci getline, ktera nacita ze vstupu dalsi radku; getline je zobecneni funkce getchar. Aby byla funkce getline uzitecna i v dalsich progra- mech, napisme ji tak univerzalne, jak jen to pujde. Prinejmen- sim musi funkce getline signalizovat konec souboru; dalsim zobecnenim bude, aby jeji hodnota byla rovna delce nactene radky, nebo nule, byl-li nacten konec souboru. Kazda radka ma minimalne jeden znak a tak nula neni platna hodnota pro del- ku radky ani tehdy, obsahuje-li radka jen znak pro novou radku /takova radka ma delku 1/. Kdyz narazime na radku, ktera je delsi nez predchazejici nejdelsi radka, tak ji musime nekde uchovat. To bude vykona- vat druha funkce, c o p y, ktera bude nejdelsi radku kopi- rovat na bezpecne misto. A na zaver potrebujeme hlavni program, ktery bude cinnost funkci getline a copy ridit. Zde je vysle- dek:
#define MAXLINE 1000 /*max. velikost vstupu radky*/
main() /*nalezeni nejdelsi radky*/
if (max>0) /*byla nactena radka/*
printf('%s', save);
}
getline (s, lim) /*nacti radku do s, vrat delku/*
char s [];
int lim;
s [i] = '0';
return (i);
}
copy(s1, s2) /*kopiruje sl do s2*/
char s1[], s2[];Ő.po 3
Funkce main a getline komunikuji pomoci dvojice argumentu a vracene hodoty. V getline jsou argumenty deklarovany na radkach
char s[];
int lim;
ktere specifikuji prvni argumet jako pole a druhy jako celo- ciselnou promennou. Delka pole s neni specifikovana ve funkci getline, protoze je urcena v hlavnim programu main. getline vraci hodnotu pomoci prikazu return volajici jednotce stejnym zpusobem jako funkce power. Nektere funkce vraceji uzitecne hodnoty, jine, jako napr. copy, jsou uzivany pouze pro to, co delaji a hodnotu nevraceji.
getline vklada znak 0
/znak, jehoz hodnota je nula/ na konec pole, ktere vytvari a oznacuje tim konec tohoto znakoveho pole. Stejnou konvenci pou- ziva prekladac jazyka C; napiseme-li nejaky znakovy retezec jako
'hello n '
prekladac vytvori znakove pole, ktere obsahuje znaky retezce a ukoci jej znakem 0, takze fukce jako je napr. printf muze zjistit konec retezce
h e l l o n 0
Formatovana specifikace %s v printf ocekava retezec znaku v tomto tvaru. Kdyz si prostudujete funkci copy, tak objevite, ze se rovnez opira o fakt, ze vstupni retezec je ukoncen znakem 0. Znak 0 se rovnez zkopiruje do vystupniho argu- mentu s2. /Predpoklada se, ze retezec sam neobsahuje znak 0./ Stoji za zminku, ze i tak maly program jako je tento pri- nasi obtize. Napr. co by mel program main delat, kdyz narazi na radku, ktera je delsi nez zvolene maximum? Funkce getline pracuje spravne. Prestane cist znakym je-li pole plne i kdyz nebyl nacten znak nove radky. Kontrolou delky radky a testo- vanim posledniho znaku muze main urcit, zda radka nebyla pri- liz dlouha a zachovat se podle toho. Pro jednoduchost jsme se timto problemem nezabyvali. Neexistuje zpusob, jak se pri pouzivani funkce getline doz- vedet dopredu, jak budou vstupni radky dlouhe, a proto funkce getline kontroluje preplneni pole. na druhe strane uzivatel funkce getline vi /nebo muze zjistit/ jak dlouhe jsou retezce, a tak jsme kontrolu delky do programu nedavali.
C v i c e n i 1-14. Upravte hlavni program main v programu pro tisk nejdelsi radky tak, aby spravne vytiskl delku libovolneŐ.po 12 dlouhe vstupni radky a co nejvice jejiho obsahu.
C v i c e n i 1-15. Napiste program, ktery bude tisknout vsechny radky delsi nez 80 znaku.
C v i c e n i 1-16. Napiste program, ktery vyjme uvodni meze-
ry a tabelatory z kazde radky na vstupu a vymazte prazdne radky.
C v i c e n i 1-17. Napiste funkci reverse, ktera prevraci znakovy retezec s. Pouzijte ji v programu, ktery prevraci vstupni radky.
1.10 Externí proměnné
Promenne v programu main /line, save, atd./ jsou soukrome, mistni jednotky main, protoze jsou v jednotce main deklaro- vany a zadna jina funkce k nim primo nema pristup. Totez pla- ti pro promenne v ostatni funkcich: napr. promenna i ve funkci getline nema vztah k promenne i ve funkci copy. Kazda mistni promenna funkce je aktivovana v okamziku vyvolani teto funkce, a zmizi, kdyz je cinnost teto funkce ukoncena. Je to proto, ze tyto promene jsou nazyvany jako v ostatnich jazycich auto- maticke promenne. Od nynejska budeme pouzivat termin auto- maticka promenna pro tyto dynamicke mistni promenne. /V kapi- tole 4 pozname staticke oblasti pameti, do`kterych mistni promenne ukladaji sve hodnoty mezi vyvolanim funkce./ Protoze automaticke promenne prichazeji a odchazeji spolu s aktivovanim funkci, neuchovavaji sve hodnoty od jednoho vyvola- ni do druheho a musi byt pri kazdem vyvolani explicitne nasta- veny. Jestlize nastaveny nebudou, budou nedefinovane a budou obsahovat nahodna cisla. Na rozdil od automatickych promennych je mozne definovat externi promenne, ktere jsou pristupny vsem funkcim, tzn. ze to jsou globalni, verejne promenne /coz pripomina prikaz COMMON ve FORTRANU nebo EXTERNAL v PL/1/. Protoze externi promenne jsou verejne pristupne, mohou byt pouzivany misto seznamu argu- mentu pro komunikaci mezi funkcemi. Navic externi promenne ne- zanikaji s vyvolanim a ukoncenim funkci, takze jejich hodnota, ktera byla nejakou funkci nastavena, zustava v pameti. Externi promenne musi byt definovany mimo funkce; to pro ne zajisti misto v pameti. Navic jeste musi byt v kazde funkci, ktera k nim ma mit pristup, deklarovany. To muze byt ucineno explicitnim prikazem extern nebo implicitne dle kontextu. Aby- chom byli konkretni, prepisme program pro nalezeni nejdelsi radky s promennymi line, save a max definovanymi jako externi promenne. To vyzaduje zmenit volani funkce, zmenit deklarace a upravit vsechny funkce.
#define MAXLINE 1000 /*max. delka vstupu vety*/
char line[MAXLINE]; /*vstupni radka*/
char save[MAXLINE]; /*nejdelsi radka je uchovana zde*/
int max; /*predposledni nejdelsi delka radky*/
main() /*nalezeni nejdelsi radky; zvlastni verze*/
if (max> 0) /*byla nactena veta*/
printf('%s', save);
}
getline() /*zvlastni verze*/
line[i] = '/0';
return (i);
}
copy() /*zvlastni verze*/
Externi promenne v jednotkach main, getline a copy jsou definovany prvnim radkem programu. Zde je urcen typ techto promennych a vyhrazeno misto v pameti. Po syntakticke strance jsou definice externich promennych shodne s deklaracemi, ktere jsme jiz drive uzivali, ale protoze se objevuji mimo funkce, tak jsou chapany jako externi promenne. Predtim, nez funkce muze pouzivat externi promennou, musi znat jeji jmeno. Jeden ze zpusobu je napsat deklaraci extern uvnitr funkce; deklarace je stejna s tim, ze na zacatku je slovo extern.
Za jistych okolnosti muze byt deklarace extern vynechana.
Jestlize se objevi ve zdrojovem textu pred pouzitim urcite funkce, tak neni nutne pouzit deklarace extern uvnitr teto funkce. V nasem prikladu jsou tedy deklarace extern v jednot-kach main, getline, copy nadbytecne. Je rozsirenou praxi umis-tit definice vsech externich promennych na zacatek souboru a nikde jiz nepouzivat prikaz extern.
Jestlize je ale program rozdelen do nekolika souboru a pro-
menna je definovna rekneme v souboru file1 a pouzivana v sou-Ő.po 12
boru file2, potom je deklarace extern ve file2 nezbytna. To bude ukazano v kapitole 4.
Vsimnete si, jak opatrne pouzivame vyrazy deklarace a defi-nice, kdyz mluvime v teto sekci o externich promennych. 'Definice' urcuje misto, kde bude externi promenna vytvorena /tj. jeji prideleno misto v pameti/. 'Deklarace' poukazuje na misto, kde je promenna ulozena, ale neni ji pridelena pritom pamet.
Zdalo by se, ze je vyhodne veskerou komunikaci mezi funkcemi omezit na pouzivani externich promennych - seznamy argumentu jsou kratke a promenne jsou vzdy tam, kde je ocekavame. Ale externi promenne jsou tam i tehdy, kdyz je nepotrebujeme. Tento zpusob programovani je ale plny nebezpeci, protoze vede k programum, kde je vzajemna komunikace nejasna. Za prve promenne mohou byt neocekavane z nedopatreni zmeneny a za druhe neni snadne program modifikovat. Druha verze programu je horsi nez prvni castecne kvuli smyslu a castec-ne proto, ze znicila univerzalnost dvou uzitecnych funkci, kdyz je pripoutala k urcitym nazvum promennych.
C v i c e n i 1-18. Podminka v prikazu for ve funkce getline je ponekud neohrabana. Prepiste program tak, aby byla jasnejsi a pritom zachovavejte cinnost funkce, narazi-li na konec dat nebo pri preplneni pole.
1.11 Shrnutí
V teto chvili jsme probrali to, co muze byt chapano jako za-klady jazyka C. Se stavebnimi bloky, ktere jsme sestrojili, mu-zeme nyni psat uzitecne programy rozumne velikosti a bude jiste dobre, kdyz si je procvicite pred dalsim studiem. Nasledujici priklady by vam mely dat namety pro slozitejsi programy, nez jsou popsany v teto kapitole.
C v i c e n i 1-19. Napiste program detab, ktery nahrazuje ta-belatory ze vstupu odpovidajicim poctem mezer. Predpokladejte, tabelatory jsou na kazde n-te pozici.
C v i c e n i 1-20. Napiste program entab, ktery nahrazuje retezec mezer minimalnim poctem tabelatoru a mezer. Uzivejte stejne pozice tabelatoru jako ve funkci detab. C v i c e n i 1-21. Napiste program, ktery rozdeluje dlouhe radky ze vstupu po poslednim nemezerovem znaku, ktera se objevi pred n-tym sloupcem vstupu, kde n je parametr. Ujistete se, ze vas program funguje s velmi dlouhym radkem, nebo jestlize nejsou zadne mezery nebo tabelatory pred n-tou pozici. C v i c e n i 1-22. Napiste program, ktery vyjima vsechny komentare z programu v jazyce C. Neopomente spravne pouzivat znakove retezce a znakove konstanty. C v i c e n i 1-23. Napiste program , ktery kontroluje zaklad-ni symboly programu jako jsou neuzavrene kulate, hranate a slo-zene zavorky, nezapomente na apostrofy, uvozovky a komentare. Tento program je slozity, jestlize jej plne zobecnite.
KAPITOLA 2: TYPY, OPERÁTORY A VÝRAZY
Promenne a konstanty jsou zakladni datove objekty, se kte-rymi se v programu pracuje. V deklaracich jsou vyjmenovany promenne, ktere budou pouzivany a je urcen jejich typ a nekdy i jejich pocatecni hodnoty. Operatory urcuji, jak s nimi bude nalozeno. Vyrazy obsahuji promenne a operatory a vysledkem je nova hodnota. To je predmetem teto kapitoly.
2.1 Názvy proměnných
Prestoze jsme si to jeste nerekli,tak pro nazvy promennych a symbolickych konstant plati jista omezeni. Nazvy se skladaji z pismen a cislic, prvni znak musi byt pisme-no.Podtrzeni '_' je chapano jako pismeno: je uzitecne pro zpre-hledneni dlouhych jmen nekterych promennych. Velka a mala pis-mena se od sebe lisi: v jazyku C je tradici pouzivat mala pis-mena pro jmena promennych a velka pismena pro symbolicke konstanty.
Jen prvnich osm znaku jmen je vyznamnych, ackoliv jich muze byt pouzito vice. Pro externi nazvy,jako jsou jmena funkci a promennych, musi byt nekdy pocet znaku mensi, protoze jsou tato jmena pouzivana ruznymi assemblery a sestavovacimi pro-gramy. Podrobnosti jsou v dodatku A.
Klicova slova, jako jsou if, else, int, float,atd. jsou rezervovana a nelze je pouzivat pro jmena promennych /musi byt psana malymi pismeny/.
Je prirozene vyhodne volit jmena promennych tak, aby byl jasny jejich vyznam a aby nebylo snadne je zamenit navzajem.
2.2 Typy dat a jejich délka
V jazyku C je jen nekolik zakladnich typu dat:
char jeden byte, muze obsahovat jeden znak
mistniho souboru znaku
int celociselna promenna, delka odpovida
delce celych cisel na danem pocitaci
float cislo s pohyblivou desetinou carkou
a jednoduchou presnosti
double cislo s pohyblivou desetinou carkou
a dvojnasobnou presnosti
Navic je jeste nekolik kvalifikatoru, ktere mohou byt aplikovany na typ int: short, long a unsigned. short a long odpovidaji ruznym delkam celociselnych promennych.
Cisla unsigned splnuji pravidla aritmeticke funkce modulo
2**n, kde n je pocet bitu promenne int. Tato cisla jsou vzdy
kladna. Deklaracni prikazy vypadaji takto
.po 3
short int x; long int y; unsigned int z;
Slovo int muze byt vynechano a obycejne v takovych pripa-dech nebyva. Presnost techto typu promennych zalezi na konkretnim typu pocitace. V tabulce jsou uvedeny nektere typy.
DEC PDP-11 Honeywell 6000 IBM 370 Interdata 8/32
ASCII ASCII EBCDIC ASCII
char 8 bitu 9 bitu 8 bitu 8 bitu
int 16 36 32 32
short 16 36 16 16
long 32 36 32 32
float 32 36 32 32
double 64 72 64 64
short a long by mely byt ruzne dlouhe tam, kde je to vyhodne. Int obycejne predstavuje 'prirozenou' delku pro dany typ pocitace. Jak vidite, kazdy prekladac jazyka C interpretuje delku long a short ruzne v zavislosti na typu pocitace. Jedine, s cim muzete pocitat je, ze short neni delsi nez long.
2.3 Konstanty
Konstanty typu int a float jsme jiz pouzivali, takze
upozorneme na nezvykle tvary
123.456e-7
nebo
0.12E3
Tento tvar konstant se nazyva 'vedecka' notace. Kazda konstanta s pohyblivou radovou carkou je chapana jako double, takze nota-ce 'e' slouzi jak pro float, tak pro double. Dlouha cela cisla se pisi s priponou L. Obycejna celo-ciselna konstanta, ktera je delsi nez short je rovnez chapana jako long.
Existuje take moznost zapisu oktalovych a hexadecimal-nich konstant: je-li na prvni pozici konstanty 0 /nula/, tak je konstanta brana jako oktalova; jsou-li na zacatku znaky 0x nebo 0X, tak se jedna o hexadecimalni promennou. Napr. dekadicke cislo 31 muze byt psano oktalove jako 037 a hexadecimalne 0xlf nebo 0X1F. Oktalove a hexadecimalni konstanty mohou mit rovnez priponu L pro oznaceni typu long.
Znakova konstanta je jeden znak napsany v apostrofech,
napr. 'x'. Hodnota znakove konstanty je numericka hodnota toho-.po 12
to znaku v souboru znaku daneho pocitace. Napr. ve znakovem souboru ASCII ma znak nula, nebo '0' hodnotu 48 a v EBCDIC '0' je 240. Oboji je velice odlisne od ciselne hodnoty 0. Napiseme-li v programu '0' misto 48 nebo 240, tak ucinime program nezavislym na konkretni reprezentaci. Znakova konstanta se muze zucastnit ciselnych operaci jako ostatni cisla, presto-ze se pouziva spise pro porovnavani s ostatnimi znaky. V nasle-dujici casti se hovori o konverznich pravidlech.
Urcite netistitelne znaky mohou byt reprezentovany se-kvenci, ktera zacina obracenym lomitkem napr. n (znak nove ra-dky)/, t (tabelator), 0 (prazdny znak), (obracene lo-mitko), ' (apostrof) atd., ktere vypada jako dva znaky, ale ve skutecnosti to je jen jeden znak. Navic muze byt libovolny vzor bitu v bytu generovan sekvenci:
'ddd' kde ddd jsou jedno az tri oktalova cisla, jako napr.
#define FORMFEED '014' (# ASCII Form Feed)
Znakova konstanta '0' predstavuje znak s nulovou hodnotou '0' piseme casto misto 0, abychom zduraznili vlastnost urci-teho vyrazu.
Konstantni vyraz je vyraz, ktery obsahuje pouze konstanty a operatory. Takovy vyraz je vyhodnocovan jiz ve fazi prekla-du a ne pri behu programu a muze byt pouzit vsude tam, kde se muze objevit konstanta, jako napr.
#define MAXLINE 1000
char line [MAXLINE + 1];
nebo v
seconds = 60*60*hours;
Znakova konstanta je sekvence nekolika znaku jako napr.
'I am a string'
/*prazdny retezec*/
Uvozovky nejsou soucasti retezce, slouzi jenom k jeho omeze-ni. Sekvence s obracenym lomitkem muze byt soucasti retezce: napr. ' reprezentuje znak uvozovky.
Z technickeho hlediska je retezec pole, jehoz prvky jsou jednotlive znaky. Prekladac automaticky ukoncuje kazdy rete-zec prazdnym znakem 0, takze program muze snadno detekovat konec retezec. Tato reprezentace tudiz nijak neomezuje delku retezce, ale program musi cele pole projit, aby urcil jeho delku. Skutecna pamet potrebna pro retezec je o jednu pozici delsi, coz je fakticka delka retezce. Nasledujici funkce s t r l e n (s) urcuje delku retezce s a nebere pri tom v uvahu znak 0.
strlen (s) /*zjisteni delky retezce*/ char s [];
Budte opatrni pri rozlisovani znakove konstanty a retezce, kte-ry obsahuje jeden znak: 'x' neni totez jako 'x'. 'x' je jeden znak, ktery je v tomto tvaru ciselnou reprezentaci znaku x v souboru znaku pocitace. 'x' je znakovy retezec, ktery obsahu-je znak x /pismeno x/ a znak 0.
2.4. Deklarace
Vsechny promenne musi byt pred pouzitim deklarovany, ackoliv nektere deklarace mohou byt podle kontextu implicitni. Deklara-ce specifikuje typ a za nim nasleduje seznam jedne nebo vice promennych tohoto typu, jako napr.:
int lower, upper, step; char c, line [1000];
Promenne mohou byt umisteny v deklaracich v libovolnem poradi, takze usporadani muze byt i nasledovane:
int lower;
int upper;
int step;
char c;
char line [1000];
Tento zpusob zapisu zabira sice vice mista textu pro-gramu, ale je vyhodny, chceme-li ke kazde promenne pridat ko-mentar nebo chceme-li provadet modifikace.
Promenne mohou byt rovnez v deklaracich inicializovany.
Inicializece se provadi timto zpusobem:
char backslash = '';
int i = 0;
float eps = 1.0e-5;
Jestlize je inicializovana promenna externi nebo staticka, ini-cializace je provedena jen jednou, a to pred zacatkem cinnosti programu. Automaticke promenne jsou inicializovany vzdy, kdyz jsou funkce vyvolany. Automaticke promenne, ktere nejsou expli-citne definovany, maji nedefinovanou hodnotu. Externi a static-ke promenne maji implicitne nulovou hodnotu, ale je dobrym stylem je stejne inicializovat. Podrobneji se problemem ini-cializace budeme zabyvat dale.
2.5. Aritmetické operátory
Binarni aritmeticke operatory jsou +, -, *, / a operator
modulo %. Existuje unarni - ale neexistuje unarni +..po 12
Celociselne deleni x/y odrezava desetinnou cast. Vyraz
x % y produkuje zbytek po deleni x%y a je nula tehdy, kdyz x je delitelne y. Napr. rok je prechodny, kdyz je delitelny 4 a neni delitelny 100. Roky delitelne 400 jsou take prechodne.
Proto
if(year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
je prechodny rok
----- ----- ------
else
neni prechodny rok
----- ----- ---------
Operator % nemuze byt pouzit pro promenne float nebo double.
Operatory + a - maji stejnou prioritu, ktera je nizsi nez priorita operatotu *, / a %. Unarni minus ma prioritu nejvyssi. Aritmeticke operatory stejne priority se vykonavaji zleva doprava /na konci kapitoly je uvedena tabulka, ktera shrnuje prioritu a asociativnost vsech operatoru/. Sled vycislovani ne-ni urcen pro asociativni a komutativni operatory jako jsou + a -. Prekladac proto muze vyrazy s temito operetory presku-pit. Proto vyraz a+(b+c) muze byt vycislen ve tvaru (a+b)+c. Druhy zpusob je vyhodnejsi, protoze neni potreba pouzivat pomocnou promennou.
2.6. Relační operátory
Relacni operatory jsou
> >= < <=
Vsechny maji stejnou prioritu. Nizsi a navzajem shodnou prioritu maji operatory rovnosti a nerovnosti.
== !=
Relacni operatory maji nizsi prioritu nez aritmeticke operatory, takze vyrazy jako i < lim-1 jsou chapany jako i < (lim-1), jak muze byt nakonec ocekavano.
Dalsimi operatory jsou && a ||. Vyrazy s temito operatory
jsou vyhodnocovany zleva doprava a vyhodnocovani skonci tehdy,
je-li zrejme, ze vyraz je pravdivy nebo nepravdivy. Tuto vlast-
nost je si treba uvedomit pri psani programu. Podivame-li se
na prikaz cyklu funkce getline, kterou jsme vytvorili v kap. 1
for(i=0; i < lim-1 && (c=getchar()) != 'n' && c != EOF; ++i) s[i] = c;
je zrejme, ze test i < lim-1 m u s i byt vykonan drive nez nacteme dalsi znak. A ne jenom to. Kdyz totiz neni tato podmin-ka splnena, n e s m i m e cist dalsi znak. Obdobne budeme v nesnazich, pokusime-li se porovnavat znak c s EOF predtim, nez zavolame getchar. Volani se musi objevit predtim, nez testujeme konec souboru.
Priorita operatoru && je vyssi nez priorita operatoru ||.
Oba tyto oparatory maji nizsi prioritu nez relacni operatory.po 3a operatory rovnosti a nerovnosti, takze vyraz:
i < lim-1 && (c=getchar()) != 'n' && c != EOF nepotrebuje dalsi zavorky, protoze priorita != je vyssi nez prirazeni, zavorky jsou nutne v (c=getchar()) != 'n' abychom dosahli pozadovaneho efektu.
Unarni operator negace ! prevadi nenulove, nebo-li pravdi-ve operatory, na 0, a nulove, nebo-li nepravdive operatory,na 1. Rozsirenym pouzitim operatoru ! je vyraz jako
if (! inword)
spise nez
if (inword == 0)
Je tezke stanovit obecna pravidla, ktera forma je lepsi.Vyraz jako ! inword, lze snadno cist /'jestlize neni slovo'/, ale komplikovanejsim vyrazum je nekdy tezke porozumet.C v i c e n i 2-1. Napiste prikaz cyklu ekvivalentni cyklu for v predchozi ukazce bez uziti &&.
2.7. Konverze typu
Jestlize se ve vyrazech objevuji promenne ruznych typu,tak jsou konvertovany na spolecny typ, podle nekolika pravidel.Automaticky jsou provadeny pouze konverze, ktere maji smysl,jako je konvertovani cisla typu int na typ float ve vyrazu f+i.Vyrazy, ktere nemaji smysl, jako napr. uziti float jako indexu,nejsou povoleny. Typy char a int mohou byt v aritmetickych vy-razech libovolne promichany; kazda promenna char je konvertova-na na int. To umoznuje dostatecnou flexibilitu v urcitych dru-zich znakove transformace. To je uzito ve funkci a t o i , ktera konvertuje retezec cislic na ciselny ekvivalent:
atoi(s) /*konvertovani s na cela cisla*/
char s[];
Jak bylo ukazano v kapitole 1, vyraz:
s[i] - '0'.po 12je numerickou hodnotou znaku ulozeneho v s[i], protoze hodnotyznaku '0', '1', tvori vzestupnou, neprerusenou radu kladnychcisel.Jinym prikladem konverze char na int je funkce l o w e r , ktera prevadi velka pismena na mala /plati pouze pro ASCII!/. Jestlize znak neni velke pismeno, vraci jej funkce lower nezme- neno.
lower(c) /*prevod c na male pismeno, pouze pro ASCII*/
int c;
Tato fukce muze pracovat pouze se znakovym souborem ASCII, pro- toze velka a mala pismena maji od sebe konstantni vzdalenost a ciselne hodnoty obou abeced jsou ve vzestupnem poradi a mezi 'A' a 'Z' neni nic jineho nez pismena. Posledni podminka nepla- ti pro EBCDIC /IBM 360/370/.
Jeden problem je ale s konverzi velkych pismen na mala spo- jen. V jazyku c neni definovano, zda je promenna char se zna- menkem nebo bez neho. Konvertujeme-li char na int, bude vysle- dek k l a d n y nebo z a p o r n y ? Nanestesti se to lisi- podle typu pocitace. Na nekterych /napr. PDP-11/ je promenna char, jejiz levy bit je 1, konvertovana na zaporne cele cislo /rozsireni znamenka/. Na jinych pocitacich je char doplnen zle- va nulami a vysledek je proto kladny. Definice jazyka C garantuje, ze libovolny znak v souboru znaku pocitace nebude nikdy zaporny, takze tyto znaky mohou byt volne pouzity ve vyrazech jako kladna cisla. Naproti tomu pro- menna, ktere jsme priradili nejaky bitovy obrazec, muze byt na nekterych typech pocitacu zaporna, na jinych kladna. Casto se tato situace vyskytuje v pripade je-li pro EOF po- uzita hodnota -1. Uvazme pripad:
char c;
c = getchar();
if (c == EOF)
Na pocitacich, ktere nemaji rozsirena znamenka, je c vzdy klad- ne, protoze je char a EOF zaporne. Vysledkem je to, ze test c == EOF nemuze byt nikdy pravdivy. Abychom se tomuto problemu vyhnuli musime pro promennou, ktera obsahuje hodnotu danou funkci getchar, deklarovat misto char typem int. Pravy duvod pro uziti int misto char nema co delat s otazkou pravdepodobneho rozsireni znamenka. Je to proto, ze funkce getchar musi vracet vsechny mozne znaky /aby mohla byt pouzita pro cteni libovolneho vstupu/ a navic odlisne hodnoty pro EOF. Proto promenna, ktere je funkce prirazena, n e s m i byt typu char, ale musi byt definovana jako int. Jinou uzitecnou formou automaticke konverze typu je to, ze relace jako i>j a logicke vyrazy s operatory && a || maji hod-.po 3 notu 1 jsou-li pravdive a 0 nejsou-li pravdive; proto prirazeni:
isdigit = c >= '0' && c <= '9';
nastavuje promennou isdigit na 1, je-li c cislice, a na 0, neni-li cislici. /V podminkach prikazu if, while, for atd. 'pravdivy' znamena 'nenulovy'./ Implicitni aritmeticke konverze funguji prevazne tak, jak ocekavame. Obecne receno, v operacich s binarnimi operatory /+, * atd./ je operand 'nizsiho' typu konvertovan na 'vyssi' predtim, nez dojde k vycisleni. Vysledek je rovnez vyssiho ty- pu. Pro kazdy aritmeticky operatator plati nasledujici soubor pravidel:
char a short jsou konvertovany na int, float je konver-
tovano na double.
Potom, jestlize je nejaky operand double, jsou ostatni
operandy konvertovany na double a vysledek je double.
Jinak, jestlize je nejaky operand long, ostatni jsou
konvertovany na long a vysledek je long.
Jinak, jestlize je nejaky operand unsigned, ostatni
jsou konvertovany na unsigned a vysledek je unsigned.
Jinak operandy musi byt int a vysledek je int.
Uvedomte si, ze vsechny promenne float jsou konvertovany na double; v jazyku C se uplatnuje jedine aritmetika s dvojnasob- nou presnosti. Konverze se rovnez uplatnuji pri porizovani; hodnota prave strany je konvertovana na typ promenne na leve strane, coz je typem vysledku. Znak je konvertovan na cele cislo, at uz plati pravidlo rozsireni znamenka nebo ne, jak uz bylo psano drive. Opacne prirazeni - int na char je bez problemu. Prebytecne bity vyssich radu jsou vypusteny. Proto v
int i;
char c;
i = c;
c = i;
je hodnota c nezmenena, at jiz plati pravidlo rozsireni znamen- ka nebo ne. Jestlize je x float a i je int, potom jsou vyrazy x = i
a i = x konvertovany; float na int odrezava desetinnou cast, double na float je konvertovano zaokrouhlenim, long jsou konvertovany na int nebo char odriznutim hornich bitu. Protoze argument funkce je vyraz, je rovnez provadena konverze pri predavani: char a short na int, float na double. Proto tedy deklarujeme argumenty funkce jako int a double, i kdyz potom funkci volame s char nebo float. .po 12 Na zaver budiz receno, ze konverze lze provadet rovnez ex- plicitne zpusobem zvanym v l a s t n o s t . V konstrukci (nazev typu) vyraz je v y r a z konvertovan na vyjmenovany typ podle vyse uvede- nych pravidel. Presny vyznam v l a s t n o s t i je v tom, ze hodnota vyrazu je prirazena promenne specifikovaneho typu a ta je potom pouzita misto celeho vyrazu. Napr. argument knihovni funkce s q r t je promenna typu double a jestize vyvolame tuto funkci s argumentem jineho typu, dostaneme nesmy- slny vysledek. Proto, je-li n cele cislo, tak
sqrt ((double)n)
prevadi n na double predtim, nez je argument predan funkci sqrt /uvedomnte si, ze vlastnost vytvari h o d n o t u n spravne- ho typu; skutecny obsah promenne n je nezmenen/. Operator vlastnosti ma stejnou prioritu jako ostatni zname operatory, jak je shrnuto v tabulce na konci teto kapitoly.
2.8. Operátory přičtení a odečtení jedničky
V jazyce C jsou dva nezvykle operatory pro prirustek a zmen- seni promennych. Operator prirustku ++ pridava ke svemu ope- randu 1; operator zmenseni ubira 1. Operator ++ jsme casto uzivali pro zvetsovani promennych, jako napr. if(c == 'n')
++nl;
Neobvyklym rysem je to, ze ++ a -- muhou byt pouzity bud pred operandem /++n/ nebo za operandem /n++/. V obou pripadech je operand zvetsen o 1. Vyraz ++n zvetsuje n p r e d pouzitim jeho hodnoty a n++ zvetsuje n p o t o m, co jeho hodnota byla pouzita. To znamena, ze v kontextu, kde ma byt pouzita hodnota promenne tak konstrukce ++n a n++ jsou odlisne. Jestize n je 5 potom po
x = n++;
je x 5, zatim co po
x = ++n;
je x 6. Operatory ++ a -- mohou byt aplikovany pouze na promen- ne. V pripadech, ze nepotrebujeme ve vyrazech pouzivat hodnotu promenne, jako v
if(c == 'n')
n1++;
si muzeme zvolit bud ++nl, nebo nl++. Vyrazy jako x=(i+j)++ jsou nepripustne. .po 3 Jsou ovsem situace, kdy potrebujeme pouzivat prave jednu z techto moznosti. Podivejme se napr. na funkci squeeze(s, c), ktera vymazava znak c z retezce s.
sgueeze(s, c) /*vymaze vsechna c ze s*/
char s[];
int c;
Kazdy znak, ktery neni pismenem c je kopirovan na pozici j, a tehdy je take promenna zvetsena o jednicku a je pripra- vena na dalsi znak. To je ekvivalentni prikazum
if(s[i] != c)
Jinym prikladem je cast funkce getline, kterou jsme vytvo- rili v kapitole 1, kde muzeme prikazy
if (c == 'n')
prepsat kompaktneji takto:
if (c == 'n')
s[i++] = c;
Ve tretim priklade funkce strcat ( s, t) pripojuje retezec t na konec retezce s. Predpokladame, ze v retezci s je dostatek mista pro vysledek:
strcat(s,t) /*pripojeni t na konec s*/
char s[], t[]; /*s musi byt dostatecne velke*/
Kdyz jsou znaky kopirovany z t do s, tak operater ++ za pro- mennymi i, j pripravuje bezprostredne kopirovani dalsiho znaku..po 12 C v i c e n i 2-3. Napiste modifikaci funkce squeeze (s1, s2), ktera zrusi vsechny znaky v s1, ktere jsou shodne se znaky v retezci s2.
C v i c e n i 2-4. Napiste funkci any (s1, s2), ktera vrati pozici prvniho vyskytu retezce s2 v retezci s1; nebo -1, jestlize s1 neobsahuje s2.
2.9 Bitové logické operátory
V jazyku C jsou k dispozici operatory pro manipulaci s bity. Tyto operatory nemohou byt aplikovany na promenne float nebo double.
& bitove AND
| bitove OR
^ bitove exkluzivni OR
<< posun doleva
>> posun doprava
` jednickovy doplnek
Bitove AND, &, je casto pouzivano pro vynulovani urcitych bitu. Napr.:
c = n & 0177;
vynuluje vsechny bity promenne n krome nejnizsich 7 bitu. Bitove OR, |, je pouzivano pro nastaveni bitu
x = x | MASK;
nastavi v x na jednicku ty bity, ktere jsou jednotkove v MASK. S opatrnosti rozlisujte bitove operatory & a | od logickych operatoru && a ||, ktere provadeji vyhodnocovani pravdivosti odleva doprava. Napr. jestlize x je 1 a y je 2, potom x & y je nula, zatimco x && y je jedna /proc?/. Operatory posunu << a >> provadeji posun bitu doleva nebo doprava v levem operandu o pocet bitu, udanych operandem v pra- vo. Proto x << 2 posouva bity promenne x o dve pozice doleva a vyplnuje prazdna mista nulami - to odpovida nasobeni cislem 4. Posun doprava promenne bez znamenka plni prazdna mista nula- mi. Posun doprava promenne se znamenkem vypli prazdna mista na nekterych pocitacich /jako je PDP-11/ jednickovym bitem /aritmeticky posun/, na jinych nulami /logicky posun/. Vysledkem unarni operace operatorem ` je doplnek celeho cis- la; to znamena, ze prevadi bity 0 na 1 a naopak. Tyto operatory se obvykle pouzivaji ve vyrazech jako
x & `077
ktery nuluje poslednich sest bitu. Uvedomte si, ze operace x & `077 je nezavisla na delce slova a proto je lepsi ji pou- zivat misto operaci x & 0177700, ktera predpoklada, ze x je sestnactibitove. Vyraz `077 nestoji pri vypoctu zadny cas.po 3 navic, protoze je jako konstantni vycislovan jiz pri prekladu. Abychom si ilustrovali pouziti nekterych bitovych operatoru, napiseme funkci g e t b i t s (x, p, n), ktera vraci pole n-bitu promenne x, ktere zacinaji na pozici p. Predpokladejme, ze bitova pozice 0 je vpravo a ze n a p maji rozumne kladne hodnoty. Napr. getbits(x, 4, 3) vraci tri bity na pozicich 4, 3 a 2 zarovnane vpravo.
getbits( x, p, n) /*ziskani n bitu od pozice p*/
unsigned x
x>>(p+1-n) presouva pozadovane pole bitu na pravy konec slova. Protoze jsme deklarovali x jako unsigned, bez znamenka, tak prazdna mista jsou vyplnena nulami a nezalezi na typu pocitace, ktery pouzivame. `0 je promenna, ktera ma vsechny bity 1; posun o nb bitu doleva prikazem `0<<n vytvari masku s nulami na miste pravych n bitu a jednickami vsude jinde; komplement teto masky ma n pravych bitu rovno 1. C v i c e n i 2-5. Modifikujte getbits pro cislovani bitu odleva doprava.
C v i c e n i 2-6. Napiste funkci wordlength (), ktera zjistu- je delku slova pouziteho pocitace, tj. pocet bitu promenne int. Funkce by mela byt prenosna, tzn. ze by mela byt pouzitelna na vsech druzich pocitacu.
C v i c e n i 2-7. Napiste funkci rightrot (n,b), ktera rotuje cele cislo n o b bitu doprava.
C v i c e n i 2-8. Napiste funkci invert (x,p,n), ktera inver- tuje /tzn. meni 0 na 1 a opacne/ n bitu promenne x od pozice p a ostatni nechava beze zmeny.
2.10. Operátory přirazení a výrazy
Vyrazy
i = i + 2
ve kterych se leva strana opakuje na strane prave, mohou byt napsany zhustenym zpusobem
i += 2
pouzitim p r i r a z o v a c i h o o p e r a t o r u jako +=. Vetsina binarnich operatoru ma odpovidajici prirazovaci operator op=, kde op je jeden z operatoru + - * / % << >> & ^ | Jestlize (e1) a (e2) jsou vyrazy, potom e1 op= e2.po 12 je ekvivalentni e1 = (e1) op (e2) s tou vyjimkou, ze (e1) je vycislovano jen jednou. Uvedomte si, ze e2 je v zavorkach. To znamena, ze
x *= y+1
je
x = x*(y+1)
a ne
x = x*y+1
Jako priklad uvedeme fukci bitcount, ktera pocita jednickove bity:
bitcount(n) /*pocet jednickovych bitu v n*/
unsigned n;
Prirazovaci operatory maji vyhodu hlavne v tom, ze jsou blizsi mysleni cloveka. Rikame spise 'pridej 2 k i' nebo 'zvetsi i o 2' nez 'vezmi i, pridej 2 a vysledek vloz do i'. Proto i += 2. Navic pri slozitych vyrazech jako
yyal[yypv[p3+p4]+yypv[p1+p2]] += 2
je tomuto zapisu lepe rozumet, protoze ctenar nemusi otrocky kontrolovat, zda vyrazy na obou stranach jsou shodne nebo ne. Navic prirazovaci operator umoznuje prekladaci generovat efektivnejsi kod. Jiz drive jsme vyuzivali skutecnosti, ze prirazovaci prikaz ma hodnotu, kterou lze pouzivat ve vyrazech. Typickym prikladem je while (c=getchar()) != EOF)
Prirazeni uzivajici operator prirazeni / +=, -= , adt./ se rovnez muze objevit ve vyrazech, prestoze je to mene obvykle. Typ operatoru prirazeni je shodny s typem na leve strane.
C v i c e n i 2-9. Systemu s dvojkovym doplnkem vyraz x &(x-1) nuluje pravy bit promenne x /proc?/. Pouzijte teto skutecnosti a napiste rychlejsi verzi funkce bitcount.
2.11. Podmíněné výrazy
Prikaz
if(a>b)
z=a;
else
z=b;
pocita zajiste maximum z cisla a a b. P o d m i n e n y v y r a z psany s operatorem '?:', umoznuje jinym zpusobem napsat podobnou konstrukci. Ve vyrazu
e1?e2:e3
je vyraz e1 spocitan nejdrive. Jestilze je nulovy /pravdivy/, potom je vycislen vyraz e2, ktery je hodnotou podmineneho vy- razu. Jinak je vycislen vyraz e3, ktery je potom hotnotou vy- razu. Jenom jeden z vyrazu e2 a e3 je vyhodnocen. Proto vypo- cet maxima z z a a b muzeme napsat - - -
z=(a>b)?a:b; /*z=max(a,b)*/
Dluzno poznamenat, ze podmineny vyraz je skutecny vyraz a muze byt pouzit jako jine vyrazy. Jestlize e2 a e3 jsou odlisneho typu, potom je vysledek zkonvertovan podle pravidel uvedenych v teto kapitole. Je-li napr. f typu float a n je int, potom vyraz
(n>0)?f:n
je typem double at jiz je n kladne nebo ne. Zavorky kolem prvniho vyrazu nejsou nezbytne, protoze priorita operatoru ?: je velmi mala, prave vyssi nez prirazeni. Je vsak vhodne je pro prehlednost psat. Podminene vyrazy vedou k velmi hutneho kodu. Napr.tento cyklus tiskne N prvku pole, 10 na radku oddelene jednou mezerou a kazda radka /i posledni/ je ukoncena presne jednim znakem pro novou radku.
for(i=0;i<N;i++)
printf('6d%c',a[i],(i%10==9||i==N-1)?'n':' ');
Znak pro novou radku 'n' je tisten vzdy po desatem prvku a po N - tem prvku. Ostatni prvky jsou nasledovany mezerou. Ackoliv tento priklad muze vypadat nepoctive, je vhodne zkusit si jej napsat bez podmineneho vyrazu.
C v i c e n i 2-10. Prepiste funkci lower, ktera konvertuje velka pismena na mala s uzitim podmineneho vyrazu misto if - else.
2.12. Priorita a pořadí vyhodnocovaní
Tabulka uvedena nize shrnuje pravidla priority a asociativ- nosti vsech operatoru vcetne tech, se kterymi jsme se jeste ne- seznamili. Operatory na jedne radce maji stejnou prioritu; rad- ky jsou serazeny sestupne, tak napr. *, / a % maji stejnou prioritu vyssi nez + a -.
Operator Asociativnost
() [] -> . zleva doprava
| - ++ -- ` (typ) * & zprava doleva
zleva doprava
zleva doprava
<< >> zleva doprava
< <= > >= zleva doprava
zleva doprava
& zleva doprava
zleva doprava
zleva doprava
&& zleva doprava
zleva doprava
zprava doleva
= += -= atd. zprava doleva
, /kapitola 3/ zleva doprava
Operatory -> a . jsou pouzivany pro pristup ke clenum struktur; budou popsany v kapitole 6 spolu se sizeof /velikost objektu/. V kapitole 5 jsou uvedeny operatory * a &. Uvedomte si, ze priorita bitovych operatoru &, ^ a | je nizsi nez == a !=. Z toho vyplyva, ze pri testovani bitu ve vyrazu jako:
if((x & MASK) == 0)
jsou zavorky nezbytne. Jak uz jsme se zminili, vyrazy obsahujici jeden z aso- ciativnich a komutativnich operatoru /*, +, &, ^, |/ mohou byt prekladacem preskupeny i kdyz jsou pouzity zavorky. Ve vetsine pripadu to nevadi; v situacich, kdy by to vadit mohlo, musi byt explicitne pouzity pomocne promenne, aby poradi vycislova- ni probihalo tak, jak si prejeme. Jazyk C, ostatne jako vetsina ostatnich jazyku, nespecifiku- je, v jakem poradi budou operandy operatoru vycisleny. Napr. v prikazu
x=f() + g();
muze byt f vycislena drive nez g nebo take naopak; proto jestli jedna z funkci f nebo g meni externi promenne, na kterych zavi- si druha z funkci, muze hodnota x zalezet na poradi vycislova- ni. Znovu opakujeme, ze mezivysledek muze byt ulozen v pomocne promenne, abychom si zajistili spravne poradi vycisleni. Obdobne neni stanoveno poradi, v jakem jsou vycislovany .po 3
argumenty funkci, takze prikaz
pritf('%d %d n', ++n, power(2,n)); /*chyba*/
muze produkovat /a take produkuje/ ruzne vysledky na ruznych pocitacich, coz zalezi na tom, je-li n zvetseno pred volanim funkce power nebo ne. Spravny zapis vypada takto:
++n;
printf('%dn', n, power(2,n));
Volani funkci a operatory ++ a -- v prirazovacich prikazech zpusobuji 'postranni efekty' - nektera promenna je zmenena jako 'vedlejsi produkt' pri vycislovani vyrazu. Ve vyrazech, kde se projevuji vedlejsi efekty, zalezi na tom, jak jsou ulozeny pro- menne, ktere vystupuji ve vyrazu. Jedna nestastna situace vypa- da takto:
a[i] = i++;
Otazkou je, zda index je stara nebo nova hodnota promenne i. Prekladac muze s timto vyrazem nalozit ruznym zpusobem. Jestlize se projevuji vedlejsi efekty, jsme vydani na milost a nemilost prekladaci, protoze nejlepsi zpusob prekladu zalezi vzdy na architekture pocitace. Moralnim zaverem teto diskuze je to, ze psani programu, kde zalezi na poradi vyhodnocovani, je spatnym programatorskym sty- lem ve vsech jazycich. Je prirozene nutne vedet, kterym vecem se mame vyhnout, ale nevite-li, jak jsou na kterych pocitacich implementovany, pak vas tato nevedomnost muze i zachranit.
KAPITOLA 3: VĚTVENÍ PROGRAMU
Prikazy pro vetveni programu specifikuji, v jakem poradi budou instrukce program vykonany. Uz jsme se setkali s nejob- vyklejsimi prikazy pro vetveni programu v drivejsich prikazech, zde uvedeme kompletni sestavu techto prikazu a upresime pred- chozi.
3.1 Příkazy a bloky
V y r a z jako je x = 0, nebo i++, nebo printf() se stava p r i k a z e m, je-li nasledovan strednikem, jako v
x = 0;
i++;
printf();
V jazyku C je strednik spise koncovy znak prikazu nez oddelo- vac, jako je tomu napr. v jazyku ALGOL.
Slozene zavorky se pouzivaji pro zdruzovani deklaraci a prikazu ve s l o z e n y p r i k a z neboli b l o k, ktery je syntakticky ekvivalentni jednomu prikazu. Jednim prikladem jsou slozene zavorky kolem jednoho prikazu. Dalsim prikladem jsou tyto zavorky kolem nekolika prikazu za prikazem if, else, while nebo for. /Promenne mohou byt ve skutecnosti definovany uprostred l i b o v o l n e h o bloku; o tom budeme mluvit v kapitole 4/. Za pravou zavorkou, ktera ukoncuje blok, neni nikdy strednik.
3.2 If - Else
Pro rozhodovani se pouziva kostrukce if - else.
Formalni syntaxe je asledujici
if(vyraz)
p r i k a z - 1
else
p r i k a z - 2
kde cast else neni povinna. V y r a z je vyhodnocen, jestlize je 'pravdivy' /tj. ma nenulovou hodnotu/, p r i k a z - 1 je vykonan, jestlize je nepravdivy /tj. ma nulovou hodnotu/ a jestlize je uvedeno else, je vykonan p r i k a z e m - 2. Protoze prikaz if pouze testuje hodnotu vyrazu, je mozne psat zkracene
if(vyraz)
misto
if(vyraz != 0)
Nekdy je tento zpusob prirozeny a jasny, nekdy je ale tajemny..po 3
Protoze cast else prikazu if-else je nepovinna, tak jestlize else vynechame v sekvenci prikazu if, vznika nejednoznacnost. else je logicky spojeno s nejblizsim predchozim vyrazem if, ktera nema cast else. Napr. v
if(n > 0)
if(a > b)
z = a;
else
z = b;
cast else je spojena s vnitrnim prikazem if, jak jsme ukazali strukturou zapisu. Jestlize teno zpusob neni ten, ktery chcete, je treba uzit zavorek pro spravne sdruzeni
if(n > 0)
else
z = b;
Dvojznacnost je zvlaste nebezpecna v situacich jako:
if(n > 0)
for(i = 0; i < n; i++)
if(s[i] > 0
else /*chyba*/
printf('error - n is zeron');
Logicka struktura zapisu ukazuje presne co chcete, ale pre- kladac presto spoji else s vnitrnim prikazem if. Tento druh chyb se velmi tezko odstranuje. Uvedomte si, ze za z = a v
if(a > b)
z = a;
else
z = b;
je strednik. Je to kvuli gramatickym pravidlum: prikaz nasle- dujici if je vzdy ukoncen strednikem.
3.3 Else - If
Konstrukce
if(v y r a z)
p r i k a z.po 12
else if(v y r a z)
p r i k a z
else if(v y r a z)
p r i k a z
else
p r i k a z
se objevuje tak casto, ze stoji zato se podrobneji o nich zminit. Sekvence prikazu if je nejobecnejsim zpusobem mnoho- nasobneho rozhodovani. V y r a z y jsou vyhodnocovany v tom- to poradi: jestlize je v y r a z pravdivy, tak prikaz s nim spojeny je vykonan, a tim je cela sekvence ukoncena. P r i k a z znamena bud jeden prikaz nebo skupinu prikazu v zavorkach. Prikaz spojeny s poslednim else je vykonan, jestlize zadna predchozi podminka nebyla splnena. V nekterych pripadech mu- ze byt cast
else
p r i k a z
vypustena.
Abychom ilustrovali tricestne rozhodovani, uvedeme binarni funkci rozhodovani, ktera urcuje, zda se urcita hodnota objevu- je v roztridenem poli v. Prvky pole musi byt usporadany vzestu- pne. Funkce vraci pozici /cislo v rozsahu 0 az n-1/ jestlize x je obsazeno ve v, a -1 jestlize neni.
binary(x, v, n) /*nalezeni x ve v[0]v[n-1]*/
int x, v[], n;
return(-1)
}
Zakladnim rozhodnutim je, zda x je mensi, vetsi, nebo rovno strednimu prvku v [mid] v kazdem kroku; to je prirozene pro else-if.
3.4 Přepínač
Prepinac je specialni, mnohonasobny rozhodovaci prikaz, kte- ry testuje, zda se vyraz v zavorkach rovna nektere z k o n- s t a n t n i c h hodnot a podle toho pokracuje v cinnosti. V kapitole 1 jsme zjistovali vyskyt kazde cislice, mezery a ostatnich znaku pouzitim sekvence if else, if else. Zde uvedeme stejny program s prikazem switch.
main() /*pocitani cisel, oddelovacu, ostatnich*/
printf('digits = ');
for(i = 0; i<10; i++)
printf(' %d ', ndigit[i]);
printf('nwhite space = %d, other = %dn',
nwhite, nother);
}
Prikaz switch vyhodnocuje celociselny vyraz v zavorkach /v nasem prikladu je to znak c/ a porovnava jeho hodnotu se vsemi case. Kazdy case musi byt oznacen celociselnou nebo znakovou konstantou, nebo konstantnim vyrazem. Jestlize nektery case odpovida hodnote vyrazu, tak zde zacina cinnost programu. Po- lozka oznacena default je vykonana, jestlize zadny case nebyl uspokojen. Polozka default nemusi byt uvadena; kdyz neni uvede- na a zadny case neni uspokojen, tak neni provedena zadna cin-
nost. Polozky case a default se mohou objevit v libovolnem.po 12
poradi. Case musi byt od sebe odlisne. Prikaz break zpusobuje okamzite ukonceni prikazu switch. Protoze case slouzi pouze jako navesti, tak potom, co jsou vy- konany prikazy odpovidajici navesti case, tak program dale p r o c h a z i dalsi navesti case, dokud neni nejakym expli- citnim prikazem ukoncen. Zakladnimi prikazy pro ukonceni prika- zu switch jsou return a break. Prikaz break muze byt take pou- zit pro okamzite vystoupeni z cyklu while, for a do, jak bude ukazano v dalsich kapitolach. Postupne prochazeni navesti case je dvojsecne. Na jedne strane dovoluje pro jednu cinnost vice navesti case, na druhe strane ale musi byt normalne kazda cinnost po navesti case ukoncena prikazem break pro zamezeni prochazeni dalsich naves- ti. Prochazeni navesti case neni citelne. S vyjimkou pou- ziti vice navesti pro jednu cinnost by melo byt postupne pro- chazeni uzivano ridce. Je dobrym zvykem ukoncovat case prikazem break /v nasem prikladu default/, i kdyz neni z logickeho hlediska potrebny. Nekdy treba budeme pridavat dalsi navesti case a break se bude hodit.
C v i c e n i 3-1. Napiste funkci expand (s, t), ktera konver- tuje znaky jako je znak pro novou radku a tabelator do viditel- neho tvaru n a t pri kopirovani retezce s na t. Pouzijte switch.
3.5 Cykly while a for
Jiz jsme se setkali s prikazy cyklu while a for. Ve
while (v y r a z)
p r i k a z
je vyraz vyhodnocen. Pokud je nenulovy, tak je p r i k a z vykonan a v y r a z je znovu vyhodnocen. Cyklus pokracuje do te doby, nez je vyraz nulovy. Potom program pokracuje za pri- kazem while. Prikaz for
for(v y r a z 1; v y r a z 2; v y r a z 3)
p r i k a z
je ekvivalentni
v y r a z 1;
while( v y r a z 2 )
Z gramatickeho hlediska jsou vsechny tri polozky prikazu for vyrazy. Obycejne vyraz1 a vyraz3 jsou prirazovaci prikazy nebo volani funkce a vyraz2 je relacni vyraz. Kterakoliv po-.po 3 lozka muze byt z prikazu for vypustena, ale stredniky musi byt uvedeny. Jestlize vypustime vyraz1 a vyraz3, promenna i se prestane menit. Jestlize vypustime relacni vyraz2, je tento vyraz bran jako stale pravdivy:
for(; ;)
tvori nekonecny cyklus, ktery muze byt ukoncen jinymi zpusoby /napr. prikazy break nebo return/. Zda pouzit prikaz while nebo for je ciste zalezitost vkusu. Napr. v
while((c = getchar()) == ' ' || c == '/n' || c == 't')
/*preskoceni oddelovacu*/
neni zadna inicializace ani reinicializace, takze pouziti pri- kazu while je prirozene.
Prikaz for ma jasne prednosti tehdy, je-li v cyklu jednodu- cha inicializace a reinicializace, protoze soustredi prikazy cyklu blizko sebe a viditelne na vrcholu cyklu. Je to zcela zrejme v
for(i = 0; i < N; i++)
coz je v jazyku C prikaz obdobny prikazu cyklu DO v jazyku FORTRAN nebo PL/1. Analogie to ale neni uplna, protoze limity cyklu for mohou byt uvnitr meneny a promenne i si uchovavaji svoji hodnotu, i kdyz je cyklus for z jakychkoliv duvodu ukon- cen. Protoze slozky cyklu for jsou libovolne vyrazy, tak cyklus for neni omezen jen na aritmeticke posloupnosti. Nicmene neni ale dobrym stylem 'vnutit' do prikazu for vyrazy, ktere spolu nemaji nic spolecneho.
Jako vetsi priklad uvedeme jinou verzi funkce atoi pro konvertovani retezce na ciselny ekvivalent. Tato verze je univerzalnejsi, pocita totiz s uvodnim oddelovacem a znamen- kem + a - /v kapitole 4 ukazeme funkci atof, ktera provadi stejnou konverzi pro cisla s pohyblivou radovou carkou/. Zakladni struktura programu vypada takto:
ignoruj oddelovace, jestlize nejake jsou -------- ----- ------ -------
vezmi znamenko, jestlize nejake je -------- ----- ------ -
vezmi ciselnou cast a konvertuj ji
-------- ----- ------ -
Kazdy krok vykona urcitou cinnost a ponecha veci v jasnem stavu pro dalsi kroky. Cely proces je ukoncen pri nalezeni prvniho znaku, ktery neni soucasti cisla. .pa.po 12
atoi(s) /*konvertovani s na cislo*/
char s[];
Vyhoda soustredeni cyklu na jedne misto je mnohem jasnejsi, nez nekolik vnorenych cyklu. Nasledujici funkce je trideni typu Shell celociselneho pole. Zakladni myslenka tohoto trideni je, ze v pocatku jsou spise nez sousedni prvky porovnavany prvky vzdalene, zrovna tak jako v normalnim trideni. Tim je ale v prvni fazi vykonano hodne prace, takze dale neni treba uz moc delat. Interval mezi porovnavanymi prvky je postupne snizovan az na jedna, kdy tato metoda efektivne prechazi na normalni premistovani sousednich prvku.
shell(v, n,) /*trideni v [0]v [n-1] vzestupne*/
int v[], n;
}
V prehledu jsou tri vnorene cykly. Vnejsi cyklus urcuje vzda- lenost mezi porovnavanymi prvky a postupne ji zmensuje od n/2 delenim dvema az do nuly. Prostredni cyklus porovnava kazde dva prvky, jejichz vzdalenost je rovna gap, vnitrni cyklus zamenu- je ty prvky, ktere jsou nespravne usporadany. Protoze promenna gap je nakonec zmensena na jedna, tak vsechny prvky jsou sprav- ne usporadany. Uvedomte si, ze obecnost prikazu for dovoluje vnejsimu cyklu mit stejnou formu jako ostatni, i kdyz to neni aritmeticka rada. Jednim z poslednich operatoru jazyka C je carka ',' , kterou nejcasteji nalezneme v prikazu for. Dvojice vyrazu odde- lene carkou je vykonavana zleva doprava, a typ i hodnota vys- ledku je shodna s typem a hodnotou praveho operandu. Proto je mozne v prikazu for pouzivat na ruznych mistech vicenasobne vyrazy, napr. menit dva indexy pole soucasne. To je ilustrovano ve funkce reverse(s), ktera invertuje retezec s. .po 3
reverse(s) /*invertovan retezec s*/
char s[];
}
Carky, ktere oddeluji argumenty funkci, promenne v deklaracich atd., nejsou operatory a n e z a r u c u j i vyhodnocovani zleva doprava.
C v i c e n i 3-2. Napiste funkci expand(s1, s2), ktera roze- pisuje zkratky jako a-z v retezci s1 v kompletni seznam abcxyz v s2. Pripustte velka i mala pismena, cislice a budte pripraveni zachazet s pripady typu a-b-c a a-z, 0-9 a a-z. /Uzitecnou konvenci je to, ze znak - je bran doslovne./
3.6 Cyklus do - while
Jak jsme jiz uvedli v kapitole 1, cykly while a for maji rozhodovaci podminku umistenou na zacatku cyklu. Treti typ cyklu v jazyku c, prikazy do - while, maji rozhodovani umis- teno na konci p o vykonani prikazu tela cyklu; telo cyklu je tady vykonano minimalne jednou. Syntaxe vypada takto
do
p r i k a z
while (v y r a z)
Prikaz je vykonan a potom je vyhodnocen vyraz. Kdyz je pravdi- vy, prikaz je znovu vykonan atd. Jestlize je vyraz nepravdivy, cyklus je ukoncen. Jak muzeme ocekavat, do-while se pouziva mene nez while a for /odhadem je to asi 5% vsech cyklu/ nicme- ne je cas od casu uzitecny, jako napr. v nasledujici funkci itoa, ktera konvertuje cislo na znakovy retezec /inverzni fun- kce k atoi/. Uloha je ponekud komplikovanejsi, nez by se mohlo na prvni pohled zdat, protoze jednoducha metoda pro generova- ni cislic je generuje ve spatnem poradi. Zvolili jsme zpusob generovani pozpatku a potom invertovani retezce.
itoa(n, s) /*konverze n na znaky v s*/
char s[];
int n;
while((n /= 10) > 0); /*smazani cislice*/
if(sign < 0)
s[i++] = '-';
s[i] = '0';
reverse(s);
}
Cyklus do-while je zde nezbytny, nebo alespon vyhodny, protoze pole s musi obsahovat alespon jeden znak nehlede na hodnotu cisla n. Uzili jsme rovnez zavorky okolo jednoho prikazu, ktery tvori telo cyklu do-while, i kdyz jsou zbytecne, takze ukvapeny ctenar si nesplete cast cyklu do s prikazem cyklu while.
C v i c e n i 3-3. Nase verze programu itoa pri reprezen- taci cisel ve dvojkovem doplnku neumi zachazet s nejvetsim zapornym cislem, tj. n = -(2**(delka slova -1)). Vysvetlete proc. Upravte ji tak, aby tisklo i tuto hodnotu spravne a nezavisle na typu pocitace.
C v i c e n i 3-4. Napiste obdobnou funkci itob(n, s), ktera konvertuje cele cislo n bez znamenka na binarni reprezentaci do retezce s. Napiste take funkce itoh, ktera konvertuje cele cislo na hexadecimalni reprezentaci.
C v i c e n i 3-5. Napiste verzi funkce itoa, ktera ma tri argumenty misto dvou. Tretim argumentem bude minimalni delka pole; konvertovane cislo musi byt doplneno zleva mezerami, aby bylo dostatecne siroke.
3.7. Break
Nekdy je vyhodne mit moznost vychazet z cyklu jinde nez na zacatku nebo na konci. Prikaz break umoznuje predcasny vystup z cyklu for, while a do a stejne tak z prepinace switch. Prikaz break pusobi na nejvnitrnejsi vlozeny cyklus /nebo switch/.
Nasledujici program odstranuje koncove mezery a tabelatory z kazde radky ze vstupu a pouziva prikazu break k vystupu z cyklu tehdy, kdyz je nalezen zprava znak, ktery neni tabelator ani mezera.
#define MAXLINE 1000
main() /*odstraneni koncovych mezer a tabelatoru*/
}
Funkce getline vraci delku radky. Vnitrni cyklus while zacina na poslednim znaku radky /uvedomte si, ze --n zmensuje n pred pouzitim jeho hodnoty/, prohledava ji odzadu a hleda prvni znak, ktery neni mezera, tabelator nebo znak pro novou radku. Cyklus je prerusen tehdy, je-li takovy znak nalezen nebo kdyz n se stane zapornym /tzn. cela radka byla prohledana/. Meli byste si overit, ze program funguje spravne, i kdyz radka obsahuje jen mezery nebo tabelatory. Namisto prikazu break je mozne polozit testovaci relaci na zacatek cyklu while:
while((n = getline(line, MAXLINE)) > 0)
Tato varianta neni lepsi nez predchozi, protoze testovaci podminka neni jiz tak jasna. Podminkam, ktere obsahuji smes &&, ||, ! nebo zavorky je lepsi se vyhnout.
3.8 Continue
Prikaz continue je obdobou prikazu break, ale mene se ho uziva; zpusobuje skok na zacatek d a l s i i t e r a c e nejvnitrnejsiho cyklu /for, while, do/. V cyklech while a do je okamzite vyhodnocena podminka, v cyklu for je vykonana reinicializace. /continue muze byt pouzito pouze v cyklech, nikolov v prepinacich switch, ktery je uvnitr cyklu, zpusobuje vykonani dalsi iterace cyklu./ V nasledujici ukazce jsou vybirana z pole a pouze kladna cisla, zaporna jsou preskocena.
for(i = 0; i < N; i++)
Prikaz continue je casto pouzivan, kdyz cast cyklu, ktera nas- leduje, je slozita, takze obracene podminky a vlozeni dalsi by mohlo byt priliz slozite.
C v i c e n i 3-6. Napiste program, ktery kopiruje vstup na vystup s tou vyjimkou, ze kopiruje pouze jednu radku ze skupi- ny totoznych radek. /Toto je jednoducha verze obsluzneho prog- ramu uniq v systemu UNIX./.po 12
3.9 Příkaz goto a navěsti
Jazyk C obsahuje take nekonecne zatracovany prikaz goto a navesti, na ktere smeruje. Obecne vzato, prikaz goto neni nikdy nepostradatelny a prakticky lze vzdy napsat program bez nej. V teto knize prikaz goto nepouzivame. Nicmene ukazeme nekolik situaci, kde se prikaz goto muze uplatnit. Obecne pouzitelny je pri vystupu z mnohonasobne vno- renych struktur, jako napr. vystup ze dvou cyklu najednou. Pri- kaz break nemuze byt primo pouzit, protoze vystupuje pouze z vnitrniho cyklu.
for()
for()
error:
chybove hlaseni
Tato organizace je vyhodna, jestlize program neni trivialni a kdyz se chyba objevuje na ruznych mistech. Navesti ma stejnou formu jako jmeno promenne a je nasledovano dvojteckou. Muze byt pripojeno k libovolnemu prikazu ve stejne funkci, jako je goto.
Jako jiny prikad uvazujme problem nalezeni prvniho zaporneho cisla ve dvojdimenzionalnim poli. /Vicedimenzionalni pole jsou probrany v kap.5./ Jedna moznost je
for(i = 0; i < N; i++)
for(j = 0; j < M; j++)
if(v[i] [j] < 0)
goto found;
/*nenalezeno*/
found:
/*nalezeno na pozici i, j*/
Program, ktery je napsan s prikazem goto muze vzdy byt napsan bez nej, mozna za cenu opakovanych testu nebo extra promenne. Nas priklad bez goto bude vypadat takto
found = 0;,
for(i = 0; i < N && !found; i++)
for(j = 0; j < M && !found; j++)
found = v[i] [j] < 0;
if(found)
/*nalezeno na pozici i-1, j-1*/
.po 3
else
/*nenalezeno*/
Prestoze nejsme dogmatici, vypada to, ze prikaz goto muze byt uzivan ridce, ne-li vubec.
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. Základy
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 */
getline(s,lim) /*nacti radku do s, vrat delku*/
char s[];
int lim;
index(s,t) /* vrat index vyskytu t v s */
char s[], t [];
return(-1);
}
Kazda funkce ma strukturu nazev (seznam argumentu, jsou-li nejake)
deklarace argumentu, 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.
Funkce, které nevracejí celá čísla
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[];
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*/
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[];
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. Více o argumentech funkcí
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. Externí proměnné
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
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
}
#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;
.po 3
}
double pop () /*vyjmi vrsek zasobniku*/
}
clear () /*vymaz zasobnik*/
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 působnosti
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;
.po 12
if ( i< lim) /*cislo je ok*/
else /*je prilis dlouhe, preskoc zby-
tek radky*/
}
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*/
ungetch (c) /*vrat znak zpet do vstupu*/
int 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. Statické proměnné
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. Proměnné 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;
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. Blokové 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)
je platnost promenne i v prikazu for a nema vztah k zadne jine promenne i v programu. Blokovou strukturu je mozno apli- kovat i na externi promenne. Mame-li danou deklaraci
int x;
f ()
tak potom uvnitr funkce f je x promennou typu double a vne je x externi promennou typu int. Totez plati i pro formalni parametry
int z;
f (z)
double z;
Uvnitr funkce f je z formalnim parametrem a nikoli externi promennou.
4.9. Inicializace
O inicializaci jsme se jiz mnohokrat zminovali, ale vzdy jen okrajove. V tomto odstavci shrneme nektera pravidla zvlaste potom, co jsme si uvedli nove typy promennych. Externi a staticke promenne maji na zacatku n u l o v e h o d n o t y . Automaticke promenne a promenne typu register jsou nedefinovane. Jednoduche promenne /ne pole ani struktury/ mohou byt inicializovany pri deklaraci pouzitim znamenka = a hodnoty:
int x = i;
char squote = ''';
long day = 60 * 24; /*minuty dne*/
Externi a staticke promenne jsou inicilizovany jednou - v dobe.po 12 prekladu. Automaticke promenne a promenne typu register jsou inicializovany pri kazdem volani funkce nebo vyvolani bloku.
Pro inicializaci automatickych promennych a promennych typu register nemusi byt pouzity jenom konstanty. Mohou to byt libovolne vyrazy, v nichz vystupuji predem definovane promenne nebo dokonce i funkce. Napr. Inicializace v programu z kapitoly 3 muze byt napsana takto
binary (x,v,n)
int x, v[], n;
misto
binary (x,v,n)
int x, v[], n;
Ve skutecnosti je inicializace automatickych promennych pouze zkracenim prirazovaciho prikazu. Je pouze veci vkusu, ktery zpusob pouzijeme. My pouzivame prevazne explicitni prirazeni, protoze prirazeni v deklaraci neni tak zretelne. Automaticka pole nemohou byt inicializovany. Externi a staticka pole mohou byt inicializovany seznamem promennych uvedenych ve slozenych zavorkach. Napr. Program pro pocitani znaku z kapitoly 1, ktery zacina main () /*pocitej cisla, mezery, jine*/
muze byt napsan takto
int nwhite = 0;
int nother = 0;
int ndigit [10] =;
main () /*pocitej cisla, mezery, jine*/
Tato inicializace neni ve skutecnosti nutna, protoze pole
je automaticky nulovano, ale je dobre to udelat explicitne. Jestlize je pocet cisel v seznamu kratsi nez je rozmer pole, tak ostatni prvky budou vynulovany. Na druhe strane vetsi pocet zpusobuje chybove hlaseni a v seznamu neni mozne pouzi- vat opakovace a ani neni mozne inicializovat pole od poloviny. Znakove pole je mozne inicializovat i jinak; muze byt pou- zit primo retezec
char pattern [] = 'the';
To je kratsi forma ekvivalentniho zapisu
char pattern [] = ;
Kdyz neni uveden rozmer pole, pocitac zjisti delku z iniciali- zacniho prikazu. V tomto nasem priklade je delka 4 /3 znaky a ukoncujici znak 0/.
1. 10. Rekurze
Funkce v jazyku C mohou byt pouzivany rekurzivne: to zname- na, ze funkce muze volat s e b e bud primo nebo neprimo. Klasickym prikladem je tisk cisla jako znakoveho retezce. Jak uz jsme se zminili drive, cisla jsou generovana v opacnem pora- di, ale tisknuta musi byt spravne. Tento problem je mozno resit dvojim zpusobem. Jeden zpusob je ulozit znaky do pole a tis- knout je v opacnem poradi, tak jak jsme to udelali v kapitole 3 ve funkci itoa. Prvni verze je udelana timto zpusobem.
printd (n) /* tisk n */
int n;
i = 0;
do
while ((n /= 10) > 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;
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
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./
KAPITOLA 5: POINTERY A POLE
Pointr (ukazatel) je promenna, ktera obsahuje adresu jine promenne. V jazyku C se pointru hojne pouziva. Castecne proto, ze je to casto jedina moznost pro vykonani vypoctu a castecne proto, ze vedou ke kompaktnejsimu kodu. Pointry jsou hazeny do jednoho pytle spolu s prikazy goto jako prikazy, ktere dokazou nadhernym zpusobem vytvaret progra- my, kterym neni vubec rozumet. To je castecne pravda tehdy, nejsou-li vyuzivany rozumne a opatrne. Je totiz velice snadne vytvorit pointer, ktery ukazuje nekam, kam to neocekavame. Naopak jsou-li pointry vyuzivany disciplinovane, muzeme napsat programy jasne a jednoduse. Tuto vlastnost pointru se budeme snazit popsat.
5.1. Pointry a adresy
Protoze pointry obsahuji adresy objektu, je mozne dosahnout objektu 'neprimo' - prave pomoci pointru. Predpokladejme, ze x je promenna typu int a px je pointr, vytvoreny zatim nespeci- fikovanym zpusobem. Unarni operator & udava adresu objektu, a tak prikaz
px = &x;
prirazuje adresu promenne x do promenne px; rikame, ze px 'uka- zuje' na x. Operator & muze byt aplikovan pouze na promenne a prvky pole; vyraz ve tvaru &(x + 1) nebo &3 je nedovoleny. Neni rovnez mozne ziskat adresu promenne typu registr. Unarni operator * naklada se svym operandem jako s adresou a z dane adresy vybira obsah. Proto je-li y promenna int, tak
y = *px;
prirazuje promenne y to, na co ukazuje pointr px. Sekvence
px = &x;
y = *px;
je ekvivalentni s prikazem
y = x;
Rovnez je nutne deklarovat vsechny promenne takto:
int x, y;
int *px;
S deklaraci promennych x a y jsme se jiz setkali drive. Dekla- race pointru je ale novinkou.
int *px;.po 3
je mnemonikem: rika, ze kombinace *px je typu int, coz znamena, ze objevi-li se px v kontextu *px, je ekvivalentni promenne typu int. Ve skutecnosti syntaxe deklarace tohoto typu promenne simuluje syntaxi vyrazu, ve kterem se dana promenna vyskytuje. To je uzitecne ve vsech komplikovanych deklaracich. Napr. deklarace
double atof(), *dp;
urcuje, ze ve vyrazu maji funkce atof() a *dp hodnotu typu double. Meli byste si uvedomit, ze v deklaraci je pointr svazan s urcitym objektem, na ktery ukazuje. Pointry se mohou objevit ve vyrazech. Napr. ukazuje-li px na celociselnou promennou x, potom se *px muze objevit vsude tam, kde x.
y = *px + 1;
prirazuje promenne y hodnotu o jednu vetsi nez x.
printf('%dn', *px);
tiskne obsah promenne x.
d = sqrt((double) *px);
prirazuje promenne d odmocninu promenne x, ktera je predtim prevedena na typ double /viz kap. 2/.
Ve vyrazech typu
y = *px + 1;
maji unarni operatory * a & vetsi prioritu nez operatory arit- meticke. Brzy se vratime k tomu, co znamena
y = *(px + 1);
Odkaz na pointr se muze rovnez objevit na leve strane prira- zovaciho prikazu. Ukazuje-li px na promennou x, potom
*px = 0;
nuluje promennou x, a
*px += 1;
ji zvetsuje o jednotku zrovna tak jako
(*px)++;
V tomto prikazu jsou zavorky nezbytne. Bez nich by byla jednic- ka prictena k px a ne k tomu, na co px ukazuje, protoze unarni operatory jako * a ++ jsou provadeny zprava doleva. Protoze pointry jsou promenne, muze byt s nimi nakladano jako s normalnimi promennymi. Jestlize py je pointr na promen- nou int, potom.po 12 py = px;
zkopiruje obsah promenne px do py. Potom py ukazuje na stejne misto jako px.
5.2 Pointry a argumenty funkci
Protoze promenne jsou predavany funkcim 'hodnotou', tak funkce nemuze primo zmenit hodnoty techto promennych ve volaji- ci funkci. Co musite udelat, chcete-li opravdu zmenit obycejny argument? Napr. podprogram pro trideni muze vymenit dva prvky pomoci funkce swap. Nestaci ale napsat pouze
swap(a, b);
je-li funkce swap nadefinovana takto
swap(x, y) /*chybne!*/
int x, y;
Funkce swap nemuze ovlivnit argumenty ve volajici jednotce, protoze jsou predavany hodnotou. Nastesti existuje zpusob, jak muze dosahnout zadaneho efek- tu. Volajici program bude predavat p o i n t r y promennych:
swap(&a, &b);
Protoze operator & udava adresu promenne, tak &a je pointr na a. Ve funkci swap musi byt argumenty deklarovany jako pointry a skutecne parametry jsou jejich prostrednictvim ovlivnovany.
swap(px, py) /*vymena *px a *py*/
int *px, *py;
Pointru jako argumentu funkci se pouziva hojne tehdy, vyzadujeme-li, aby funkce vracela vice nez jednu hodnotu /mu- zeme rici, ze funkce swap vraci dve hodnoty - nove hodnoty svych argumentu/. Jako priklad uvazujme funkci getint, ktera zajistuje cteni cisel ve volnem formatu rozdelenim vstupni sekvence na celociselne hodnoty; pri jednom vyvolani jedno cis- lo. getint vraci hodnotu, ktera byla nactena, nebo EOF, kdyz.po 3 narazila na konec vstupu. Tyto hodnoty musi byt vraceny v ruz- nych promennych, protoze at uz pro EOF zvolime jakoukoli hodno- tu, mohlo by dojit ke kolizi. Jedno z reseni je zalozeno na funkci scanf, ktera bude po- psana v kapitole 7. Tato funkce vyuziva toho, ze getint vraci jako hodnotu EOF, kdyz je nalezen konec souboru. Kazda jina hodnota znamena, ze bylo nacteno cislo. Numericka hodnota na- cteneho cisla je predavana argumentem, ktery musi byt pointr. Tento zpusob oddeluje urcovani konce souboru od predani cisel- nych hodnot. Nasledujici cyklus vyplnuje pole celymi cisly, nactene funkci getint:
int n, v, array[SIZE];
for(n = 0; n < SIZE && getint(&v) != EOF; n++)
array[n] = v;
Pri kazdem volani je promenne v prirazeno nactene cislo. Uve- domte si, ze je nezbytne uvest &v jako argument funkce getint. Pouzijeme-li jako argument jenom v, bude ohlasena adresova chy- ba, protoze getint pocita s tim, ze argument je pointr. Funkce getint je modifikaci funkce atoi, kterou jsme jiz drive tvorili:
getint(pn); /*nacti cislo ze vstupu*/
int *pn;
for(*pn = 0; c >= '0' && c <= '9'; c = getch())
*pn = 10 * *pn + c - '0';
*pn *= sign;
if(c != EOF)
ungetch(c);
return(c);
}
Uvnitr funkce getint je *pn pouzita jako normalni promenna typu int. Rovnez jsme pouzili funkci getch a ungetch /popsane v ka- pitole 4./, takze znak, ktery byl nacten navic, muze byt vracen zpatky na vstup.
C v i c e n i 5-1. Napiste funkci getfloat, ktera nacita cislo float, analogickou funkci getint. Jaky typ bude funkce getfloat vracet jako hodnotu?
5.3 Pointry a pole
V jazyce C je uzky vztah mezi pointry a poli. Dokonce tak uzky, ze s poitry a poli muze byt nakladano stejne. Kazda ope- race s prvkem pole muze byt provedena s pointry. Obecne je ver- ze s pointry rychlejsi, ale nekdy je tezsi k pochopeni.
Deklarace int a[10]
definuje pole o delce 10 jako blok deseti po sobe nasledujicich prvku a[0], a[1], , a[9]. Notace a[i] znamena, ze tento prvek je vzdalen i pozic od pocatku. Jestlize je pa pointr na promennou typu integer definovany takto
int *pa;
potom prirazeni
pa = &a[0];
nastavuje pa na nulny prvek pole a. pa obsahuje adresu prvku a[0]. Nyni prikaz
x = *pa;
zkopiruje obsah a[0] do x.
Jestlize pa ukazuje na urcity prvek pole a, potom pa + 1 ukazuje na dalsi prvek. Obecne pa - i ukazuje na i-ty prvek vlevo a pa + i na i-ty prvek vpravo. Proto kdyz pa ukazuje na a[0], tak
*(pa + 1);
ukazuje na prvek a[1] pa + i je adresa a[i] a *(pa + i) je obsah prvku a[i]. Tyto poznatky plati nehlede na to, jakeho typu jsou promen- ne pole a. Definice 'pricti 1 k pointru' a obecne cela aritme- tika pointru je zalozena na tom, ze prirustek je vynasoben velikosti objektu, na ktery pointer ukazuje. Proto v pa + i je i vynasobeno rozmerem objektu, na ktery ukazuje pointr pa. Souhlas mezi indexovanim a aritmetikou pointru je zrejmy. Ve skutecnosti je odkaz na pole prekladacem preveden na pointr na zacatek pole. Z toho plyne, ze nazev pole j e vlastne pointr. To je velice uzitecny zaver. Protoze jmeno pole je synonymem pro umisteni nulteho prvku, pak prirazeni
pa = &a[0];
muze byt zrovna tak napsano
pa = a;
Jeste vice prekvapive, alespon na prvni pohled, je fakt, ze.po 3 odkaz na a[i] muze byt napsano jako *(a + i). Prekladac jazyka C totiz vyraz a[i] prevadi vzdy na *(a + i). Tyto formy jsou naprosto ekvivalentni. Aplikujeme-li operator & na obe casti teto ekvivalence dostaneme, ze &a[i] a a + i jsou rovnez iden- ticke: a + i je adresa i-teho prvku pole a. Druhou stranou teto mince je to, ze je-li pa pointr, tak muze byt pouzivan s indexem: pa[i] je totozne s *(pa + i). Kratce receno indexovy vyraz z libovolneho pole muze byt napsan jako pointr a offset a naopak; dokonce ve stejnem prikazu. Je jenom jeden rozdil mezi jmenem pole a pointrem, ktery si musime uvedomit. Pointr je promenna a proto pa = a a pa++ jsou dovolene operace. Nazev pole je naproti tomu k o n- s t a n t a a ne promenna. Proto jsou vyrazy typu a = pa nebo a++ nebo p = &a neplatne. Kdyz je funkci predavano jmeno pole, tak je predana adresa pocatku pole. Uvnitr volane funkce je argument jiz ale normal- ni promennou a tak jmeno pole je opravdu pointr, tj. promenna obsahujici adresu. Tento fakt muzeme pouzit k napsani nove verze funkce strlen, ktera pocita delku retezce
strlen(s) /*zjisteni delky retezce*/
char *s;
Zvetsovani promenne s je dovolene, protoze to je pointr. s++ nijak neovlivnuje retezec ve funkci, ktera strlen vola, ale jenon zvetsuje privatni kopii adresy tohoto pole. Formalni parametr ve funkci muze byt definovan bud takto
char s[];
nebo takto
char *s;
Obe definice jsou totozne. Ktera z nich ma byt pouzita zalezi vyhradne na tom, v jakem tvaru budou psany vyrazy teto funkce. Jestlize je funkci predano pole, funkce predpoklada, ze ji bylo predano pole nebo pointr a podle toho s nim take naklada. Fun- kci je take mozno predat pouze cast pole predanim pointru zacatku tohoto podretezce. Napr. je-li a pole, potom
f(&a[2])
a
f(a+2)
oboji predava adresu prvku a[2], protoze &a[2] a a+2 jsou vyra- zy, ktere ukazuji na treti prvek pole a. Ve funkci f muze byt provedena deklarace takto .pa.po 12
f(arr)
int arr[];
nebo takto
f(arr)
int *arr;
Co se tyce funkce f same, tak vubec nezalezi na tom, ze argument ukazuje pouze na cast nejakeho vetsiho pole.
5.4 Adresová aritmetika
Jestlize je p pointr, potom operace p++ zvetsuje p a p po- tom ukazuje na dalsi prvek a p++ = i zvetsuje p a i tak, ze ukazuje o i prvku dal. Tyto a jim podobne operace jsou nej- jednodussi a nejvice pouzivanou formou adresove aritmetiky. C je konzistentni a regularni v pristupu k adresove aritme- tice. System pointru poli a adresove aritmetiky je hlavni silou jazyka. Ilustrujme nektere vlastnosti tim, ze napiseme jednodu- chy program pro alokaci pameti. Budou to dva podprogramy: alloc(n) vraci pointr p na n po sobe jdoucich znakovych pozic, ktere mohou byt pouzity pro ulozeni znaku. Funkce free(p) uvol- nuje alokovanou pamet. Funkce jsou opravdu 'zakladni', protoze funkce free musi byt vyvolana, je-li vyvolana funkce alloc. To znamena, ze pamet obhospodarovana temito funkcemi je zasobnik neboli LIFO /last in first out/. Ve standartni knihovne jsou obdobne funkce, ktere nemaji dana omezeni a v kap.8 ukazeme zdokonalenou verzi. Zatim ale potrebujeme pouze trivialni fun- kci alloc, abychom mohli alokovat pamet nezname velikosti v ruznych chvilich. Funkce alloc bude 'podavat kousky pole', nazvaneho allocbuf. Toto pole bude soukrome pro funkce free a alloc. Protoze tyto funkce pracuji s pointry a nikoliv s idexy, jine funkce nemusi o tom poli nic vedet. Toto pole muze byt tedy deklarovano jako extern static, coz znamena, ze je mistni ve zdrojovem souboru obsahujici funkce alloc a free a mimo ne je 'nevi- ditelne'. Toto pole nemusi mit ani jmeno. Muze byt ziskano tak, ze pozadame operacni system o nejaky nepojmenovany blok pame- ti. Dalsi informace, kterou potrebujeme znat je to, jak casto je allocbuf vyuzivano. Budeme pouzivat pointr nazvany allocp na dalsi volny prvek. Jestlize pozadame funkci alloc o ,n znaku, tak alloc zjisti, jestli je jeste misto v poli allocbuf. Jestlize je, tak alloc vrati stavajici hodnotu pointru allocp /tzn. zacatek volneho bloku/ a zvysi hodnotu allocp o n. free(p) nastavi allocp na p, kdyz p je uvnitr allocbuf..po 3
#define NULL 0 /*pointr pro chybove hlaseni*/
#define ALLOCSIZE 1000 /*maximalni dosazitelna pamet*/
static char allocbuf[ALLOCSIZE]; /*pamet pro alloc*/
static char *allocp = allocbuf; /*dalsi volna pozice*/
char *alloc(n) /*vrat ukazatel na n znaku*/
int n;
else /*malo mista*/
return(NULL);
}
free(p) /*volna pamet*/
char *p;
Obecne muze byt pointr inicializovan zrovna tak jako kazda jina promenna, prestoze normalne ma jedine vyznam NULL, nebo vyraz zahrnujici adresy drive definovanych dat shodneho typu. Deklarace
static char *allocp = allocbuf;
definuje allocp jako znakovy pointr a inicializuje jej tak,ze
ukazuje na allocbuf, coz je vlastne prvni volne misto v pameti, kdyz program zacina cinnost. To by ale take stejne dobre mohlo byt napsano ve tvaru
static char *allocp = &allocbuf[0];
protoze jmeno pole j e adresa jeho nulteho prvku.
Podminka
if(allocp + n <= allocbuf + ALLOCSIZE)
testuje, zda je jeste dostatek pameti pro n znaku. Jestlize je, tak allocp bude maximalne o jednu za koncem pole allocbuf. Jestlize pozadavek muze byt splnen, tak alloc vraci normalni pointr /vsimnete si vlastni definice funkce/. Jestlize nemuze byt pozadavek splnen, tak alloc musi nejak tuto skutecnost signalizovat. V jazyku C je zaruceno, ze zadny pointr nebude obsahovat nulu jako hodnotu, a proto nule muze byt pouzita pro tuto signalizaci. Piseme radeji NULL nez nula, protoze to je jasnejsi. Pointrum obecne vzato nemohou byt prirazena cis- la integer. Nula je ale specialni pripad. Podminky jako
if(allocp + n < = allocbuf + ALLOCSIZE)
a.po 12
if(p > = allocbuf && p < allocbuf + ALLOCSIZE)
ukazuji dalsi uzitecne vlastnosti aritmetiky pointru. Pointry mohou byt za urcitych okolnosti porovnavany. Jestlize p a q ukazuji na prvky tehoz pole, tak relace <, >= atd. maji vyznam p < q muze byt pravda, napr. jestlize p ukazuje na drivejsi clen pole nez q. Relace == a != je rovnez mozno pouzit. Libovolny pointr muze byt porovnan s NULL. Ale vsechny vyhody jsou pryc, poro- vnavate-li pointry, ktere ukazuji kazdy na neco jineho. Jestli- ze mate stesti, tak program nebude pracovat na zadnem pocitaci. Jestlize ale stesti nemate, tak program bude na jednom pocitaci radne pracovat a na druhem zkolabuje. Dale jsme si mohli vsimnout, ze pointr a cislo integer mohou byt secteny nebo odecteny. Konstrukce
p + n
znamena n-ty prvek za mistem, kam ukazuje pointr p. Pocitac vy- nasobi n odpovidajicim rozmerem objektu, na ktery pointr ukazu- je. Napr. na pocitaci PDP-11 je pro char nasobny faktor 1, pro int a short 2, pro long a float 4 a pro double 8. Odecitani pointru ma take vyznam; jestlize p a q ukazuji do stejneho pole, pak p-q je pocet prvku mezi p a q. Tohoto faktu muze byt pouzito pro novou variantu funkce strlen
strlen(s) /*vypocet delky retezce s*/
char *s;
V deklaraci je p inicializovano na s, to znamena, ze ukazuje na jeho prvni znak. V cyklu while jsou znaky testovany na 0. Protoze 0 je nula a protoze while testuje, zda je vyraz nulo- vy, muzeme vynechat explicitni text a cyklus muzeme psat
while(*p)
p++;
Protoze p ukazuje na znak, p++ posouva p na dalsi znak a p-s udava pocet znaku, o ktery je p posunuto - tj. delka retezce. Aritmetika pointru je konzistentni. Jestlize pracujeme s float, p++ se posune na dalsi float. Tak muzeme napsat dalsi funkci alloc, ktera bude pracovat s float misto s char. Toho docilime tim, ze vsude ve funkcich alloc a free nahradime deklaraci char deklaraci float. Operace s pointry budou provadeny opet sprav- ne. Jine operace s pointry, nez o kterych jsme se zde zminili, jsou nedovolene. Nemuzeme scitat dva pointry, nasobit je, nebo k nim pricitat cisla float a double.
5.5 Znakové pointry a funkce
Z n a k o v a k o n s t a n t a, psana jako 'I am a string' je znakovym polem. Ve vnitrni interpretaci prekladac toto pole zakoncuje znakem 0, takze program snadno nalezne konec. Poza- davek na pamet je tady o jednotku vyssi nez skutecna delka retezce. Pravdepodobne se retezce nejcasteji vyskytuji jako para- metry funkci printf('hello, worldn'); Jestlize se takovyto retezec znaku objevi v programu, tak pri- stup k nemu je zprostredkovan pointry. Funkce printf ve skutec- nosti obdrzi pointr na tento retezec znaku. Znakova pole nemusi ale byt pouze argumenty funkci. Jestlize message je deklarovano takto
char *message;
potom prikaz
message = 'now is the time';
priradi message pointr na skutecny retezec. Neni to k o p i e retezce. C neumoznuje praci s retezci jako s jednotkami. Dalsi vlastnosti pointru a poli budeme ilustrovat dvema uzi- tecnymi funkcemi ze standardni knihovny vstupu a vystupu, ktere budou probrany v kapitole 7. Prvni funkci je strcpy(s,t), ktera kopiruje retezec t do retezce s. Argumenty jsou v tomto poradi podle analogie a pri- razovacim prikazem
s = t;
Prvni verze s pouzitim poli
strcpy(s, t) /*kopiruj t do s*/
char s[], t[];
Pro srovnani nyni strcpy s pointry
strcpy(s, t) /*kopiruj t do s; verze s pointry*/
char *s, *t;
}
Protoze argumenty jsou predavany hodnotou strcpy muze pouzit s a t jak chce. Prakticky strcpy nebude napsana tak, jak jsme uvedli. Dalsi moznost je:
strcpy(s, t) /*kopiruj t do s; 2. verze s pointry*/
char *s, *t;
Tato verze zvetsuje s i t v testovaci casti. Hodnota *t++ ma hodnotu znaku, na ktery t ukazuje jeste pred zvetsenim. Postfix ++ nemeni t dokud nebyl vybran prvek. Podobnym zpusobem je znak ulozen na starou hodnotu s. Tento znak je take porov- nan s 0. Vysledkem je, ze jsou kopirovany znaky retezce az po znak 0 vcetne. Znovu si uvedomime, ze porovnavani s 0 je zbytecne a napiseme konecnou verzi
strcpy(s, t) /*kopiruj t do s; 3. verze s pointry*/
char *s; *t;
Prestoze se to na prvni pohled muze zdat nesrozumitelne, vy- hoda je zrejma a tento tvar se nam musi vzit uz jen proto, ze se casto v C programech uziva. Druhou funkci je funkce strcmp(s,t), ktera porovnava znakove retezce s a t a vraci bud zapornou hodnotu, nulu nebo kladnou hodnotu podle toho, je-li retezec s lexikalne mensi, roven nebo vetsi nez t. Vracena hodnota je rozdilem prvnich dvou znaku, ve kterych se retezec s a t lisi.
strcpm(s,t) /*vrat<0 kdyz s<t,0 kdyz s==t,>0 kdyz s>t*/
char s[], t[];
Verze s pouzitim pointru:
.po 3
strcmp(s, t) /*dtto*/
char *s, *t;
Protoze ++ a -- mohou byt bud pred nebo za promennou, mohou se objevit i jine kombinace ++ a --. Napr.
*++p
zvetsuje p p r e d vybiranim znaku, na ktery ukazuje. C v i c e n i 5-2. Napiste pointrovou verzi funkce strcat, kterou jsme uvedli v kapitole 2. strcat(s, t) kopiruje retzec t na konec retezce s.
C v i c e n i 5-3. Napiste makro pro strcpy.
C v i c e n i 5-4. Prepiste programy z drivejsich kapitol s pouzitim pointru.
5.6 Pointry nejsou celá čísla
V minulych programech v jazyce C jste si mohli vsimnout kavalirskeho pristupu k pointrum. Obecne plati, ze na mnoha po- citacich je pointrem prirazeno cele cislo a naopak. To ale ved- lo k prilisne svobode. V podprogramech, ktere vraci pointry ktere jsou predavany dale, jsou vynechany deklarace pointru. Uvazujme napr. funkci strsave(s), ktera uklada retezec s na bezpecne misto, ktere ziska pomoci funkce alloc. Spravne ma byt napsano takto:
char *strsave(s) /*uloz nekam retezec/*
char *s;
Prakticky se ale vynechava deklarace
strsave(s) /*uloz nekam retezec*/
.po 12
Tato verze bude fungovat na mnoha typech pocitacu, protoze
implicitni hodnota pro funkce a argumenty je int a pointr a int
je mozno casto zamenit navzajem. Nicmene je tento zpusob zapisu
riskantni, protoze priliz zavisi na pouzitem pocitaci. Moudrej- si je psat radne vsechny deklarace. /Program lint nas bude pri takovych konstrukcich varovat/.
5.7 Vícerozměrná pole
C umoznuje pouzivani vicerozmernych poli, prestoze jsou v praxi mnohem mene pouzivany nez pole nebo pointry. V tomto odstavci si ukazeme nektere jejich vlastnosti. Uvazujme o problemu konverze data ze dne a mesice na den v roce a naopak. Napr. 1. brezen je 60. den neprestupneho roku a 61. den roku prestupneho. Budeme definovat dve funkce: day_of_year bude konvertovat mesic a den na den v roce a month_day bude konvertovat den v roce na mesic a den. Protoze tato funkce vraci dve hodnoty, tak month a day budou pointry month_day(1977, 60, &m, &d) nastavi m na 3 a d na 1 /1.brezen/. Obe funkce potrebuji shodne informace: tabulku poctu dni kazdeho mesice. Protoze se pocet dni lisi podle toho, je-li rok prestupny nebo ne, je jednodussi pouzit dvourozmerneho po- le. Funkce bude vypadat takto:
static int day_tab[2] [13] =
;
day_of_year(year, month, day)
int year, mont, day;
month_day(year, yearday, pmonh, pday)
int year, yearday, *pmonth, pday;
Pole day_tab je externi obema funkcim a je prvnim vicerozmernym polem, se kterym jsme se setkali. V jazyku C je dvojrozmerne - pole, jehoz prvky jsou zase jednorozmerna pole. Proto je psano
day_tab[i][j]
spise nez
day_tab[i, j]
jako je tomu v jinych jazycich. Jinak se s dvojrozmernymi poli naklada uplne stejne. Prvky jsou skladany po sloupcich, coz znamena, ze pravy index se meni nejrychleji. Pole je inicializovano seznamem hodnot v zavorkach. Kazda radka dvojrozmerneho pole je inicializovana odpovidajicim podsezna- mem. Prvni prvek pole day_tab jsme inicializovali na nulu, takze muzeme pracovat s celymi cisly 1 - 12 namisto 0 - 11. Jeden prvek navic zde neni rozhodujici a tak se vyhneme ze- sloziteni indexu pole. Jestlize je funkci predavano dvojrozmerne pole, tak v dekla- raci argumentu m u s i byt uveden pocet sloupcu pole. Dekla- race poctu radek neni rozhodujici, protoze je predavan pointr. V tomto pripade je to pointr na objekty, ktere jsou 13-ti di- menzionalnimi poli. Je-li tedy predavano pole day_tab funkci, tak deklarace ve funkci f musi vypadat takto
f(day_tab)
int day_tab[2] [13];
Deklarace argumentu muze stejne dobre vypadat takto
int day_tab[] [13];
protoze pocet radek neni dulezity, nebo takto
int *day_tab[13];
V teto deklaraci je uvedeno, ze argument je pointer na pole 13 celych cisel. Kulate zavorky jsou nezbytne, protoze hranate zavorky maji vyssi prioritu nez *. Bez zavorek bude
int(*day_tab)[13];
deklarovano pole 13 pointru. To uvidime v dalsim odstavci. 5.8 Pole pointru. Pointry na pointry
Protoze pointry jsou promenne, tak muzeme predpokladat, ze muzeme vyuzit pole pointru. Ilustrujeme to na programu, ktery bude tridit soubor radek podle abecedy. /Bude to zjednodusena forma utility sort systemu UNIX./ V kapitole 3 jsme uvedli funkci Shell sort, ktera tridi pole celych cisel. Pouzijeme stejny algoritmus s tim, ze nyni musi- me nakladat s retezci cisel nezname delky, ktere nemohou byt porovnavany nebo premisteny jednou operaci. Potrebujeme vhodnou a dostatecne efektivni datovou reprezentaci radek promenne del- ky. Nyni vstoupi na scenu pole pointru. Jestlize jsou radky, ktere maji byt trideny, ulozeny v jednom poli bez mezer mezi sebou, tak kazda radka muze byt reprezentovana pointrem na jeji prvni znak. Pointry mohou byt ulozeny do pole. Dve radky mohou byt potom porovnavany funkci strcmp. Jestlize chceme prohodit dve radky, potom staci prohodit pouze pointry na ne. To znacne zjednodusuje celou operaci.
Tridici postup sestava ze tri casti:
nacteni vsech radek ze vstupu
trideni
vytisteni serazenych radek
Jako obvykle rozdelime program na funkce, ktere budou vykona- vat jednotlive kroky a hlavni program, ktery vse bude ridit. Odlozme na chvili krok trideni a venujme se datovym struk- turam vstupu a vystupu. Vstupni funkce musi cist a uchovavat znaky kazde radky a sestavit pole pointru na tyto radky. Protoze vstupni funkce muze nakladat pouze s konecnym poctem radek, mohla by vratit nesmyslnou hodnotu, pokud by radek bylo priliz mnoho. Vystupni funkce pouze radky tiskne podle poradi pole pointru.
#define NULL 0
#define LINES 100 /*maximalni pocet radek*/
main() /*trideni vstupnich radek*/
else
printf('input too big to sortn');
}
#define MAXLEN 1000
readlines(lineptr, maxlines) /*cti vsupni radky*/
char *lineptr[];.po 3
int maxlines;
return(nlines);
}
Znak pro novou radku je z konce radek vymazan, aby neovlivnil poradi pro trideni.
writelines(lineptr, nlines) /*vypis radky*/
char *lineptr[];
int nlines;
Hlavni novinkou je deklarace lineptr:
char *lineptr[LINES];
ktera rika, ze lineptr je pole prvku, z nichz kazdy je pointrem na promennou char. To znamena, ze lineptr[i] je znakovy pointr a *lineptr[i] je dany znak. Protoze lineptr je pole, ktere je predavano funkci writelines tak s nim muze byt nakladano jako s pointrem stejnym zpusobem, jako v nasich drivejsich ukazkach. Funkce muze byt napsana takto:
writelines(lineptr, nlines) /*pis vystupni radky*/
char *lineptr[];
int nlines;
lineptr na zacatku ukazuje na prvni radku. Kazdy inkrement jej posouva na dalsi radku a pritom se promenna nlines zmensuje. Kdyz jsme se postarali o vstup a vystup, muzeme prejit ke trideni. Program Shell sort z kapitoly 3 potrebuje ale jiste.po 12 zmeny: musi byt modifikovany deklarace a srovnani musi byt pre- vedeno do specialni funkce. Zaklad algoritmu se nezmenil, coz nam dokazuje, ze je stale dobry.
sort(v, n) /*roztrid retezec v[0]v[n-1]*/
char *v[]; /*vzestupne*/
int n;
}
Protoze libovolny prvek v /neboli lineptr/ je znakovy pointr, tak temp take musi byt znakovy pointr. Napsali jsme tento program tak, aby pracoval co nejrychleji. Mohl by byt ovsem rychlejsi. Mohl by totiz kopirovat radky ze vstupu primo do pole array a ne do pole line a potom dale. Je ale lepsi udelat prvni verzi jasne a o 'ucinnost' se starat pozdeji. Tato uprava by ale program nijak podstatne nezrychli- la. Rozdil by ale byl, poud bychom pouzili nejaky lepsi tridi- ci algoritmus / napr. QUICKSORT/. V kap. 1 jsme si ukazali, ze prikazy while a for provadeji testovani p r e d prvnim vykonanim tela cyklu. To nam zarucu- je, ze program bude fungovt spravne, i kdyz na vstupu nejsou zadne radky. Je dobre si program pro trideni projit a zjisto- vat, co se stane, nebyl-li nacten zadny vstup.
C v i c e n i 5-5. Prepiste readlines tak, aby radky ukladal v jednotce main a ne ve funkci alloc. O kolik bude program rychlejsi?
5.9 Inicializace pole pointru
Napisme funkci month_name(n), ktera vraci pointer na rete- zec, obsahujici jmeno n-teho mesice. To je idealni aplikace in- terniho statickeho pole. Funkce month_day obsahuje svoje vnitr- ni pole retezce znaku a je-li vyvolana, vraci spravny pointr na patricne misto. V teto casti se budeme zabyvat problemem inicializace pole jmen. Syntaxe je obdobna jako drive: .pa.po 3
char *month_name(n) /*vrat jmeno n-teho mesice*/
int n;
;
return((n < 1 || n > 12) ? name[0] : name[n]);
}
Deklarace promenne name, ktera je polem znakovych pointru, je obdobna deklarci lineptr v tridicim programu. Iniciali- zace se provadi jednoduse seznamem znakovych retezcu. Kazdy retezec ma v poli odpovidajici misto. Presneji receno znaky i-teho retezce jsou nekdy ulozeny a pointr na ne je ulozen v name[i]. Protoze neni specifikovana delka pole name, prekla- dac ji sam zjisti z poctu inicializaci.
5.10 Pointry a vícedimenzionální pole
Zacatecnici jsou obcas zmateni rozdilem mezi dvojdimenzi- oalnim polem a polem pointru, jako je napr. pole jmen mesicu v predchozim priklade. Mame-li dany deklarace
int a[10][10];
int *b[10];
tak pouziti a a b je obdobne v tom, ze a[5] [5] a b[5] [5] jsou dovolene reference na jeden prvek int. a je opravdove po- le. Bylo pro nej alokovano 100 prvku a pouzivano normalniho postupu pri vypoctu indexu. V pripade pole b je ale alokova- no pouze 10 pointru. Kazdy musi byt nastaven tak, aby ukazoval na pole typu int. Kdyz predpokladame, ze kazdy pointr ukazuje na pole int o rozmeru 10, tak bude alokovano celkem 100 bunek plus 10 bunek pro pointry. Pole pointru potrebuje tedy vice pa- meti a muze tak pozadovat explicitni inicializaci. Ma ale take dve vyhody: adresovani prvku je provadeno neprimo pointrem a ne nasobenim a scitanim jako u pole normalniho a navic radky mohou mit promennou delku. To znamena, ze ne kazdy prvek b musi uka- zovat na pole o rozmeru 10. Nektery muze ukazovat na 2 pole, druhy na 20 a dalsi na zadny..po 12 Prestoze jsme se zde omezili na prvky typu int, tak nejvet- si pouziti pro pole pointru je takove, jako v pripade pole month_name: tj. ukladani znakovych retezcu ruzne delky. C v i c e n i 5-6. Prepiste funkce day_of_year a month_day s pouzitim pointru.
5.11 Argumenty ve tvaru příkazové řádky
Existuje zpusob, jak predavat argumenty nebo parametry pro- gramu, ktery zacina svou cinnost. Kdyz je spousten program main, tak ma dva argumenty. Prvni z nich /obycejne nazyvany (argc) udava pocet argumentu prikazove radky, kterou byl pro- gram vyvolan. Druhy parametr (argv) je pointr na pole znakovych retezcu, ktere obsahuji argumenty - vzdy jeden na retezec. Nejjednodussi ilustrace pouziti nezbytnych deklaraci je program echo, ktery proste tiskne argumenty. Potom prikaz
echo hello, world
bude mit vystup
hello, world
argv[0] je jmeno programu, ktery byl vyvolan a tak argc je prinejmensim 1. V predchozim priklade je argc 3 a argv[0] je 'echo', argv[1] je 'hello,' a argv[2] je 'world'. Prvnim skutecnym argumentem je argv[1] a poslednim argv[argc-1]. Je-li argc = 1, potom nebyly zadany parametry. Zde je prog- ram echo:
main(argc, argv) /*opakuj argumenty, 1.verze*/
int argc;
char *argv[];
Protoze argv je pointr na pole pointru, tak tento program mu- zeme napsat mnoha zpusoby. Ukazeme 2 varianty.
main(argc, argv) /*opakuj argumenty, 2. verze*/
int argc;
char *argv[];
Protoze argv je pointrem na zacatek pole retezcu argumentu,.po 3
tak zvetsenim o 1(++argv) bude ukazovat na argv[1] a ne na
argv[0]. Kazde zvetseni ho posouva na dalsi argument. Ve stej- nem case je argc zmensovano. Kdyz je nulove, tak jiz nejsou zadne dalsi agrumenty.
main(argc, argv) /*opakuj argumenty 3. verze*/
int argc;
char *argv[];
V teto verzi je ukazano, ze argumenty funkce printf mohou byt vyrazy. Neni to priliz caste, ale stoji za zapamatovani. Ve druhem priklade provedeme vylepseni programu pro vyhle- davani retezcu z kap. 4. Tam jsme retezec, ktery ma byt vyhle- dan zaclenili do programu. Nyni tento program zmenime tak, ze retezec bude zadavan jako argument /obdoba systemove funkce grep v UNIX/.
#define MAXLINE 1000
main(argc, argv) /*nalezni prvni vyskyt retezce s*/
int argc;
char *argv[];
Na tomto zakladnim modelu budeme ilustrovat dalsi konstrukce s pointry. Predpokladejme, ze chceme pouzivat dva argumenty. Je- den rika: 'vytiskni vsechny radky m i m o tu, ve ktere je da- ny retezec' a druhy 'vytiskni kazdou radku s cislem radky'. Zacina-li v C nejaky argument znakem minus, tak je to para- metr. Vybereme si tuto konvenci: -x(except) pro inverzi a -n(numer) pro cislovani. Potom
find -x -n the
se vstupem ve tvaru
now is the time
for all good men
to come to the eid
of their party
vytiskne
2: for all good men
Parametry se mohou vyskytovat v libovolnem poradi a program by nemel byt citlivy na to, kolik argumentu bylo zadno. Kon- kretne, volani programu index by nemelo referovat na argv[2], kdyz tam byl zadan parametr a na argv[1], kdyz tam parametr za- dan nebyl. Navic je pro uzivatele vyhodne, mohou-li byt para- metry slucovany. Tj.
find -nx the
Zde je program:
#define MAXLINE 1000
main(argv, argc) /*nalezni retezec*/
int argc;
char *argv[];
if(argc != 1)
printf('Usage: find -x -n patternn');
else
while(getline(line, MAXLINE) > 0)
}
}
argv je zvetseno pred kazdym parametrem a argc je zmenseno. Jestlize nenastaly chyby, tak na konci musi byt argc=1 a argv ukazovat na retezec, ktery ma byt vyhledan. Uvedomte si, ze .po 3 *++argv je pointr na retezec argumentu; (*++argv)[0] je jeho prvni znak. Zavorky jsou zde nezbytne, protoze jinak by byl tento vyraz chapan takto: *++(argv[0]), coz je nespravne. Dalsi vhodnou formou zapisu je **++argv.
C v i c e n i 5-7. Napiste program add, ktery vycisluje v obracene polske notaci vyraz, zadany jako argument. Napr. add 2 3 4 + * vypocte 2 * (3+4).
C v i c e n i 5-8. Modifikujte program entab a detab /z kap.1/ aby tabelatory byly zadavany jako argumenty. Pouzijte normalni tabelator, nebyl-li zadan zadny argument.
C v i c e n i 5-9. Rozsirte entab a detab tak, aby umoznoval zadai ve tvaru entab m + n coz jest: tabelator stavi kazdy n-ty sloupec, pocinaje na sloupci m.
C v i c e n i 5-10. Napiste program tail, ktery tiskne poslednich n radek ze vstupu. Implicitne bude n=10, ale muze byt zmeneno argumentem tail -n Program by se mel chovat normalne bez ohledu nato, jak nesmy- slna hodnota n je. Napiste program tak, aby co nejlepe vyuzival pamet. Radky by mely byt ukladany stejnym zpusobem jako ve funkci sort a ne jako v dvoudimenzionalnim poli konstantni delky.
Pointry funkcí
Funkce v C neni promenna, ale je mozno defiovat p o i n t r f u n k c e, se kterym muze byt manipulovano /muze byt preda- van jako argument funkcim, ukladan do pole atd./. Budeme to ilustrovat tim, ze zmodifikujeme tridici program tak, ze bude- li uveden parmetr -n, tak rady budou setrideny ciselne a ne podle abecedy. Trideni obvykle sestava ze tri casti: P o r o v n a v a n i, ktere urcuje poradi porovnavaneho paru, v y m e n y, ktera meni poradi paru a t r i d i c i h o a l g o r i t m u, ktery pro- vadi porovnani a vymenu tak dlouho, dokud neni vse utrideno. Tridici algoritmus je nezavisly na porovnavacich operacich a operacich vymeny, takze predanim ruznych porovnavacich funkci a funkci vymeny muzeme provadet trideni podle libovolnych krite- rii. Teto postup bude pouzit v novem tridicim programu. Porovnani podle abecedy provadi funkce strcmp, vymenu fun- kce swap. Dale budeme potrebovat funkci numcmp, ktera radky porovnava na zaklade ciselne hodnoty a vraci stejnou indikaci jako funkce strcmp. Tyto tri funkce jsou deklarovany v jed- notce main a jejich pointry jsou predavany funkci sort. Funkce sort vola funkce pomoci pointru.
#define LINES 100 /*maximalni pocet radek*/
main(argc, argv) /*setrideni vstupnich radek*/
int argc;
char *argv[];.po 12
else
printf('input too big to sortn');
}
strcmp, numcmp a swap jsou adresy funkci. Protoze vime, ze jde o funkce, tak operator & neni nezbytny. Proto ho ta- ke neni treba uvadet pred nazvem pole. Prekladac sam pripra- vi adresy funkci nebo poli. Funkce sort vypada takto:
sort(v, n, comp, exch) /*setrideni v[0]v[n-1]*/
char *v[];
int n;
int(*comp)(),(*exch)();
}
Musime dat pozor na deklarace. Deklarace
int(*comp)()
rika, ze comp je pointr funkce, ktera vraci int. Prvni par za- vorek je nezbytny, protoze bez nich
int*comp()
znamena, ze comp je funkce, ktera vraci pointr, coz jak vidime
je neco zcela odlisneho..po 3 Pouziti funkce comp v radce
if((*comp) (v[j], v[j+gap]) <= 0)
je v souhlase s deklaraci: comp je pointr na funkci, *comp je funkce a
(*comp)(v[j], v[j+gap])
je volani funkce. Zavorky jsou nezbytne. Jeste uvedeme funkci numcmp:
numcmp(s1, s2) /*porovnavani s1 a s2 ciselne*/
char *s1, *s2;
Nakonec uvedeme funkci swap, ktera meni dva pointry:
swap(px, py) /*vymena *px a *py*/
char *px[], *py[];
C v i c e n i 5-11. Upravte sort tak, aby bylo mozno zadat parametr -r, ktery pozaduje trideni sestupne. -r musi rovnez pracovat s -n.
C v i c e n i 5-12. Pridejte parametr -f, ktery dava naroven mala a velka pimena.
C v i c e n i 5-13. Pridejte parametr -d /'slovnikove srovna- vani'/. Potom budou porovnavany pouze pismena, cisla mezery. Zkontrolujte, zda bude pracovat spolu s -f.
KAPITOLA 6. STRUKTURY
S t r u k t u r a je sestava jedne nebo vice promennych stejneho nebo ruzneho typu. Je oznacena jednim jmenem, aby se s ni dalo dobre zachazet. /Struktury jsou nekdy nazyvany 'rekordy' napr. v PASCALU./ Klasickym prikladem jsou osobni data zamestnancu na vy- platni listine. 'Zamestnanec' je popsan mnozinou atributu, jako je jmeno, adresa, rodne cislo, plat atd. Nektere z polo- zek mohou byt opet struktury: jmeno ma vice slozek, adresa a plat take. Struktury pomahaji organizovat slozita data speci- alne v rozsahlych programech, protoze v mnoha situacich umoznu- ji, aby s celou soustavou dat bylo nakladano jako s jednou promennou. V teto kapitole popiseme, jak se pouzivaji struktury. Programy budou vetsi nez ty, ktere jsme dosud poznali, ale budou jeste stale v rozumne velikosti.
6.1 Základy
Podivejme se znova na program z kapitoly 5 zabyvajici se datem. Datum je slozeno z ruznych casti: den, mesic, rok a den v roce a popr. jmeno mesice. Techto pet udaju muze byt ulozeno do jedne struktury:
struct date
;
Klicove slovo struct uvadi deklaraci struktury, ktera je seznamem deklaraci, vlozenych do zavorek. Nepovinnym para- matrem muze byt jmeno, nazyvane oznaceni struktury /tag/. Je uveden za slovem struct. V nasem prikladu je to jmeno date. Oznaceni struktury oznacuje strukturu a muze byt pouzito jako zkratka pro podobnou deklaraci. Prvky nebo promenne uvedene ve strukture jsou nazyvany jejimi c l e n y . Clen struktury nebo jeji oznaceni muze byt shodne se jmenem obycejne promenne, protoze muze byt vzdy odliseno podle kontextu. Ovsem vhodne je pouzivat shodne nazvy pouze pro uzce svazane veliciny. Prava zavorka ukoncuje seznam clenu a za ni muze nasle- dovat seznam obycejnych promennych. Tj.
struct () x, y, z;
je z hlediska syntaxe identicke s
int x, y, z;
Deklarace struktury, za kterou nenasleduje seznam clenu,.po 3 nealokuje zadnou pamet. Popisuje pouze 'sablonu' struktury. Jestlize je struktura oznacena jmenem, tak jmeno muze byt pouzito pro definici aktualnich objektu struktury. Napr. mame-li danu deklaraci struktury date, potom
struct date d;
definuje promennou d, ktera je strukturou typu date. Struk- tura typu static nebo external muze byt inicializovane.
struct date d = ;
Clen struktury muze byt dosazen takto:
oznaceni struktury. jmeno
Operator clenu struktury '.' spojuje jmeno struktury s jejim clenem. Napr. nastaveni promenne leap z data struktury muze byt provedeno takto:
leap = d.year % 4 == 0 && d.year % 100 != 0
|| d.year % 400 == 0;
Kontrola nazvu mesice
if (strcmp(d.mon_name, 'Aug') == 0)
nebo konvertovani prvniho znaku mesice na male pismeno
d.mon_name [0] = lower (d.mon_name[0];
Struktury mohou byt vkladany do sebe. Data zamestnance mohou
vypadat takto
struct person
;
Struktura person obsahuje dva datumy. Jestlie budeme deklaro- vat emp jako
struct person emp;
potom
emp.birthdate.month
udáva měsic narozeni. Operator '.' pracuje zleva doprava.
6.2 Struktury a funkce
V jazyku C existuji omezeni pro struktury. Zakladním pravi-
dlem je to, ze jedine operace, které muzeme se strukturami provádět, je zjitěni adresy operatorem & a pristup k jejím členum. Z toho plyne, ze struktury nemohou byt priřazovány nebo kopirovany jako obyčejne promenne a nemohou byt predavany jako parametry. /Tato omezeni budou v dalsi verzi jazyka C odstraneny./ Pointry na struktury nemaji tato omezeni, takze struktury a funkce mohou dobre spolupracovat. Automaticke struktury stejne jako automaticka pole nemohou byt inicializo- vany. Nektere z techto zaveru si overime pri prepsani funkce pro konverzi data z predchozi kapitoly. Protoze neni mozne predavat struktury funkcim primo, musime bud predavat jedno- tlive prvky nebo pointry na struktury. Prvni varianta pouziva funkci day_of_year z kapitoly 5:
d.yearday = day_of_year(d.year, d.month, d.day);
Druhy zpusob je predani pointru. Jestlize jsme deklarovali hiredate jako
struct date hiredate;
a prepsali funkci day_of_year, muzeme napsat
hiredate.yearday = day_of_year (&hiredate);
Funkce musi byt modifikovana, protoze jejim argumentem je nyni pointr a ne seznam promennych
day_of_year(pd) /*vypocet dne roku z mesice a dne*/
struct date *pd;
Deklarace
struct date *pd
urcuje, ze pd je pointr na strukturu typu date. Notace
pd -> year
je novinkou. Jestlize p je pointr struktury potom
p -> clen struktury
ukazuje na urcity clen. Operator sestava ze znamenka minus a >. Protoze pd je pointr na strukturu, tak clen year muze byt dosazen take takto
(*pd).year
Pointry na struktury jsou casto pouzivany a tak operator -> je vyhodnou zkratkou. Ve vyrazu (*pd).year jsou zavorky nezbytne, protoze operator '.' ma vyssi prioritu nez *. -> a . pracuji zleva doprava
p -> q -> memb
je emp.birthdate.month
(p -> q) -> memb
je (emp.birthdate).month
month_day (pd) /*vypocet mesice a dne ze dne roku*/
struct date *pd;
Operator -> a ., spolu s () pro seznam argumentu a []
pro indexy maji ze vsech operatoru nejvyssi prioritu. Napr.
mame-li danu deklaraci
struct
*p;
potom
++p -> x
zvetsuje x a ne p, protoze prikaz je vykonan takto: ++(p -> x). Pro zmenu priority mohou byt pouzity zavorky: (++p) -> x zvetsuje p pred dosazenim x a (p++) -> x zvetsuje p potom. /Tyto posledni zavorky jsou zbytecne. Proc?/ Stejnym zpusobem *p -> y vybira to, na co ukazuje y: *p -> y++ zvetsuje y po dosazeni objektu /prave tak jako *s++/. (*p -> y)++ zvesuje to, na co ukazuje y. *p++ -> y zvetsuje p po dosazeni toho, na co ukazuje y. .pa
6.3 Pole struktur
Struktury jsou vyhodne hlavne pro operace s poli pro- mennych. Napr. uvazujme program, ktery zjistuje vyskyt klico- vych slov jazyka C. Potrebujeme pole znakovych retezcu, ktera budou obsahovat jmena a pole cisel int, kde bude ukladan pocet. Jednou z moznosti je pouziti dvou paralelnich poli - keyword a keycount:
char *keyword [NKEYS];
int keycount [NKEYS];
Prave fakt, ze jsou pole paralelni indikuje to, ze by byla mozna jina organizace. Kazdy prvek je vlastne par:
char *keyword;
int keycount;
Deklarace struktury
struct key
keytab [NKEYS];
Pole keytab alokuje pamet. Kazdy prvek pole je strukturou. To muze byt napsano takto
struct key
;
struct key keytab [NKEYS];
Protoze struktura keytab obsahuje ve skutecnosti konstan- tni soubor jmen, je usnadnena inicializace. Inicializace struktury je podobna predchozim - definici nasleduje seznam vlozeny do zavorek:
struct key
keytab [] =
;
Inicializace je provedena pary odpovidajicimi strukture clenu. Bylo by presnejsi vkladat patricne pary do zavorek
('break', 0)
('case', 0)
ale neni nutne, protoze v nasem pripade jsou to jednoduche promenne a znakove retezce. Jako obvykle prekladac zjisti pocet clenu z inicializace a zavorky [] mohou zustat prazdne. Program pro zjistovani poctu klicovych slov zacina deklaraci keytab. Hlavni program cte vstup opakovanym volanim funkce getword, ktera nacita ze vstupu vzdy jedno slovo. Pro kazde slovo je prohledana tabulka keytab pomoci funkce pro binarni hledani, kterou jsme uvedli v kapitole 3. /Sez- nam klicovych slov muze byt usporadan vzestupne./
#define MAXWORD 20
main() /* pocitej klicova slova */
binary (word,tab,n) /*najdi slovo v tab[0]tab[n-1]*/
char *word;
struct key tab [];
int n;
return (-1);
}
Na chvili se zastavime u funkce getword. Pro zacatek staci rici, ze vraci LETTER pokazde, kdyz nalezne slovo. Toto slovo kopiruje do prvniho argumentu. NKEYS udava pocet klicovych slov v tabulce keytab. Prestoze bychom ho mohli snadno zjistit sami, je lepsi to ne- chat na pocitaci, zvlaste bude-li se program jeste menit. Rozsah pole je uz znam v dobe prekladu. Pocet prvku je
rozmer keytab /rozmer struct key
Existuje unarni operator sizeof, ktery muze byt pouzit pro urceni delky objektu. Vyraz
sizeof (objekt)
je cele cislo, ktere je rovno velikosti specifikovaneho objektu /delka je udana v 'bytech', ktere maji stejnou velikost jako char/. Objekt muze byt obycejna promenna, pole, struktura, jmeno typu int nebo double, nebo jmeno odvozene od struct jako v nasem pripade. Tohoto vypoctu je pouzito pri vypoctu NKEYS:
#define NKEYS (sizeof(keytab)/ sizeof(struct key))
Nyni k funkci getword. Jiz jsme napsali obecnejsi funkci getword, nez je v nasem priklade potreba. getword vraci 'slovo' ze vstupu. Jmeno je bud retezcem znaku a cisel zacinajici pismenem, nebo jeden znak. Typ objektu je urcen hodnotou, kterou funkce vraci: LETTER bylo-li nacteno slovo, EOF pro konec souboru nebo nacteny nealfanumericky znak. Funkce getword pouziva funkce getch a ungetch, ktere jsme napsali v kapitole 4. Funkce getword vola type pro urceni typu znaku ze vstupu. Nasledujici verze je pouze pro ASCII znaky.
type (c)
int c;
Symbolicke konstanty LETTER a DIGIT mohou mit libovolnou hodnotu, ktera neni v rozporu s nealfanumerickymi znaky a EOF. Obvykle se voli
#define LETTER 'a'
#define DIGIT '0'
Funkce getword by mohla byt rychlejsi, kdyby bylo vola- ni funkce type nahrazeno odkazem na odpovidajici pole type[]. Standardni knihovna jazyka C obsahuje makro nazvane isalpha a isdigit, ktere funguje timto zpusobem.
C v i c e n i 6-1. Provedte tyto zmeny v getword a zmerte rozdil v rychlosti.
C v i c e n i 6-2. Napiste funkci type, ktera je nezavisla na souboru znaku.
C v i c e n i 6-3. Napiste verzi programu pro pocitani kli- covych slov, ktery nepocita vyskyt v retezcich v uvozovkach.
6. 4. Pointry na struktury
Abychom ilustrovali nektere vlastnosti pointru a poli struktur, napisme znovu program pro zjisteni poctu vyskytu jednotlivych klicovych slov. Pouzijme ale pointry a ne indexy pole. Deklarace extern pole keytab muze zustat nezmenena. Musime zmenit main a binary.
main() /*pocitej klicova slova, verze s pointry*/
struct key *binary(word, tab, n) /*najdi slovo*/
char *word;,
struct key tab[];
int n;
return (NULL);
}
Za zminku zde stoji vice veci. Za prve deklarace funkce binary musi indikovat, ze funkce vraci pointr na strukturu typu key. To musi byt deklarovano jak v jednotce main, tak ve funkci binary. Jestlize funkce binary slovo najde, tak vraci pointr na nej. Kdyz slovo nenajde vraci NULL. Za druhe - pristup k prvkum pole keytab je pres pointry. Proto musime zmenit funkci binary. Prostredni prvek nemuze uz byt jednoduse zjistovan vyrazem
mid = (low + high) / 2
protoze soucet pointru produkuje nesmyslny vysledek /dokonce i kdyz je vydelen 2/. To musi byt zmeneno na
mid = low + (high - low) / 2
coz nastavuje mid na prvek na polovine cesty mezi low a high. Vsimnete si rovnez inicializace low a high. Je mozne totiz inicializovat pointr na adresu drive definovanoho objektu.
V main jsme napsali
for (p = keytab; p > keytab + NKEYS; p++)
p je pointr na strukturu a tak kazda operace s p bere v potaz rovnez struktury. p++ zvetsuje p odpovidajicim zpusobem na dalsi pole struktur. Nepredpokladejte ale, ze rozmer struktu- ry je dan pouze souctem rozmeru jejich clenu. Nakonec se podivejme na format programu. Jestlize funkce vraci komplikovany typ jako
struct key *binary(word, tab, n)
tak muzeme v deklaraci jmeno funkce prehlednout. Proto nekdy pouzivame zapis ve tvaru
struct key *
binary(word, tab, n)
To je veci vkusu programatora. Vyberte si jeden zpusob a drzte se ho.
6. 5. Struktury odkazující se samy na sebe Predpokladejme, ze chceme resit obecnejsi problem: pocitat mnozstvi vyskytu v s e c h slov ze vstupu. Protoze ale seznam slov neni na zacatku znam, nemuzeme pouzit binarniho prohleda- vani. Ani linearniho prohledavani nemuzeme pouzit, protoze by program spotrebovaval mnoho casu /pozadovany cas by rostl kva-.po 3 draticky s mnozstvim nactenych slov/. Jak tedy musime organi- zovat data? Jednim z reseni je neustale nacitana slova tridit. To zna- mena ukladat prave nactene slovo tam, kam patri. To ale nemu- zeme delat v linearnim poli, protoze to by rovnez trvalo dlou- ho. Misto toho pouzijeme datovou strukturu nazyvanou b i n a r n i s t r o m. Tento strom obsahuje vzdy jeden uzel pro kazde slovo. Kazdy uzel obsahuje:
pointr na dalsi slovo
pocet vyskytu daneho slova
pointer na levy poduzel
pointer na pravy poduzel.
Zadny uzel nemuze mit vic nez dva poduzly; muze mit jeden nebo nemusi mit zadny. Uzly jsou usporadany tak, ze levy podstrom obsahuje slova, ktera jsou mensi nez slovo v danem uzlu, a pravy podstrom ob- sahuje slova vetsi. Abychom zjistili, zda je prave nactene slovo ve stromu, zacneme u korenu a porovname nactene slovo se slovem v uzlu. Jestlize slovo souhlasi, jsme hotovi. Jestlize je nactene slovo mensi nez slovo v uzlu, pokracujeme do leveho poduzlu; je-li vetsi, tak pokracujeme vpravo. Jestlize ale jiz v danem smeru neni zadny uzel, znamena to, ze nactene slovo neni ve stromu obsazeno a misto pro ne je prave chybejici uzel. Proces vyhledavani je rekurzivni, protoze se pri prohledavani z jednoho uzlu se vyuziva prohledavani z jednoho poduzlu. Ob- dobne proces ukladani a tisku je rekurzivni. Vratme se zpet k popisu uzlu. Je to struktura se ctyrmi polozkami:
struct tnode /*zakladni uzel*/
;
'Rekurzivni' definice uzlu muze vypadat zmatene, ale je
uplne spravna. Je zakazano, aby struktura obsahovala sebe
samu jako polozku, ale
struct tnode *left;
deklaruje text jako p o i n t r na uzel a neobsahuje uzel. Text programu je velice kratky a vyuziva funkce, ktere jsme napsali drive. Je to getword pro nactena slova ze vstupu, alloc, ktera zajistuje misto v pameti pro jednotliva slova. Hlavni program jednoduse cte slova a uklada je do stromu tree
#define MAXWORD 20
main() /*pocitani frekvence slov*/
Funkce tree je jednoducha. Slovo word je ulozeno na vrcho-
lek stromu (root). V kazde fazi je porovnano se slovem uloze- nym v uzlu. Potom se pokracuje do leveho nebo praveho poduzlu rekurzivnim volanim funkce tree. Slovo je bud ve stromu nale- zeno (a je prictena jednicka k mnozstvi vyskytu), nebo je vy- sledkem nulovy pointr, ktery indikuje, ze uzel musi byt ve stromu vytvoren. Jestlize je vytvoren novy uzel, tak tree vra- ci pointr na nej a tento pointr je zarazen do vyssiho uzlu.
struct tnode *tree (p,w) /*ulozeni do p nebo
nize*/
struct tnode *p;
char *w;
else if ((cond = strcmp(w,p -> word)) == 0)
p -> count ++ ; /*opakovane slovo*/
else if (cond < 0) /*je mensi*/
p -> left = tree (p -> left,w);
else /*je vetsi*/
p -> right = tree (p ->
right, w);
return (p);
}
Pamet pro novy uzel je pridelovana funkci talloc, ktera je obdobou funkce alloc, kterou jsme napsali drive. Vraci pointr na volne misto pro uzel stromu. /Za chvili se k tomu vratime./ Nove slovo je kopirovano kamsi v pameti funkci strsave, je ini- cializovan pocet vyskytu a poduzly jsou vynulovany. Tato cast programu je vykonavana pouze tehdy, vytvari-li se novy uzel. Vynechali jsme testovani chyby po navratu z funkci strsave a talloc, coz neni prilis moudre. treeprint tiskne strom levym podstromem pocinajic. V kazdem uzlu tiskne levy podstrom /coz jsou vsechna slova mensi nez slovo v uzlu/, potom slovo samo, a nakonec pravy podstrom /vsechna vetsi slova/. Pokuste se sami si vypsat strom uzitim funkce treeprint; je to jedna z nejjasnejsich rekurzivnich fun- kci, na kterou jste kdy narazili.
treeprint (p) /*tiskni p rekurzivne*/
}
Prakticka poznamka: jestlize strom neni vyvazen, protoze slova
neprichazeji nahodne, cas vypoctu roste prilis rychle. Nejhor- sim pripadem je, prichazeji-li slova jiz usporadana. Program vlastne potom provadi linearni prohledavani. Existuje zobecneni linearnich stromu: stromy typu 2-3 a AVL, ktere jsou vyhodnejsi ale my se o nich zde nebudeme zminovat. Predtim, nez opustime tento priklad, vratme se jeste k pro- blemu pridelovani pameti. Idealne by mel v programu existovat jenom jeden alokacni podprogram a mel by pridelovat pamet ruz- nym objektum. Ale kdyz ma jeden alokacni podprogram pridelit pamet pro pointr na char a pointr na tnode struktury, tak vy- vstavaji dve otazky. Za prve jak je splnena podminka mnoha po- citacu pro zarovnani adresy /napr. int. musi byt umisteno na sude adrese/. Za druhe jak nalozime s tim, ze podprogram vraci pokazde jiny druh pointru? Pozadavky na zarovnani mohou byt snadno splneny za predpo- kladu urciteho plytvani pameti. Alokacni podprogram bude vzdy vracet pointr, ktery splnuje v s e c h n y pozadavky na srovnani pameti. Napr. na PDP-11 mohou byt objekty ulozeny na sudych adresach. Jedinou cenou za to je jeden zbytecny znak na liche adrese. Obdobne je tomu na jinych pocitacich. Proto funkce alloc neni prenosna, ale jeji pouziti je univerzalni. Nase funkce z kapitoly 5 nesplnuje zakladni pozadavky na zarov- navani. V kapitole 8 tuto funkci napiseme spravne. Otazka zpusobu deklarace funkce alloc je klicova pro vsechny jazyky, kde je provadeno kontrolovani typu seriozne. V jazyce C je nejlepsim zpusobem deklarovat, ze alloc vraci pointr na char a potom upravit pointr na pozadovany tvar. Jestlize je p dekla- rovano jako
char *p;
potom
(struct tnode*)p
jej konvertuje na pointr typu tnode. Proto je talloc napsana takto:
struct tnode *talloc()
Je to vice nez pozaduji soucasne prekladace, ale predstavu- je to nejbezpecnejsi pristup k budoucim prekladacum.
C v i c e n i 6-4 Napiste program, ktery cte program v jazy- ku C a tiskne podle abecedy skupiny nazvu promennych, ktere jsou shodne v prvnich sedmi znacich a v ostatich se lisi.
C v i c e n i 6-5 Napiste jednoduchy program pro krizove re- ference. Program ma tisknout vsechna slova ze vstupu a ke kaz- demu slovu uvest cisla radek, ve kterych se vyskytuje.
C v i c e n i 6-6 Napiste program, ktery tiskne rozdilna slo- va ze vstupu setridena sestupne podle frekvence vystupu. Pred kazde slovo vytisknete pocet vyskytu.
6.6 Prohledávaní tabulky
Dalsi vlastnosti struktur budeme ilustrovat souborem progra- mu pro prohledavani tabulek. Tyto programy byvaji soucast pod- programu pro rozvijeni maker a pro ukladani parametru do tabu- lek. Napr. uvazujeme prikaz #define v jazyku C. Jestlize je na radce text
#define YES 1
tak jmeno YES i jeho hodnota 1 jsou ulozeny v tabulce. Pozdeji, kdyz se objevi jmeno YES v prikazu
inword = YES
tak musi byt nahrazeno jednickou. Pro operace s nazvy a jejich nahradami existuji dve zakladni funkce. install(s,t) uklada jmeno s a nahrazujici retezec t do tabulky. s a t jsou znakove retezce. Funkce lookup prohledava tabulku a vraci pointr na misto, kde byl nalezen text, nebo NULL nebyl-li text nalezen. V techto funkcich je pouzito kli- covaciho algoritmu: jmena jsou konvertovana na mala kladna cisla, ktera jsou potom pouzita jako indexy do pole pointru. Prvek tohoto pole ukazuje na zacatek retezce bloku popisuji- cich jmena, ktera maji tuto hodnotu. Vraci NULL, nejsou-li zadna takova jmena. Blok v retezci je strukturou obsahujici pointr na jmena, nahrazujici text a odkaz na dalsi blok v retezci. Nulovy pointr oznacuje konec retezce.
struct nlist /*zakladni polozka*/
;
Pole pointru vypada takto:
#define HASHSIZE 100
static struct nlist *hashtab[HASHSIZE];
/*tabulka pointru*/
Transformacni funkce, ktere je pouzito ve funkcich lookup a install scita hodnoty znaku v retezci a vrati zbytek po de- leni delkou hashovaci tabulky. /Neni to algoritmus nejlepsi, ale je jednoduchy/.
hash(s) /*transformace retezce s*/
char *s;
Tento proces produkuje zacatecni index pole hashtab. At uz byl retezec nalezen kdekoliv, bude ulozen v retezci bloku zaci- najicich zde. Funkce lookup realizuje prohledavani. Jestlize jmeno jiz existuje, vraci pointr na nej. Jestlize ale neexistu- je, vraci NULL.
struct nlist *lookup(s) /*prohledavani*/
char *s;
Funkce install pouziva lookup pro zjisteni, zda je jmeno jiz
ulozeno. Jestlize je ulozeno, tak nova definice musi nahradit starou. Jinak je ulozeno nove jmeno. Install vraci nulu, kdyz neni misto pro novy prvek.
struct nlist *instal (name,def) /*uloz(name,def)*/
char *name, *def; /*do nashtab*/
else /*existuje*/
free (np -> def); /*vymazani*/
if ((np -> def = strsave (def)) == NULL )
return (NULL); return (np);
}
Strsave kopiruje retezec, ktery je jeho argumentem na bezpe- cne misto. Misto je prideleno funkci alloc. alloc jsme ukazali v kapitole 5. Protoze se volani alloc a free muze objevit v libovolnem poradi a protoze je potreba dodrzovat pravidla zaro- vnani adresy, tak tato jednoducha verze nedostacuje. Viz kapi- tolu 7 a 8. C v i c e n i 6-7 Napiste program, ktery vyjme jmeno a nahra- zujici retezec tabulky, obhospodarovane funkcemi lookup a ins- tall.
C v i c e n i 6-8 Implementuje jednoduchou verzi programu, ktery zpracovava prikazy #define v programech v jazyce C. Pou- zijte funkce z teto kapitoly a take funkce getch a ungetch.
6.7 Pole bitu
Jestlize je otazka pameti na prvnim miste, muze byt nezbytne
sloucit ruzne objekty do jednoho pocitacoveho slova. Hodne se pouziva souboru priznakovych bitu v aplikacich typu tabulky symbolu prekladace. Predstavte si fragment prekladace, ktery zachazi s tabulkou symbolu. Kazdy identifikator sebou nese jiste informace. Napr. je-li klicovym slovem, zda je externi nebo externi staticky ne- bo interni atd. Nejkompaktnejsim zpusobem zapisu techto infor- maci je jejich zakodovani do promenne typu int nebo char. Obvyklym zpusobem jak to udelat je definice masek, odpovi- dajicich pozici bitu ve slove:
#define KEYWORD 01
#define EXTERNAL 02
#define STATIC 04
/Cisla musi byt mocninami dvou/. Pristup k bitum bude provaden posouvanim, maskovanim a doplnkovym operatorem, popsanym v ka- pitole 2. Vyraz
flags |= EXTERNAL | STATIC;
Nastavuje bity EXTERNAL A STATIC ve flags, zatimco
flags &= `(EXTERNAL | STATIC);
nuluje tyto bity. Vyraz
if ((flags & (EXTERNAL | STATIC)) == 0)
je pravdivy, jestlize je nektery z obou bitu jednotkovy. Jako alternativu nabizi jazyk C moznost definovani a pristup k polim bitu primo. P o l e b i t u je soustava bitu v jedne promenne int. Syntaxe definice pole bitu a pristup k nemu je zalozen na strukture. Napr. tabulka symbolu #define muze byt nahrazena definici tri poli bitu:
struct
flags;
Tim je nadefinovana promenna flags, ktera obsahuje 3 pole bitu. Pole bitu jsou definovany jako unsigned, protoze to jsou skutecne veliciny bez znamenka. Jednotliva pole bitu jsou: flags.is_keyword, flags.is_extern atd. stejne jako cleny struktur. Pole bitu se chovaji jako male promenne bez znamenka a mohou vystupovat v aritmetIckych operacich jako jine promenne int. Predchozi priklady mohou byt napsany rovnez takto:
flags.is_extern = flags.is_static = 1
nastavuje bity a
flags.is_extern = flags.is_static = 0
je nuluje a
if (flags.is_extern == 0 && flags.is_static == 0)
je testuje. Pole bitu by nemely prekrocit meze promenne int: stane-li se to, tak pole je prirazeno na dalsi adresu. Po- le bitu nemusi mit jmeno. Nepojmenovana pole /dvojtecka s uda- nou delkou/ mohou byt pouzita jako vypln. Pole bitu jsou na nekterych pocitacich prirazovana do pro- menne zleva doprava, na nekterych zprava doleva - zalezi na ty- pu pocitace. Je nutne mit na mysli dalsi omezeni. Pole bitu nemaji zna- menko, mohou byt prirazovany do int nebo unsigned. Nejsou to pole jako takova. Nemaji adresy a tak pro ne nemuze byt pouzi- vano unarni operace &.
6.8 Uniony
Union je promenna, ktera muze obsahovat v ruznych chvilich objekty ruznych typu a velikosti. Uniony umoznuji pracovat s ruznymi typy objektu v jedne oblasti pameti, aniz by vyuzivaly nejake strojove zavisle operace. Pro priklad se vratme opet k tabulce symbolu prekladace. Predpokladejme, ze konstanty mohou byt int, float nebo znakove pointry. Hodnota konstanty musi byt ulozena v promenne odpovi-.po 12 dajiciho typu. Konstanty by mely zabirat stejne misto a nemelo by zalezet na typu. Muzeme pouzit union, kde ruzne promenne mohou sdilet stejne misto. Tak jako u pole bitu je syntaxe de- finice zalozena na strukture:
union u_tag
uval;
Promenna uval bude dostatecne velka, aby mohla obsahovat nejvetsi ze tri typu a nezalezi na hardwaru pocitace. Libovol- ny z techto typu muze byt promenne uval prirazen a potom pou- zit ve vyrazu. Typ musi odpovidat naposled ulozenemu typu. Za- lezi pouze na programatorovi, aby si pamatoval, co do promenne uval ulozit naposledy. Vysledek zalezi na pocitaci pouze tehdy, ulozi-li se do unionu neco jineho, nez co je pozdeji vyuzito. Cleny unionu jsou dosazitelne takto: jmeno unionu.clen nebo
pointr_unionu -> clen
tak, jako ve strukturach. Jestlize do promenne utype ulozime
typ, ktery byl prirazen union uval, pak muzeme psat
if (utype == INT)
printf ('%dn', uval.fval);
else if (utype == FLOAT)
printf ('%fn', uval.fval);
else if (utype == STRING)
printf ('%sb',uval.pval);
else
printf ('bad type %d in utypen', utype);
Uniony se mohou objevit ve strukturach a polich a naopak. Zapis pro dosazeni clenu unionu ve strukture /a naopak/ je identicky s vnorenymi strukturami. Napr. v poli struktury nadefinovanem takto:
struct
uval;
} symtab [NSYM];je promenna ival urcena takto
symtab[i].uval.ival
a prvni znak retezce pval takto
*symtab[i]uval.pval
Ve skutecnosti union je struktura, ve ktere vsechny cleny maji relativni posun adresy nulovy, struktura je dostatecne velka, aby mohla 'pojmout nejsirsi' clen a zarovnanim adresy plati pro vsechny cleny unionu. Jedina povolena operace s uni- ony je dosazeni jejiho clenu a urceni jeji adresy. Uniony nemo- hou byt parametry funkci, nemohou stat na leve strane prirazo- vaciho prikazu a nemohou byt vraceny funkcemi. Pointry na uni- ony mohou byt uzivany stejnym zpusobem jako pointry na struk- tury. V kapitole 8 ukazeme, jak uniony mohou byt pouzivany. 6.9 Příkaz typedef
C umoznuje definovat nova datova jmena napr.
typedef int LENGTH;
LENGTH je synonymum pro int. 'Typ' LENGTH muze byt pouzit v
deklaracich stejnym zpusobem jako int:
LENGTH len, maxlen;
LENGTH lenghts[];
obdobne deklarace
typedef char *STRING;
definuje STRING jako synonymum pro char nebo znakovy pointr, ktery potom muze byt pouzit v deklaracich STRING p, lineptr
[LINES], alloc();
Uvedomte si, ze typ, ktery je deklarovan v prikazu typedef se objevuje na pozici jmeno promenne a ne primo za slovem type- def. Syntakticky je typedef na stejne urovni jako extern, sta- tic atd. Pouzili jsme rovnez velkych pismen, abychom zduraznili jmeno. Jako slozitejsi priklad uvedeme definici uzlu stromu s pou- zitim typedef:
typedef struct tnode /*zakladni uzel*/
TREENODE, *TREEPTR;
Tato definice vytvari dve nova slova: TREENODE /struktura/ aTREEPTR /pointr na strukturu/. Potom funkce talloc muze byt napsano takto:
TREEPTR talloc();
char *alloc();
return((TREEPTR) alloc (sizeof (TREENODE)));
Musime zduraznit, ze prikaz typedef n e v y t v a r i nove datove typy. Pridava pouze dalsi jmena existujicim typum. Ne- ni ani pouzito nove semantiky: promenne deklarovane timto zpusobem maji presne stejne vlastnosti, jako kdyby byly defi- novany normalne. Ve skutecnosti je typedef obdobou prikazu
#define, ale je interpretovan prekladacem.
typedef int (*PFI)();
Vytvari typ PFI pro 'pointr na funkci, ktera vraci int', ktere-
ho muze byt pouzito v
PFI strcmp, numcmp, swap;
v programu sort z kapitoly 5. Jsou dva zpusoby pro pouzivani deklarace typedef. Prvni du- vod je, ze umoznuje jednoduse resit problemy presnosti progra- mu. Je-li nektery typ zavisly na pouzitem pocitaci, tak staci zmenit pouze jeden prikaz typedef. Je take mozne pouzivat jmen definovanych v typedef pro ruzne typy int a pak konkretne dosa- dit short, int, long dle potreby pouziteho pocitace. Druhym du- vodem je zajisteni lepsi dokumentace programu: typu nazvanemu TREEPTR je lepe rozumet, nez je-li definovan pouze jako pointr na strukturu. Je take mozne, ze v budoucnu bude prekladac nebo program lint uzivat informace obsazene v typedef pro kontrolu spravnos- ti programu.
KAPITOLA 7: VSTUP A VÝSTUP
7.1 Pristup do standardni knihovny
Kazdy zdrojovy soubor, ktery potrebuje funkce ze standardni knihovny musi obsahovat radek
#include <stdio.h>
na zacatku souboru. V soubotu stdio.h jsou definovana jista makra a promenne, ktere jsou standardni I/O knihovnou vyuziva- ny. Pouzitim znamenek < a > misto uvozovek smerujeme prekladac do adresare, ktery obsahuje standardni zaznamy se zahlavim (na systemu UNIX je to |usr|include). Nekdy muze byt nutne definovat knihovnu pri zavadeni progra- mu explicitne. Napr. na PDP 11, UNIX bude prikaz pro preklad programu vypadat takto:
CC zdrojovy soubor, -&S
kde -&S znamena nacitani ze standardni knihovny.
7.2. Standardní vstup a vystup - getchar a putchar
Nejjednodussim mechanismem nacitani je cteni znaku ze 'standardniho vstupu', obecne uzivatelova terminalu funkci getchar (). Funkce getchar () vraci dalsi znak ze vstupu pokazde, kdyz je vyvolana. Na mnoho systemech, kde je imple- nutovan jazyk C, muze byt vstup z terminalu nahrazen vstupem ze souboru operatorem <. Jestlize program p r o g pouziva funkce getchar, tak prikazova radka
prog < infile
zpusobi, ze prog bude cist vstup ze souboru infile misto z terminalu. Toto prepinani vstupu je delano takovym zpusobem,ze program prog o nem 'nevi'. Retezec '<infile>' neni rovnez zarazen do argumentu argv. Prepinani vstupu si rovnez muzeme vsimnout pri situaci, kdyz vystup jednoho programu je vstupem programu dalsiho (pipe line). Prikaz
program1 | program2
spusti dva programy: program1 a program2 a vystup z programu1 bude vstupem programu program2.
Funkce getchar vraci EOF, kdyz narazi na konec vstupu, at je vstupem cokoliv (terminal nebo soubor). Ve standardni knihovne je definovan EOF jako - 1 (v souboru stdio.h). Testovani ma byt provadeno s EOF a ne s -1, aby bylo nezavisle na konkretni hod- note. Pri vystupu funkce putchar (c) 'zapise' znak c do 'standardniho vystupu', coz je standardne rovnez terminal. Vystup muze byt presmerovan znakem > do souboru. Jestlize program prog pouziva putchar, tak prikazem
prog > outfile
bude prog svuj vystup psat do souboru outfile. V systemu UNIX muze byt rovnez pouzit znak | pro retezeni programu (pipe line)
program1 | program2
Ani zde se programy 'nestaraji' o zmenu vstupu a vystupu. Vystupy z funkce printf a putchar mohou byt vzajemne zameno- vany. Prekvapive velke mnozstvi programu cte pouze jeden vstup a zapisuje na jeden vystup. Pro tyto programy jsou funkce getchar, putchar a printf zcela postacujici. Mame navic moznost presmerovani vstupu a vystupu a moznost retezeni programu. Uvazujeme napr. program l o w e r , ktery meni velka pismena ze vstupu na mala
# include <stdio.h>
main () /* konvertovani velkych pismen na mala
'Funkce' isupper a tolower jsou ve skutecnosti makra definovana ve stdio.h. Makro insupper testuje, zda jeho argument je velke pismeno. Kdyz je argument velke pismeno, tak vraci nenulovou hodnotu a nulu vraci v opacnem pripade. Makro tolower konvertu- je velka pismena na mala. Nehlede na to, jak jsou tyto funkce implementovany na ruznych pocitacich, tak efekt je vzdy a vsude stejny a program se nemusi starat o soubor znaku na danem pocitaci. Pro konvertovani vice souboru muze pouzit program cat
cat soubor1 soubor2 | lower > output
a nemusime se ucit, jak se ma k souborum pristupovat. (programcat bude popsan dale v teto kapitole). Standardni 'funkce' vstupu a vystupu putchar a getchar mohou byt ve skutecnosti makra a tak se nemusime starat o konvenci volani funkci. Jak to je udelano ukazeme v kapitole 8.
7.3. Formátový výstup - printf
Dve rutiny printf pro vystup a scanf pro vstup (bude popsano v pristim odstavci) umoznuji translaci cisel na znakovou repre- zentaci. Umoznuji rovnez zapis a cteni formatovanych radek. Funkce printf jsme uzivali v predchozich kapitolach, aniz bychom znali jeji presny popis. Zde uvedeme kompletnejsi presnejsi definici
printf (control,arg1,arg2,)
Funkce prinf provadi konverze cisel a formatovy vystup do standardniho souboru. Format je udavan ridicim retezcem c o n t r o l. Tento retezec obsahuje dva typy objektu: obycejne znaky, ktere jsou normalne kopirovany na vystup a konverzni specifikace, z nichz kazda plati pro patricny argument funkce printf. Kazda konverze je uvedena znakem % a je ukoncena znakem pro typ konverze. Mezi znakem % a znakem typu konverze muze byt:
Znamenko - , ktere zpusobuje zarovnani argumentu na po-
zici doleva.
Cislo udavajici minimalni delku pole pro zobrazovany
retezec. Konvertovane cislo bude vytisteno do pole
minimalne teto delky a popr. bude pouzito pole sirsiho,
bude-li to nezbytne. Jestlize ma konvertovane cislo
mene znaku nez specifukuje delka pole, tak vystup bude
'vyplnen' nalevo (nebo napravo bylo-li specifikovano
znamenko - ). Vyplnovaci znaky jsou normalne mezery.
Byla-li delka pole specifikovana s uvodnimi nulami,
budou vyplnovaci znaky nulami.
Tecka, ktera oddeluje delku pole od dalsiho cisla. Ciselny retezec (presnost) ktery udava maximalni pocet znaku, ktere maji byt tisteny napravo od desetinne tecky pro cisla float nebo double.
Modifikator delky l (pismeno l), ktere indikuje to, ze argu- mentem je long.
Typ konverze muze byt nasledujici:
d argument je konvertovan na desitkovy zapis
o argument je konvertovan do osmickove soustavy
bez znamenka (bez uvodnich nul)
x argument je konvertovan na hexadecimalni tvar
bez znamenka (bez uvodnich nul)
u argument je konvertovan na desitkovy zapis bez znamenka
c argument je dan jako jeden znak
s argument je retezec. Retezec je tisten, az do znaku
0 nebo do delky, ktera byla specifikovana
e argument je bran jako float nebo double a konverto-
van na desitkovy zapis ve forme [-]m.nnnnnne[+]xx,
kde delka retezce je specifikovana presnost (impli
citne 6)
f argument je chapan jako float nebo double a konver-
tovan na desitkovy zapis ve tvaru [-]mmm.nnnnnn ,
kde delka retezce n je specifikovana presnosti
(implicitne 6), presnost nijak neovlivnuje tisk
vyznamovych cislic
g pouziva %e nebo %f podle toho, co je kratsi,
nevyznamne nuly nejsou tisteny
Jestlize znak za znamenkem % neni znakem konverze, tak je nor- malne vytisten. % muze byt vytisknuto takto: %%. Vetsina formatu jiz byla pouzita a vysvetlena v predchozich kapitolach. Jedinou vyjimkou je zadani presnosti a jeji vliv na tisteny retezec. V nasledujici tabulce jsou probrany ruzne varianty specifikace pri tisku retezce 'hello, word',ktery ma 12 znaku. Retezec jsme v prikladech ohranicili znaky:
:%10s: :hello, world:
:%-10s: :hello, world:
:%20s: : hello, world:
:%-20s: :hello, world :
:%20.10s: : hello, wor:
:%-20.10s: :hello, wor :
:%.10s: :hello, wor:
V a r o v a n i: prvni argument funce printf udava, kolik
argumentu nasleduje a jakeho jsou typu. Jestlize je argumentu
mene, nebo jsou nespravneho typu, tak vystup bude nesmyslny.
C v i c e n i 7.1. Napiste program, ktery bude tisknout udaje
ze vstupu rozumnym zpusobem. Minimalne by mel nepismenove znaky tisknout v osmickove nebo sestnactkove soustave a rozde- lovat dlouhe radky.
7.4. Formátový vstup - scanf
Funkce scanf je funkce analogickou funkci printf. Provadi obdobne konverze opacnym smerem.
scanf(control,arg1,arg2,)
scanf cte znaky standardniho vstupu a interpretuje je podle formatovych specifikaci, uvedenych v retezci control a uklada do argumentu. Retezec c o n t r o l bude dale popsan. Ostatni argumenty, z n i c h z k a z d y m u s i b y t p o i n t r urcuji, na ktere misto bude ukladan konvertovany vstup. Retezec control obvykle obsahuje znaky konverznich specifikaci, ktere jsou pouzivany k prime konverzi. Retezec control muze obsahovat:
Mezery, tabelatory nebo znaky pro novou radku
'oddelovace', ktere jsou ignorovany
Obycejne znaky (vyjma %), o kterych se predpoklada, ze
budou souhlasit se znaky ze vstupu.
Konverzni specifikace, sestavajici ze znaku %, nepo-
vinneho znaku * pro potlaceni prirazeni, nepovinneho
poctu udavajiciho maximalni delku pole a znaku typu
konverze.
Konverzni specifikace je aplikovana na dalsi vystupni pole. Normalne je vysledek ulozen do promenne, na kterou ukazuje nasledujici argument. Jestlize se ve specifikaci objevi znak *, je vstup 'preskocen'. Vstupni pole je definovano jako rete- zec, ktery neobsahuje oddelovace. Jeho delka omezena dalsim oddelovacem nebo specifikaci delky. Scanf tedy nebude pri nacitani znaku omezovana jednotlivymi radky, protoze znaky pro novou radku jsou pro tuto funkci normalnimi oddelovaci. Znak typu konverze urcuje interpretaci vstupniho pole.
Odpovidaji argument musi byt pointr.
Nasledujici konverze jsou povoleny:
d ocekava se cislo v desitkovem zapisu.
Odpovidajici argument by mel byt pointr na cele
cislo int
o cislo v osmickovem tvaru je ocekavano odpovidajici
argument musi byt pointr na int.
x je ocekavano cislo hexadecimalnim tvaru. Odpovida-
jici argument musi byt pointr na int.
h je ocekavano cislo typu short int. Odpovidajici
argument musi byt pointr na short int.
c je ocekavan jeden znak, odpovidajici argument ma
byt pointr na char. V tomto pripade je potlaceno
preskakovani oddelovacu. K nacteni dalsiho znaku,
ktery neni oddelovacem, je treba pouzit %1s.
s je ocekavan znakovy retezec. Odpovidajici argument
musi byt pointr na pole znaku dostatecne velke,
aby se do neho vesel cely vstupni retezec vcetne
ukoncovaciho znaku 0.
f je ocekavano cislo s plovouci desetinnou teckou.
Odpovidajici argument by mel byt pointr na float.
e Vstupni retezec muze obsahovat znamenko, desetinnou
tecku a exponent, obsahujici E nebo e a cislo
integer se znamenkem. e ma stejny vyznam jako f.
Pred znaky konverze o, d, x muze byt uvedeno l (pismeno l), ktere urcuje long int. Obdobne pred znaky e a f muze byt l, ktere urcuje double.
Napr. volani
int i;
float x;
char name[50]; scanf('%d%f%s',&i,&x,name);
Se vstupem ve tvaru
25 54.32E-1 Thompson
priradi promenne i hodnotu 25, x=5.432 a poli name retezec Thompson0. Tato 3 vstupni pole mohou byt od sebe oddelena libovolne mnoha oddelovaci.
V o l a n i
int i;
float x;
char name[50];
scanf('%2d%f%*d%2s',&i,&x,name);
se vstupem ve tvaru
56789 0123 45 a72
priradi promenne i=56, x=789.0, preskoci 0123 a umisti retezec
'45' do name. Dalsi vyvolana vstupni funkce zacne nacitani od pismene a. V techto dvou prikladech je n a m e pole a proto nemusi byt oznaceno znakem &. V dalsim prikladu prepiseme program pro kalkulator z kapito- ly 4 s pouzitim funkce scanf:
#include<stdio.h>
main() /*jednoduchy stolni kalkulator*/
Funkce scanf ukonci cinnost, jestlize vycerpa ridici retezec, nebo narazi-li na vstup, ktery neodpovida zadane specifikaci. Jako hodnotu vraci pocet uspesne nactenych vstupnich polozek. Narazi-li na konec vstupu, vraci EOF. To je odlisne od znaku O, ktery vraci neodpovida-li vstup formatove specifikaci. Po dalsim vyvolani pokracuje funkce nasledujicim znakem.
Z a v e r e c n e v a r o v a n i: argumenty musi
byt pointry! Obvykla chyba je:
scanf('%d',n); misto scanf('%d'&n);
7.5. Formátové konverze u paměti
Funkce scanf a printf maji sve obdoby, nazyvane sscanf a sprintf, ktere provadeji obdobne konverze, ale operuji s retezci misto se soubory. Obecny tvar zapisu.
sprintf(string, sontrol, arg1, arg2, )
sscanf(string, control, arg1, arg2, )
Sprintf konvertuje argumenty stejnym zpusobem jako printf, ale vysledek pise do retezce string misto do standardniho vystupu. Ovsemze string musi byt dostatecne velky. Jestlize je navic znakovym polem a n je cele cislo, potom
sprintf(name,'temp%d',n);
vytvari retezec ve tvaru tempnnn v poli name. nnn je hodnota promenne n. sscanf provadi opacnou konverzi - prohledava retezec podle formatove specifikace v retezci control a hodnoty uklada do
arg1, arg2, coz opet musi byt pointry.
Volani
sscanf(name,'temp%d',&n);
nastavuje n na hodnotu retezce za nazvem temp.
C v i c e n i 7.2 Prepiste kalkulator z kapitoly s pouzitim funkce scanf a sscanf.
7.6. Přístup k souborum
Programy, ktere jsme dosud napsali ctou ze standardniho vstupu a pisou na standardni vystup, ktere jsou programu 'preddefinovany' operacnim systemem. V dalsim kroku napiseme program, ktery pracuje se soubory, ktere k nemu nejsou opercnim systemem implicitne prirazeny. Jednim takovym programem je program cat, ktery retezi skupinu pojmenovanych souboru na standardni vystup. Program cat je pouzivan pro tisk souboru na terminal a je univerzalnim predzpracovacim programem pro ty programy, ktere nemaji pristup k jinemu nez standardnimu vstupu. Napr. prikaz
cat x.c y.c
Tiskne obsah souboru x.c a y.c do standardniho vystupu. Otazkou zustava, jak zabezpecit to, aby soubory byly nacteny - tzn. jak spojit externi jmena souboru s aktualnim prikazem pro nacitani dat. Pravidla jsou jednoducha. Predtim, nez soubor bude cten nebo do nej bude psano, musi byt otevren pomoci standardniho programu fopen. Fopen prijima externi jmena (jako x.c, y.c)provadi interakci s operacnim systemem (detaily nas nemusi zajimat) a vraci interni jmeno, ktere musi byt pouzito v nasledujicim cteni nebo psani. Toto interni jmeno je vlastne pointr (ktery se nazyva p o i n t r n a s o u b o r ) na strukturu, ktera obsahuje informace o souboru. Informace obsahuje udaje o umisteni vyrovnavaci pameti (bufferu), aktualni pozici v teto pameti, zda je soubor cten nebo je do neho psano atd. Programator nemusi znat podrobnosti, protoze casti standardnich definic obsazenych ve stdio.h je definice struktu- ry nazvana FILE. Jedina potrebna deklarace je
FILE *fopen(),*fp;
Definice rika, ze fp je pointr na FILE a fopen vraci pointr na FILE. Uvedomme si, ze FILE je nazev typu zrovna tak jako je int a neni to vlastne nazev struktury. (Podrobnosti, jak vse pracuje pod systemem UNIX jsou dany v ka- pitole 8)
Skutecne volani fopen vypada takto
fp=fopen(name,mode);
Prvni argumentem funkce fopen je n a z e v s o u b o r u name, zadany jako znakovy retezec. Druhy argument, mode, urcuje zpusob pristupu k souboru. Je to rovnez znakovy retezec. Volene zpusoby pristupu jsou: cteni ('r'), zapis ('w') a pripisovani na konec ('a').
Jestlize otevreme soubor, ktery neexistuje, pro zapis nebo pripisovani na konec, tak je soubor vytvoren. Otevreni existu- jiciho souboru pro zapis zpusobi jeho smazani. Pokus o cteni neexistujiciho souboru pusobi chybu. Chyba pri cteni muze na- stat take z dalsich duvodu (napr. cteni souboru, ke kteremu neni povolen pristup). Jestlize pri cteni vznikla chyba, je hodnota pointr fopen rovna NULL (coz je rovnez definovano v stdio.h) Dale je nutno vedet, jak cist a psat do prave otevrenych souboru. Je mnoho moznosti. Funkce getc a putc jsou nejjedno- dussi moznosti. Jako argument je zadan pointr na file.
c=getc(fp)
Vraci znak ze souboru, na nejz ukazuje pointr fp. Je-li dosa- zeno konce souboru, vraci EOF. Putc je inverzi getc:
putc(c,fp);
Zapisuje znak do souboru fp a vraci c. Zrovna tak jako getchar a putchar, getc a putc mohou byt makra. Jestlize je program pusten, tak automaticky jsou otevreny tri soubory a jsou dostupne pointry na tyto soubory. Temito soubory jsou: standardni vstup, standardni vystup a standardni vystup pro chybove hlaseni. Pointr na tyto soubory se jmenuji stdin, stdout a stderr. Normalne jsou spojeny s terminalem, alestdin a stdout mohou byt presmerovany na soubor, nebo pouzity pro retezeni programu (pipe line) viz odst.7.2. Getchar a putchar mohou byt definovany pomoci getc a putc a stdout takto:
#define getchar() getc(stdin)
#define putchar(c) putc(c,stdout)
Pro formatovy vstup a vystup mohou byt pouzity funkce fscanf a fprintf. Tyto funkce jsou identicke s funkcemi scanf a printf s tim ze prvni argumentem je pointr na soubor a retezec control je az druhym parametrem. Nyni muzeme pristoupit k napsani programu cat. Zakladni princip je pouzitelny pro mnoho dalsich programu: jsou-li zadany argumenty v prikazove radce, jsou brany poporadku. Jestlize argumenty zadany nejsou, je pracovano se standardnim vstupem. Tento program muze byt pouzit samostatne, nebo muze byt casti vetsiho celku
#include < stdio.h >
main(argc, argv) /*cat:spojovani souboru*/
int argc;
char*argv];
else
}
}
filecopy (fp) /* zkopirovani fp do standardniho vystupu */
FILE *fp;
Pointry na soubory stdin a stdout jsou preddefinovany v knihov- ne vstupu a vystupu a mohou byt pouzivany vsude tam, kde mohou byt pouzivany pointry FILE *. Jsou to k o n s t a n t y a ne promenne, takze se nepokousejte jim neco prirazovat. Funkce flose je inverzni funkce fopen. Prerusuje spojeni mezi externim nazvem souboru a pointrem. Pointr muze byt dale pouzit pro jiny soubor. Protoze kazdy opreracni system ma omezeni poctu soucasne otevrenych souboru, je vyhodne pouzivatty pointry, ktere uz nejsou potreba - jak jsme to udelali v programu cat. Pro pouziti funkce fclose je dalsi duvod: uvolnuje vyrovnavaci pamet, kterou pouziva putc (funkce fclose je automaticky vyvolana pro kazdy otevreny soubor, kdyz program konci normalne svoji cinnosti)
7.7. Oetřovaní chyb - stderr a exit
Osetreni chyb v programu cat neni idealni. Jestlize jeden ze souboru nemohl byt otevren, tak chybova hlaska je pripojena na konec spojenych souboru. To neni na zavadu tehdy, je-li vystup smerovan na terminal. Je-li ale vystup vstupem pro dalsi program, tak uz tento zpusob neni prijatelny. Druhym vystupnim souborem je stderr. Vystup, ktery je psan na stderr se objevuje na terminalu i tehdy, je-li stdout pres- merovan jinam.
Prepiseme nyni program cat s pouzitim stderr.
#inclunde* <stdio.h>
main(argc, argv) /*cat:spojovani souboru*/
int argc;
char *argv[];
else
exit(0);
}
Program signalizuje chybu dvema zpusoby. Chybove hlaseni jde na stderr, coz je uzivatel terminalu. Rovnez je zde pouzita standardni funkce exit, ktera ukoncuje cinnost programu, je-li vyvolana. Argument funkce exit muze byt testovan nasledujicim progra- mem. Obycejne se pouziva 0 pro znameni, ze vse probehlo radne a nenulova hodnota pro nenormalni ukonceni. Funkce exit vola fclose pro kazdy otevreny vystupni soubor. Obsah vyrovnavaci pameti je pritom pripsan do soubor___u a je vyvolana funkce - exit. Funkce - exit okamzite ukoncuje cinnost programu. Muze byt rovnez primo vyvolana.
7.8. Řádkový vstup a výstup
Standardni knihovna obsahuje funkce fgets, ktera je obdobou funkce getline, kterou jsme pouzivali v teto knize. V o l a n i
fgets(line,MAXLINE,fp);
cte dalsi radku (vcetne znaku pro novou radku) ze souboru fp do znakoveho pole line. Maximalne je nacteno MAXLINE - 1 znaku. Radka je ukoncena znakem 0. Fgets normalne vraci radku. Na konci souboru vraci NULL (nase funkce getline vraci delku radky a NULL pro konec souboru). Funkce fputs pise retezec znaku (retezec nemusi obsahovat znak pro novou radku) do souboru.
fputs(line,fp)
Abychom ukazali, ze na funkcich fgets a fputs neni nic magicke- ho, uvedeme primo jejich kopie ze standardni knihovny
#include <stdio.h>
char *fgets(s,n,iop) /*nacteni n znaku z iop*/
char *s;
int n;
register FILE *iop;
}
fputs(s,iop) /* zapsani retezce s do souboru
iop */
register char *s;
register FILE *iop;
C v i c e n i 7.3. Napiste program, ktery porovnava dva soubory a tiskne prvni radku a pozici, kde se soubory lisi.
C v i c e n i 7.4. Modifikujte program pro nalezeni retezce z kapitoly 5, aby cetl vstup ze vstupnich souboru, ktere budou zadany jako jeho argumenty. Jestlize zadny parametr nebyl za- dan, tak bude cist ze standardniho vstupu. Muze byt vytistenojmeno souboru, ve kterem je retezec nalezen? C v i c e n i 7.5. Napiste program, ktery tiskne skupinu sou- boru, kazdy na novou stranku. Tisknete take nazev souboru a cislujte stranky pro kazdy soubor zvlast.
7.9. Některé dalí funkce
Nektere funkce ze standardni knihovny jsou mimoradne uzitec- ne. Jiz jsme se zminili o funkcich strlen, strcpy, strcat a strcmp. Zde jsou nektere dalsi. Testovani znaku a konverzni funkce
Jsou to tato makra
isalpha(c) nenulova, je-li c pismeno,
nulova, jestlize neni
isupper(c) nenulova je-li c velke pismeno
nulova, jestlize neni
islower(c) nenulova, je-li c male pismeno,
nulova, jestlize neni
isdigit(c) nenulova, je-li c cislice,
nulova, jestlize neni
isspace(c) nenulova, jestlize c je mezera,
nulova, jestlize neni
toupper(c) konvertuje c na velke pismeno
tolower(c) konvertuje c na male pismeno
Ungetc
Ve stantardnim knihovne je hodne omezena verze nasi funkce ungetch, kterou jsme napsali v kapitole 4.
ungetc(c,fp)
Vraci znak zpet do souboru fp. Je dovoleno vracet pouze jeden znak na radku. Ungetc muze byt pouzita s funkcemi vstupu a vys- tupu a makry jako jsou scanf, getc nebo getchar.
Systemova volani
Funkce system (s) vykona prikaz, ktery je specifikovan v re- tezci s a vraci se zpatky do programu. Obsah retezce s zalezi na operecnim systemu. Napr. v systemu UNIX radka
system('date')
spusti program date.
Organizace pameti
Funkce calloc je obdobou funkce alloc, kterou jsme pouzivali v predchozich kapitolach
calloc(n,sizeof(objekt))
Vraci pointr na misto v pameti, kam lze umistit n objektu spe- cifikovane delky. Neni-li pozadovana pamet dostupna, vraci NULL. Alokovana pamet je vynulovana. Pointr je radne nastaven na pozadovany objekt, ale mel by byt nastaven na spravny typ:
char *calloc();
int *ip;
ip=(int*)calloc(n,sizeof(int));
Funkce cfree (p) uvolnuje misto v pameti, na ktery ukazuje pointr p. P bylo puvodne ziskano vyvolanim funkce calloc. Uvolnovani pameti n e m u s i byt delano poporadku. Je ale chybou uvolnovat pamet, ktera nebyla pridelena funkci calloc. V kapitole 8 si ukazeme implementaci funkce calloc.
KAPITOLA 8: SYSTÉMOVÉ SOUVISLOSTI S OPERAČNÍM SYSTÉMEM UNIX
Tato kapitola se zabyva souvislostmi jazyka C a operacniho systemu UNIX. Protoze mnoho uzivatelu jazyka C pracuje na sys-temech typu UNIX, bude tato kapitola uzitecna vetsine ctenaru. Dokonce i tem uzivatelum, kteri pracuji s jinymi systemy, bude uzitecne pro pochopeni programovani v jazyku C prostudovani prikladu uvedenych v teto kapitole.
Kapitola je rozdelena do tri hlavnich casti:
- vstup a vystup
- system souboru
- pridelovani pameti.
Prve dve casti predpokladaji minimalne znalost vnejsich vlastnosti systemu UNIX.
Kapitola 7 se zabyvala systemovym interfacem, ktery je spo-lecny pro mnoho operacnich systemu. Na jakemkoliv z techto systemu jsou funkce standardni knihovny napsany pomoci vstupne /vystupnich vlastnosti skutecneho systemu. V nasledujicich odstavcich vysvetlime zakladni systemove vstupni body pro vstupy a vystupy operacniho systemu UNIX a ukazeme si, jak s jejich pomoci mohou byt implementovany nektere casti standardni knihovny.
8.1. Deskriptory souboru
V operecnim systemu UNIX jsou vsechny vstupy a vystupy usku-tecnovany pomoci cteni nebo zapisu do souboru, protoze vsechna periferni zarizeni, dokonce terminal operatora, jsou chapana jako soubory ze systemu souboru. To znamena, ze jediny, homo-genni interface ridi veskerou komunikaci mezi programem a perifernim zarizenim.
Pred ctenim nebo zapisem do souboru je nejdrive nutne o teto skutecnosti informovat system procesem zvanym 'otevreni soubo-ru'. Jestlize chcete do souboru zapisovat je take nutne ho nej-prve vytvorit. System sam zkontroluje, je-li vse v poradku /Existuje soubor? Je do neho dovolen pristup?/ a jestlize ano, vrati vasemu programu nizke kladne cele cislo zvane 'deskriptor souboru'. Toto cislo je pak vzdy uzivano pri pristupu k souboru namisto jmena souboru. /Tato vlastnost je podobna zvyklostem ve FORTRANU napr: READ(5,) nebo WRITE(6,)./ Vsechny in-formace o otevrenem souboru jsou ovladany systemem, uzivateluv program pouziva pouze deskriptor souboru. Protoze vstup a vy-stup terminalu operatora je spolecny s ovladanim souboru, existuje moznost jak teto vyhody vyuzit. Kdykoliv interpretator prikazove radky operacniho systemu /tzv. 'shell'/ spousti vas program otevre vzdy tri soubory, ktere maji deskriptory souboru 0, 1 a 2 a jsou nazvany nazvy: standardni vstup, standardni vystup a standardni vystup chybovych hlaseni. Vsechny tyto soubory jsou normalne prirazeny k terminalu operatora, takze program, ktery cte ze souboru urceneho deskriptorem 0 a ktery pise do souboru urcenych deskriptory: 1 a 2 muze toto uskutec-nit, aniz by bylo nutne tyto soubory otevrit.
Uzivatel muze zmenit vstupne vystupni prirazeni do a ze .po 3
souboru pomoci znaku < a > :
prog <infile >outfile
Ve vyse uvedenem pripade zmeni shell puvodni prirazeni deskrip-toru souboru 0 a 1 z terminalu na jmenovane soubory. Deskriptor souboru 2 vetsinnou byva nezmeneny, a chybova hlaseni vystupuji na terminal operatora. Podobne konvence jsou platne v pripade, kdy je vstup a vystup uzivan pri zreteznem vykonavani programu /pipe/. Je nutne poznamenat, ze ve vsech vyse uvedenych pripa-dech byla zmena provedena shellem a nikoliv programem uzivate-le. Program uzivatele nevi, odkud prichazi vstup, ani kam smeruje vystup, dokud pouziva soubory 0 pro vstup a soubory 1 a 2 pro vystup.
8.2. Vstup a výstup nejnií úrovně - READ a WRITE
Nejnizsi uroven vstupu a vystupu v operacnim systemu UNIX nevykonava zadne bufferovani ani jine sluzby, je to ve skutec-nosti primy vstup do operecniho systemu. Vsechny vstupy a vy-stupy jsou uskutecneny pomoci dvou funkci: READ a WRITE. Pro obe dve je prvnim argumentem deskriptor souboru. Druhy argument je buffer ve vasem programu, do ktereho budou data zapisovana, ci budou z neho ctena. Treti argument udava pocet slabik, ktere jsou preneseny. Pouziti je nasledujici:
n_read=read(fd,buf,n);
n_writen=write(fd,buf,n);
Kazde vyvolani vyse uvedenych funkci vrati pocet slabik, ktere byly skutecne systemem preneseny. Pri cteni muze byt toto cislo nizsi, nez-li je zadana hodnota. Pokud je vracena hodnota 0, je to znameni konce souboru a hodnota -1 znamena, ze doslo k chybe pri vykonani funkce. Pri zapisu je vracena hodnota rovna poctu skutecne zapsanych slabik, pokud je ruzna od hodnoty zadane, je to znameni chyby.
Pocet slabik, ktere se mohou zapsat ci precist je libovolny.
Dve nejcasteji pouzivane hodnoty jsou 1 slabika /tj. zapis po jednotlivych znacich/ a 512 slabik /tato hodnota odpovida fyzikalni delce bloku na mnoha perifernich zarizenich/. Druhy pripad je vice efektivni, ale ani prvni neni zcela nevhodny.
Na zaklade vyse uvedenych faktu muzeme nyni napsat jednodu-chy program, ktery kopiruje data ze vstupu na vystup. Je ekvi-valentni programu, ktery je uveden v prvni kapitole. Pod operacnim systemem UNIX tento program kopiruje data odkudkoliv kamkoliv, protoze muze byt prerazen jeho vstup i vystup na li-bovolne zarizeni nebo do souboru.
#DEFINE BUFSIZE 512 /*vhodna velikost pro system
PDP-11 UNIX*/
main () /*kopiruje vstup na vystup*/
Jestlize delka souboru neni nasobkem konstanty BUFSIZE bude pri nekterem cteni vraceno cislo mensi nezli hodnota BUFSIZE, tako-vy pocet slabik bude zapsan funkci write. Pristi volani funkce vrati hodnotu 0.
Je poucne prostudovat, jak pomoci fukci read a write napsat podprogram vyssi urovne, jako treba jsou podprogramy getchar, putchar atd. Napriklad uvedeme si jednoduchou verzi funkce get-char, ktera nepouziva vstup s vyuzitim buferu.
#DEFINE CMASK 0377 /*maska, po jejimz pouziti maji
znaky kladnou hodnotu*/
getchar() /*vstup jednoho znaku, bez pou-
ziti buferu*/
Promenna c musi byt deklarovana jako promenna typu char, proto-ze funkce read vyzaduje argument typu ukazovatko na promennou typu char. Vraceny znak musi byt maskovan hodnotou 0377, aby bylo zaruceno, ze je pozitivni. Jinak by se mohlo stat, ze bude diky znamenkovemu rozsireni negativni. Konstanta 0377 je vhodna pro system PDP-11,ale pro jine systemy muze byt rozdilna. Dalsi verze funkce getchar uskutecnuje vstup vice znaku na-jednou, ale vystup je uskutecnovan po jednom znaku.
TADY CHYBI STR. 162 PUVODNIHO (ANGLICKEHO?) TEXTU.
Jako priklad si uvedeme jednoduchou verzi pomocneho programu systemu UNIX, ktery ma nazev cp. Jedna se o program, ktery ko-piruje jeden soubor do souboru jineho. Podstatne zjednoduseni je to, ze nas program kopiruje pouze jeden soubor a neni mozne, aby druhym argumentem byl adresar.
#DEFINE NULL 0
#DEFINE BUFSIZE 512
#DEFINE PMODE 0644 /*R/W pro vlastnika, R pro
ostatni*/
main (argc,argv) /*cp: kopiruj f1 do f2*/
int argc;
char argv[];
error(s1, s2) /*vytiskni chybovou zpravu a ukonci
cinnost*/
char *s1,*s2;
Existuje maximalni pocet soucasne otevrenych souboru, typic-ky 15 az 25. Program, ktery pouziva mnoho souboru , musi byt schopen vicenasobneho pouziti deskriptoru souboru. Funkce close ukonci spojeni mezi deskriptorem souboru a otevrenym souborem. Tim se uvolni deskriptor souboru pro pouziti s dalsim souborem. Ukonceni programu funkci exit nebo navratem z hlavniho programu uzavre vsechny otevrene soubory.
Funkce unlink filename zrusi soubor se jmenem filename v sy-stemu souboru.
C v i c e n i 8-1. Napiste znovu program cat z kapito-ly 7 s pouzitim funkci read, write, open a close na misto je-jich ekvivalentu ze standardni knihovny. Porovnejte rychlosti obou verzi.
8.4 Náhodný přístup - SEEK a LSEEK
Normalni pristup k souborum je sekvencni. Kazdy novy zapis nebo cteni ze souboru se uskutecni na nasledujici pozici v sou-boru. Kdyz je nezbytne, je mozne soubory cist nebo do nich za-pisovat na libovolne pozici. Systemova funkce lseek umoznuje pristup do libovolneho mista souboru, aniz by bylo nutne soubor skutecne cist, ci do neho zapisovat.
lseek (fd,offset,origin);
Funkce lseek nastavi pozici v souboru, ktery je urceny deskrip-torem souboru fd, na misto urcene posunutim offset, ktery je urcovan od mista v souboru, ktere je urcene argumentem origin. Nasledujici cteni nebo zapis do souboru se uskutecni na teto pozici. Arument offset je typu long, argumenty fd a origin jsou typu int. Argument origin muze nabyt hodnoty 0, 1 a 2, ktera urcuje, ze posunuti je mereno od pocatku, od prave aktualni pozice v souboru nebo od konce souboru. Napr. nastaveni na konec souboru /append/ se uskutecni nasledovne :
lseek(fd,0L,2);
Nastaveni na zacatek souboru /rewind/:
lseek(fd,0L,0);
Je nutne poznamenat, ze argument 0L je mozne psat ve tvaru (long)0.
S pomoci funkce lseek je mozne se soubory pracovat jako s rozlehlymi poli, na ukor pomalejsiho pristupu k jednotlivym polozkam. Napriklad nasledujici funkce cte libovolny pocet sla-bik z jakehokoliv mista v souboru:
get(fd,pos,buf,n) /*cti n slabik od pozice pos*/ int fd, n; long pos; char *buf;
V operacnim systemu UNIX V7 je hlavni systemovou funkci, ktera umoznuje pristup k souborum, funkce seek. Funkce seek je analogicka funkci lssek, vyjma te skutecnosti, ze argument je typu int. Protoze u systemu PDP-11 maji cisla typu int velikost 16 bitu, je mozna nejvetsi hodnota argumentu offset 32767. Proto existuji dalsi mozne hodnoty argumentu /3, 4, 5/, ktere zpusobuji vynasobeni hodnoty offset konstantou 512 /tj. poctem slabik v jednom fyzickem bloku dat/ a samotny argument origin je zpracovan, jako kdyby jeho hodnota byla 0, 1nebo 2. Je tedy nutne pri pristupu do rozlehlych souboru uskutecnit dve vyvola-ni funkce seek. Prve urci blok a druhe, jehoz argument origin ma hodnotu 1 urci pozadovanou slabiku uvnitr bloku.
C v i c e n i 8-2. Funkce seek muze byt napsana pomoci funkce lseek a naopak. Provedte.
8.5 Příklad - Implementace funkci FOPEN a GETC
Nyni si ukazeme, jak pomoci vyse uvedenych informaci imple-mentovat funkce fopen a getc ze standardni knihovny.
Pripominame, ze soubory jsou ve standardni knihovne urceny ukazovatkem souboru a nikoliv deskriptorem souboru. Ukazovatko na soubor, je ukazovatkem na strukturu, ktera obsahuje nezbytne informace o souboru, ukazovatko na bufer, takze souboru muze byt cten po vetsich castech; citac poctu znaku , ktere v buferu zbyvaji; ukazovatko na znak v buferu, ktery ma byt zpracovan: nekolik klicu urcujicich mody cteni/zapis atd. a deskriptor souboru.
Datova struktura popisujici soubor je definovana v souboru STDIO.H, ktery musi byt pripojen /pomoci #include/ ke vsem zdrojovym souborum, ktere pouzivaji podprogramy ze standardni knihovny. Je take zaclenen funkcemi teto knihovny. Nasledujici vytah ze souboru STDIO.H urcuje ty, ktere jsou uzivany pouze funkcemi standardni knihovny, znakem podtrhnuti /'_'/ tak, aby nevznikly kolize se jmeny v uzivatelove programu.
#define _BUFSIZE 215
#define _NFILE 20 /*pocet soucasne otevrenych.po 3
souboru*/
typedef struct _iobuf FILE;
extern FILE _iob[_NFILE];
#define stdin (_iob[0])
#define stdout (_iob[1])
#define sterr (_iob[2])
#define _READ 01 /*soubor otevren pro cteni*/
#define _WRITE 02 /*soubor otevren pro zapis*/
#define _UNBUF 04 /*pristup bez buferu*/
#define _BIGBUF 010 /*pristup s buferem*/
#define _EOF 020 /*priznak konce souboru*/
#define _ERR 040 /*priznak chyby pri pristupu*/
#define NULL 0
#define EOF (-1)
#define getc(P) (--(P) -> _cnt >= 0
? *(p) -> _ptr++ & 0377 : _fillbuf (p)
#define getchar() getc(stdin)
#define putc(x,p) (--(p) -> _cnt >= 0
?*(p) -> _ptr++ = (x) : _flushbuf ((x),p))
#define putchar(x) putc (x,stdout)
Makro getc pouze dekrementuje citac, nastavi ukazovatko na novou pozici a vrati znak. (Dlouha definice #define je prodlouzena na dalsi radku pomoci znaku ''.) Jestlize se citac stane negativni, je vyvolana funkce _fillbuf, ktera naplni bufer, provede opetnou inicializaci obsahu struktury a vrati znak. Tyto funkce smeji obsahovat neprenositelny interface k operacnimu systemu, napr. funkce getc maskuje znak hodnotou 0377, aby predesla znamenkovemu rozsireni, ktere vzni-ka na systemu PDP-11, a tim zajisti, ze vsechny znaky budou mit kladene hodnoty. Prestoze se nebudeme zaobirat detaily, je zde take definice funkce putc analogicka funkci getc, vyvolavajici vsak funkci _flushbuf pri zaplnenem buferu.
Nyni muzeme jiz napsat definici funkce fopen. Nejvetsi cast funkce fopen se zabyva vlastnim otevrenim souboru a nastavenim ukazovatka na spravne misto v souboru a nastaveni klicu na pozadovany stav. Funkce fopen neprideluje zadny prostor pro bufer, ten je pridelen funkci _fillbuf pri prvem cteni ze sou-boru.
#include <stdio.h>
#define PMODE 0644 /*R/W pro vlastnika, R pro ostatni*/
FILE fopen (name,mode) /*otevri soubor a vrat
ukazovatko na desk. soub*/
register char *name, *mode;
for(fp >= _iob;fp < _iob + _NFILE;fp++)
if((fp -> _flag & (_READ | _WRITE)) == 0
break; /*najdi volnou polozku*/
if(fp) = _iob + _NFILE /*neni volna polozka*/
return (NULL); if(*mode == 'w') /*pristupuj do souboru*/ fd = creat(name, PMODE); else if(*mode == 'a')
else
fd = open(name,0);
if(fd == -1) /*nemohu otevrit soubor*/ return(NULL);
fp -> _fd = fd; fp -> _cnt = 0; fp -> _base = NULL; fp ->_ flag &= ^(_READ | _WRITE); fp -> _flag |= (*mode == 'r') ? _READ : _WRITE; return(fp);
Funkce _fillbuf je ponekud komplikovanejsi. Hlavni problem spociva ve faktu, ze funkce _fillbuf se pokusi umoznit pristup k souboru dokonce i vpripade, kdy neni dostatek volne pameti pro vstup/vystup s pouzitim buferu. Jestlize je pamet pro novy buffer pridelena funkci calloc, je vse vporadku. Jestlize pamet pridelena neni, umozni funkce _fillbuf pristup k souboru bez buferu, tj. znak po znaku s vyuzitim sveho vnitrniho pole.
#include <stdio.h>
_fillbuf (fp) /*pridel a napln vstupni bufer*/
register FILE fp;
return(*fp -> _ptr++ & 0377); /*vrat kladny znak*/
Pri prvnim vyvolani funkce getc pro dany soubor bude citac _cnt roven nule, coz zpusobi vyvolani funkce _fillbuf. Jestlize funkce _fillbuf zjisti, ze soubor neni otevren pro cteni, vrati bezprostredne hodnotu EOF. Jestlize tomu tak neni, zkusi nej-prve pridelit souboru bufer. Pokud neni mozne souboru bufer pridelit, poznaci to v promenne _flag.
Jakmile je bufer stanoven, je vyvolana funkce read, aby ho naplnila, je nastaven citac, ukazovatka a je vracen znak z pocatku buferu. Dalsi volani funkce _fillbuf jiz naleznou bufer souboru prideleny.
Zbyva pouze jedina vec, kterou je nutne vysvetlit. Pole _iob musi byt definovano a inicializovano pro soubory stdin, stdout a stderr:
FILE _iob[_NFILE]=(
(NULL, O, NULL, _READ, 0) ,/*stdin*/
(NULL, 0, NULL, _WRITE,1) ,/*stdout*/
(NULL, 0, NULL, _WRITE _UNBUF, 2) /*stderr*/
);
Z inicializace promenne _flag je patrne, ze soubor stdin je urcen pouze pro cteni, souboru stdout pouze pro zapis a soubor stderr pouze pro zapis bez pouziti bufferu.
C v i c e n i 8-3. Prepiste funkce fopen a _fillbuf a pouzijte pole bitu namisto explicitnich bitovych operaci. C v i c e n i 8-4. Navrhnete funkce _flushbuf a fclose.
C v i c e n i 8-5. Soucasti standardni knihovny je funkce fseek
fseek(fp,offset,origin) ktera je identicka s funkci lseek s tim rozdilem, ze fp je ukazovatko na soubor misto deskriptoru souboru. Napiste funkci fseek. Ujistete se o spravne funkci vasi funkce ve spo-lupraci s ostatnimi funkcemi ze standardni knihovny.
8.6 Příklad - Výpis adresáře
Rozdilnou ulohou pri praci se soubory je ziskavani informaci o souboru, nikoliv prace s jeho obsahem. Prikaz operacniho sys-temu UNIX ls /list directory = vypis adresar/ vytiskne jmena souboru v adresari a pokud je zadouci i dalsi informace, jako je velikost, zpusoby pristupu atd.
Protoze v operacnim systemu UNIX je adresar chapan jako soubor, je prace s nim stejna jako s jinymi soubory, system nejprve soubor s adresarem precte a vyjme z neho vsechny nez-bytne informace o souborech, ktere v nem nalezne. Format infor-mace v adresari je urcen operacnim systemem, nikoliv uzivate-lovym programem, proto musi program ls znat, jaka je reprezen-tace informaci v adresari.
Ukazeme si nektere vyse uvedene zasady na programu nazvanem fsize. Program fsize je specializovana forma programu ls, ktera vytiskne velikosti vsech souboru, ktere jsou uvedeny v seznamu argumentu prikazove radky. Jestlize jeden z techto argumentu je adresarem, uplatni se na nem rekurzivne opet program fsize. Jestlize nejsou zadne argumenty bude zpracovan prave pouzivany adresar.
Nyni strucny prehled o systemu souboru. Adresar je soubor, ktery obsahuje seznam jmen souboru a informace o tom, kde je soubor ulozen. Informace o umisteni souboru je ve sku-tecnosti indexem v tabulce zvane 'inod table'. Polozka adresare se sklada pouze ze dvou casti. Ze jmena souboru a cisla urcuji-coho polozku v tabulce 'inod table'. Tato polozka obsahuje ves-kere informace o souboru a jeho umisteni. Presna specifikace je zaclenena v souboru sys/dir.h, ktery obsahuje:
#define DIRSIZ 14 /*maximalni delka jmena souboru*/
struct direct /*struktura polozky adresare*/
;
Typ ino_t je typ definovany pomoci konstrukce typedef a popisu-je index do tabulky 'inod table'. Na systemu PDP-11 UNIX je ty-pu unsigned, ale na jinych systemech muze byt rozdilneho typu. Kompletni prehled vsech systemovych typu je obsazen v souboru sys/types.h.
Funkce stat ma vstupnim argumentem jmeno souboru a vraci veskerou informaci, ktera je obsazena v tabulce 'inod table', nebo hodnotu -1, pokud doslo k chybe.
struct stat stbuf; char *name; stat(name,&stbuf);
Funkce stat zaplni strukturu stbuf informaci obsazenou v tabul-ce 'inod table' pro dany soubor. Struktura popisujici vracenou informaci je obsazena v souboru sys/stst.h a je nasledovana:
struct stat /*struktura vracena funkce stat*/
;
Mnoho z vyse uvedene struktury je objasneno v komentarich obsa-zenych v definici. Polozka st_mode obsahuje soubor klicu, ktere popisuji soubor. Definice klicu je soucasti souboru sys/stat.h.
#define S_IFMT 0160000 /*typ souboru*/
#define S_IFDIR 0040000 /*adresar*/
#define S_IFCHR 0020000 /*zvlastni znak*/
#define S_IFBLK 0060000 /*zvlastni blok*/
#define S_IFREG 0100000 /*regularni*/
#define S_ISUID 04000 /*nastav vlastnikuv identifi-
kator*/
#define S_ISGID 02000 /*nastav skupinovy identifi-
kator*/
#define S_ISVTX 01000 /*uschovej text po pouziti*/
#define S_IREAD 0400 /*cteni dovoleno*/
#define S_IWRITE 0200 /*zapis dovolen*/
#define S_IEXEC 0100 /*vykonani dovoleno*/
Teprve nyni muzeme zacit psat program fsize. Jestlize informace obdrzena funkci stat urcuje, ze soubor neni ardesarem, muze byt jeho velikost primo obdrzena a vytisknuta. Jestlize se jedna o adresar zpracujeme rekurzivne jeho polozky. Nejvyssi uroven programu se zabyva zpracovanim argumentu prikazove radky. Kazdy argument teto radky poskytne ke zpracovani funkci fsize.
#include <stdio.h>
#include <sys/types.h> /*definice typu*/
#include <sys/dir.h> /*struktura polozky adresare*/
#include <sys/stat.h> /*struktura vracena funkci stat*/
#define BUFSIZE 256
main (argc,argv) /*fsize: tiskni velikost souboru*/
char *argv[];
else.po 12
while (--argc > 0)
}
Funkce fsize tiskne velikosti souboru. Jestlize se jed-na o adresar, vyvola funkce fsize funkci directory, aby mohla zpracovat soubory obsazene v adresari. Vsimnete si pouziti nazvu klicu S_IFMT a S_IDFIR ze souboru stat.h.
fsize (name) /*tiskni velikost daneho souboru*/ char *name;
if ((stbuf.st_mode & S_IFMT) == S_IFDIR) directory (name); printf ('%81d %s n',stbuf.st_size,name);
Funkce directory je ponekud komplikovana. Jejim hlavnim uce-lem je vytvoreni uplneho jmena souboru, kterym se prave zabyva. directory (name) /*velikosti vsech souboru adresare*/ char *name;
close (fd);.po 3
*--nbp = '0'; /*obnov jmeno*/
Jestlize polozka adresare neni pouzita, protoze byl soubor zrusen, je index na tabulku 'inod table' roven nule. Potom je tato polozka pri zpracovani preskocena. Kazdy adresar obsahuje take polozku pro sebe sama, ktera je oznacena '.' a pro sve 'otce', ktera je oznacena '..'. Tyto polozky musi byt take preskoceny, protoze by doslo k zacykleni programu. Prestoze je program fsize ponekud specializovany, lze si na nem ukazat nekolik dulezitych idei. Za prve, mnoho progra-mu, prestoze nejsou programy systemovymi, pouziva informace obhospodarovane operacnim systemem. Za druhe, pro takove pro-gramy je rozhodujici, ze reprezentace takovych informaci je ulozena pouze ve standardnich souborech, jako jsou napr. stat.h a dir.h, a ze programy, ktere tyto soubory zaclenuji, nemusi skutecne deklarace v sobe obsahovat.
8.7 Příklad - Přidělování paměti
V kapitole 5 byla uvedena jednoducha verze funkce alloc.
Verze, ktera bude nyni uvedena ma neomezene moznosti. Volani funkci alloc a free mohou byti prostridana v libovolnem pora-di. Funkce alloc pouziva volani operacniho systemu pro pride-leni pameti. Tyto funkce take ilustruji moznost psat progra-my zavisle na technickem vybaveni systemu relativne nezavisle od tohoto vybaveni. Dale ukazuji uziti struktur, unionu a defi-nic typu typedef v praxi.
Na misto pridelovani pameti z pole pevne dane velikosti urcene pri prekladu vyuziva funkce alloc zadosti k operacnimu systemu o prideleni pameti. Protoze i dalsi cinnosti programu mohou vyzadovat prideleni pameti, je pamet pridelovana nekonti-nualne. Proto musi byt volna pamet /tj. informace o ni/ udrzo-vana v retezu volnych bloku pameti. Kazdy blok obsahuje svoji velikost, ukazovatka na dalsi blok a vlastni volnou pamet. Bloky jsou zretezeny v poradi stoupajici velikosti adresy ope-racni pameti a blok posledni /s nejvyssi adresou/ ukazuje zpet na prvni blok, takze retez vytvari ve skutecnosti kruh.
Pri zadosti o prideleni pameti je prohledavan seznam vol-nych bloku pameti, dokud neni nalezen dostatecne veliky blok pameti. Jestlize ma blok pozadovanou velikost, je ze seznamu volnych bloku vyrazen a pridelen uzivatelovi. Jestlize je blok prilis veliky, je rozdelen a primerena cast je pridelena uziva-telovi, zatim co cast zbyvajici je zaclenena zpet do seznamu volnych bloku. Jestlize neni nalezen zadny blok dostatecne velikosti, je zadan operacni system o prideleni dalsiho bloku pameti, ktery je zaclenen do seznamu volnych bloku.
Uvolnovani bloku pameti zpusobuje prohledavani seznamu vol-nych bloku proto, aby bylo nalezeno vhodne misto pro zaclene-ni prave uvolneneho bloku pameti. Jestlize prave uvolnovany blok pameti je prilehajici k bloku jinemu, je k nemu pripojem, aby nedochazelo k prilisnemu rozdeleni pameti. Nalezeni prile-hajicich bloku je snadne, protoze seznam volnych bloku pameti je organizovan dle poradi ukladani.
Jeden problem, o kterem byla zminka v kapitole c. 5, je.po 12
zajisteni toho, aby blok pameti, prideleny funkci alloc byl vhodny pro objekty, ktere do neho budou ulozeny. Ackoliv existuji ruzne druhy vypocetnich systemu, existuje pro kazdy z nich nejvice omezeny objekt. Nejvice omezeny objekt muze byt ukladan pouze na nektere adrese. Na tyto adresy pak mohou byt ukladany take vsechny objekty mene omezene. Napriklad, na systemech IBM 360/370, Honeywell 6000 a nekterych dalsich mohou byt libovolne objekty ulozeny na mista vhodna pro uloze-ni objektu typu double. Na systemu PDP-11 je timto objektem objekt typu int.
Volny blok obsahuje ukazovatko na nasledujici blok v retez-ci, zaznam o velikosti bloku a vlastni volnou pamet. Kontrolni informace umistena na pocatku bloku se nazyva hlavickou /'hea-der'/. Pro zjednoduseni pridelovani maji vsechny bloky velikost rovnu nasobku velikosti hlavicky, ktera musi byt spravne umis-tena. Toho je dosazeno unionem, ktery obsahuje pozadovanou strukturu hlavicky a prikladem nejvice omezeneho typu.
typedef int ALIGN; /*vhodne prirazeni pro PDP-11*/
union headers;
ALIGN x; /*prirad spravne blok*/
};
typedef union header HEADER;
Funkce alloc zaokrouhli pozadovanou velikost pridelene pameti v nasobcich velikosti hlavicky. Skutecna velikost bloku pridelene pameti je vetsi o jednu jednotku velikosti hlavicky, protoze blok musi obsahovat i vlastni hlavicku, tato hodnota je obsa-zena v promenne size hlavicky. Ukazovatko vracene funkci alloc, vsak ukazuje na pocatek volne pameti, nikoliv na hlavicku blo-ku volne pameti.
static HEADER base; /*pocatecni prazdny seznam*/
static HEADER *allocp = NULL; /*naposledy prideleny blok*/
char *alloc (nbytes) /*zakladni funkce pridelovani pameti*/
unsigned nbytes;
for (p = q -> s.ptr; ; q = p -> s.ptr)
allocp = q;
return ((char*)(p+1));
}
if (p == allocp) /*zamez opakovanemu prohledavani*/
if ((p = morecore (nunits)) == NULL)
return (NULL); /*neni zadny volny blok*/
}
Promenna base je pouzita pouze pri odstartovani procesu pride-lovani pameti. Jestlize je ukazovatko allocp hodnoty NULL, pro-vadi se prve volani funkce alloc, pri kterem je vytvoren dege-nerovany seznam volne pameti, ktery obsahuje jeden blok nulove velikosti a odkazuje sam na sebe. Pri dalsich volanich funkce alloc, jiz bude blok volne pameti hledan. Vyhledani volneho bloku pameti zapocne z mista, kde byl pridelen volny blok pame-ti naposledy, to napomaha k udrzeni homogennosti seznamu volne pameti. Jestlize je nalezen prilis veliky blok pameti, je uzi-vateli pridelena pamet od konce bloku. Timto zpusobem je hla-vicka puvodniho bloku zachovana. Pouze je zmenena hodnota size udavajici velikost volne pameti bloku. Ve vsech pripadech je vsak uzivateli vraceno ukazovatko na skutecne volnou pamet, ktera pocina jednu jednotku velikosti hlavicky bloku za pocat-kem bloku /tj. za hlavickou/. Poznamenejme, ze promenna t je konvertovana na promennou typu ukazovatko, pred jejim vracenim funkci alloc.
Funkce morecore obdrzi volnou pamet od operacniho systemu.
Podrobnosti o tom jak, se samozrejme lisi na ruznych systemech. V systemu UNIX vrati systemove volani sbrk (n) ukazovatko na n dalsich slabik volne pameti. Ukazovatko vracene volanim uspo-kojuje vsechny pozadavky na spravne prideleni pameti. Protoze pridelovani pameti prostrednictvim operacniho systemu je pom-merne narocnejsi operace, neni zadouci, aby kazde volani fun-kce alloc pouzivalo sluzby operacniho systemu. Proto funkce morecore zaokrouhli pocet zadanych jednotek volne pameti na vetsi hodnotu. Takovy blok potom muze byt znovu pridelovan, jak je zapotrebi. Pravidlo zaokrouhleni je parametrem, ktery muze byt nastaven tak, jak je zadouci.
#define NALLOC 128 /*pocet jednotek najednou pridelenych*/ static HEADER *morecore(nu) /*zadej system o volnou pamet*/ unsigned nu;
Funkce sbrk vrati hodnotu -1, jestlize uz ani system nemuze pridelit zadnou volnou pamet, ackoliv by byla vhodnejsi hodno-ta NULL. Hodnota -1 musi byt konvertovana na hodnotu typu int, aby mohla byt porovnana. Naproti tomu pouziti konstrukce 'cast' (vid. 7.2) umoznuje, aby funkce byla pomerne imunni proti ruznym reprezentacim ukazovatek na rozdilnych systemech.
Funkce free jednoduse prohledava seznam volnych bloku pame-ti, pocinaje od bloku urceneho ukazovatkem allocp. Hleda mis-to pro zarazeni volneho bloku. Takove misto existuje bud mezi dvema bloky nebo za poslednim blokem v seznamu. Kazdopadne vsak budou volne bloky, ktere spolu sousedi, spojeny v blok jediny. Jedina obtiz programu je udrzet ukazovatka tak, aby ukazovala na spravna mista a velikosti volnych bloku pameti byly spravne.
free (ap) /*vloz blok ap do seznamu volnych bloku*/ char *ap;
else
p -> s.ptr = q -> s.ptr;
if (q + q > s.size == p) /*pripoj k predch. bloku*/
else
q -> s.ptr = p;
allocp = q;
Ackoliv je pridelovani pameti v podstate zavisle na technickem vybaveni systemu, program vyse uvedeny ukazuje, jak tyto zavislosti mohou byt rizeny a svereny nevelke casti programu. Uziti konstrukci typedef a union je vhodne pro rizeni prideleni spravneho useku pameti /za predpokladu, ze funkce sbrk vrati spravne ukazovatko/. Pouziti konstrukce 'cast' zabe-zpeci explicitni uskutecneni konverze ukazovatka a to dokonce i v pripade, kdy je spatne navrzeny systemovy interface. Pres-to ze se jedna o podrobnosti uplatnene pri navrhovani funkci, zabezpecujicich pridelovani volne pameti, je jejich vseobec-ne uplatneni daleko sirsi.
C v i c e n i 8-6. Funkce ze standardni knihovny calloc (n,size) vrati ukazovatko na volny blok pameti o velikosti n* size, ktera je inicializovana nulami. Napiste funkci calloc, pouzitim funkce alloc jako vzoru nebo jako volane funkce.
C v i c e n i 8-7. Funkce alloc prijme zadost o prideleni volne pameti dane velikosti aniz kontroluje realnost takove zadane velikosti pameti. Funkce free dale predpoklada, ze blok, ktery je zpracovavan, obsahuje korektni hodnotu v promenne size. Pozmente vyse uvedene funkce tak, aby byly schopny vice rozpoznavat takove chyby.
C v i c e n i 8-8. Napiste funkci bfree(p,n), ktera uvolni libovolny blok p o n znacich a zaradi do seznamu volne pameti. Pouzitim funkce bfree muze uzivatel priradit staticke nebo externi pole do seznamu bloku volne pameti v libovolnem okamiku.
PŘÍLOHA A: REFERENČNÍ POPIS JAZYKA C
1.Úvod
Tato priloha popisuje jazyk C, takovy jaky je na pocitacich DEC PDP-11, Honeywell 6000, IBM System/370 a Interdata 8/32. Tam kde mezi temito jazyky existuji rozdily, zameruje se popis na verzi pro system PDP-11, ale soucasne zduraznuje implemen-tacne-zavisle skutecnosti. Az na nekolik vyjimek jsou tyto roz-dily zpusobeny rozdilnym technickym vybavenim /hardware/ vyse uvedenych pocitacu. Jinak jsou vyse uvedene predkladace ja-zyka C vpodstate zcela kompatibilni.
2. Lexikální konvence
Existuje sest kategorii lexikalnich polozek: identifikatory, klicova slova, konstanty, retezce, operatory a separatory. Me-zery, tabulatory, znaky nova radka a komentare /souhrne nazy-vane nevyznamne znaky/ jsou ignorovany pokud neslouzi k oddele-ni lexikalnich polozek. Nektere nevyznamne znaky jsou pozadova-ny pro oddeleni sousednich identifikatoru, klicovych slov a konstant.
Jestlize byl vstupni retezec znaku rozdelen na lexikalni po-lozky az po nejaky dany znak, bude nasledujici lexikalni poloz-ka obshovat nejdelsi mozny retezec znaku, ktery by mohl tvorit nejakou lexikalni polozku.
2.1. Komentáře
Komentar je uveden dvojici znaku '/*' a ukoncen dvojici zna-ku '*/'. Komentare nesmeji byt do sebe navzajem vnorene.
2.2. Identifikátory ( jména )
Identifikator je rada pismen a cislic. Prvni znak identifi-katoru musi byt vzdy pismeno. Znak '_' se radi k pismenum. Jsou rozlisovana mala a velka pismena. Pouze prvnich 8 znaku iden-tifikatoru je vyznamnych, ackoliv identifikator muze byt utvo-ren z vice znaku. Pro externi identifikatory, ktere jsou pouzi-vany ruznymi prekladaci jazyka symbolickych adres a zavadecimi programy, plati prisnejsi pravidla:
DEC PDP-11 7 znaku, mala i velka pismena rozlisuje
Honeywell 6000 6 znaku, nerozlisuje mala a velka
IBM 360/370 7 znaku, nerozlisuje mala a velka
Interdata 8/32 8 znaku, mala i velka rozlisuje
2.3 Klíčová slova
Nasledujici identifikatory jsou rezervovany jako klicova slova a nesmeji byt pouzity v jinem vyznamu:
int extern else
char register for
float typedef do
double static while
struct goto switch
union return case
long sizeof default
short break entry
unsigned continue
auto if
Klicove slovo entry neni doposud implemetovano na zadnem z vyse uvedenych kompilatoruu, ale je rezervovano pro budouci pouziti. Nektere dalsi implementace C jazyka obsahuji take rezervovana slova fortran a asm.
2.4 Konstanty
Existuje nekolik typu konstant, ktere budou popsany dale. Jejich zavislosti na technickych prostredcich jsou shrnuty v odstavci 2.6.
2.4.1 Celočíselné konstanty
Celociselna konstanta se sklada ze rady cislic. Jestlize ta-to rada zacina cislici 0 je konstanta predpokladana v oktalove soustave namisto dekadicke. Napr. cislice 8 a 9 maji v oktalo-ve soustave vyjadreni 10 a 11. Rada cislic, ktera je predchaze-na znaky '0x' nebo '0X' je chapana v soustave hexadecimalni. Hexadecimalni cislice obsahuji navic znaky 'a' nebo 'A' az 'f' nebo 'F', ktere odpovidaji cislum 10 az 15. Dekadicka konstanta jejiz hodnota je vetsi nez nejvetsi cele cislo se znamenkem, ktere muze dany typ pocitace zpracovat se chape jako konstanta typu long. Oktalova nebo hexadecimalni konstanta, jejiz hodno-ta je vetsi nez nejvetsi cele cislo bez znamenka, ktere muze dany typ pocitace zpracovat se take chape jako konstanta typu long.
2.4.2 Explicitní konstanty typu long
Dekadicka, oktalova nebo hexadecimalni celociselna konstan-ta, ktera je bezprostredne nasledovana znakem 'l' nebo 'L' je chapana jako konstata typu long. Jak bude dale diskutovano, na nekterych pocitacich mohou byt konstanty typu cele cislo a typu long povazovany za totozne.
2.4.3 Znakové konstanty
Znakova konstanta je znak uzavren mezi jednoduche uvozovky, napr. 'x'. Hodnota znakove konstanty je ciselena hodnota znaku v pouzitem znakovem systemu.
Nektere nezobrazitelne znaky, jednoduche uvozovky ''' a zpetne lomitko '' mohou byti pouzity jako znakove konstanty s vyuzitim nasledujici tabulky:
nazev znaku: oznaceni: vkladany retezec znaku:
novy radek NL nebo LF n
tabulator HT t
zpetny vymaz BS b
navrat voziku CR r
nova stranka FF f
zpetne lomitko
jednoduche uvoz. ' '
retez bitu ddd ddd
Retezec 'ddd' se sklada ze znaku zpetne lomitko, ktere je
nasledovano 1, 2 nebo 3 oktalovymi cislicemi, ktere specifiku-
ji hodnotu pozadovaneho znaku. Specialnim pripadem pouziti
teto konvence je retezec znaku '0', ktery predstavuje znak
NULL. Jestlize znak, ktery nasleduje za znakem zpetne lomitko
neni zadny z vyse vyjmenovanych je znak zpetne lomitko
ignorovan.
2.4.4 Konstanty v plovoucí čárce
Konstanta v plovouci carce se sklada z celociselne casti,
desetinne tecky a desetinne casti, znaku 'e' nebo 'E' a pripad-
ne celociselneho exponentu se znamenkem. Celociselna a desetin-
na cast se skladaji z rady cislic. Bud celociselna nebo dese-
tinna cast muze byt vynechana, ale nikoliv obe dve zaroven. Bud
desetinna tecka nebo znak 'e' /'E'/ a exponent muze byt vyne-
chan, ale nikoliv oba soucasne. Kazda konstanta v plovouci car-
ce je chapana s dvojnasobnou presnosti.
2.5 Řetězce
Retezec je rada znaku uzavrena mezi dvojite uvozovky, napr.
''. Retezec je objekt typu pole znaku a jeho ukladaci trida
je static /viz. odstavec c.4/ a je inicializovan danymi znaky.
Vsechny retezce, i identicke, jsou ruzne. Prekladac umisti na
konec kazdeho retezece znak 0 proto, aby program mohl najit
konec retezce. Uvnitr retezce musi byt znak dvojite uvozovky
predchazen znakem zpetne lomitko. Dale mohou byt pouzity stejne
prepinaci posloupnosti, ktere byly popsany v odstavci c.2.4.3.
Znak zpetne lomitko, ktery je bezprostredne nasledovan zna-
kem nova radka je spolu s nim ignorovan.
2.6 Technické vybavení
Nasledujici tabulka shrnuje vlastnosti technickeho vybaveni
jiz vyse zminenych ctyr typu pocitacu. Ackoliv tyto vlastnostimaji vliv na prenositelnost programu, ve skutecnosti je daleko
mene problemu, nez by bylo mozne ocekavat.
DEC PDP-11 Honeywell 6000 IBM 370 Interdata 8/32
ASCII ASCII EBDIC ASCII
char 8 bits 9 bits 8 bits 8 bits
int 16 36 32 32
short 16 36 16 16
long 32 36 32 32
float 32 36 32 32
double 64 72 64 64
rozsah (+-10)** (+-10)** (+-10)**(+-10)**
(+-38) (+-38) (+-76) (+-76)
poznamky: vsechny systemy maji cisla s plovouci carkou, ktera
maji 8-mi bitovy exponent.
3. Syntaktický zápis
V syntaktickem zapise, ktery je pouzit v teto priloze,
jsou syntakticke kategorie oznacovany malymi pismeny. Lite-
raly a znaky jsou oznaceny velkymi pismeny. Ruzne kategorie
jsou vypsany na samostatnych radcich. Nepovinny terminalni
nebo neterminalni symbol je oznacen znaky '???'..
Napriklad:
(expression???)
oznacuje nepovinny vyraz uzavreny v zavorkach. Syntaxe je
shrnuta v odstavce c. 18.
4. Co je to jméno?
V C jazyku ma kazdy identifikator prideleny dva atributy:
ukladaci tridu a typ. Ukladaci trida urcuje umisteni a dobu
existence pameti pridelene identifikatoru. Typ urcuje vyznam
hodnoty ulozene pameti pridelene identifikatoru.
Existuji ctyri deklarovatelne ukladaci tridy: automatic,
external, static a register. Promenne tridy automatic jsou
lokalni pri kazdem vyvolani bloku /odstavec c.9.2/ a jsou
zruseny po ukonceni bloku. Promenne tridy static jsou lokal-
ni v bloku, ale uchovavaji svoji hodnotu az do opetovneho
navratu do bloku.Externi promenne existuji a uchovavaji svoji
hodnotu po celou dobu behu programu. Mohou byt pouzity pro
komunikaci mezi ruznymi funkcemi, ktere mohou byt dokonce
oddelene prekladany.Registrove promenne jsou ukladany do rych-
lych registru pocitace /pokud jsou dovoleny/, jinak se chovaji
stejne jako promenne tridy automatic.
V jazyku C je definovano nekolik zakladnich typu objektu:
Objekty deklarovane jako znakove /char/ jsou tak velike,
aby do nich mohl byt ulozen libovolny clen pouziteho systemu
znaku. Jestlize je nejaky znak ulozen do znakove promenne,
jeho hodnota odpovida celociselnemu kodu tohoto znaku. Do
promenne typu char mohou byt ulozeny libovolne hodnoty, alejejich vyznam je zavisly na technickych prostredcich.
Existuji tri ruzne promenne typu int, ktere jsou deklaro-
vany jako short int, int a long int. Promenne typu long nebo
int neposkytuji pro ulozeni promennych mene pameti nez promenne
typu int nebo short. Implementace vsak muze byt takova, ze
promenne typu short int nebo long int nebo obe dve mohou byt
ekvivalentni zakladni promenne typu int. Zakladni promenna
typu int ma velikost odpovidajici prirozene architekture po-
citace. Ostatni typy promennych typu int jsou definovany pro
specialni ucely.
Cela cisla bez znamenka, deklarovana jako unsigned, pouzi-
vaji aritmetiku modulo 2**n, kde n je pocet bitu v dane repre-
zentaci celeho cisla. Na systemu PDP-11 nejsou promenne typu
unsigned long int implementovany.
Promenne typu floating point jednoduche i dvojnasobne pres-
nosti mohou byt v nekterych implementacich totozne.
Protoze vsechny predchazejici typy promennych mohou byt cha-
pany jako cisla, mohou byt oznaceny take jako promenne typu
aritmetic. Typy char a int vsech velikosti mohou byt souhrne
oznacovany jako promenne typu integral. Promenne typu float
a double mohou byt souhrne oznacovany jako promenne typu floa-
ting.
Mimo zakladni aritmeticke typy existuje v podstate nekonecny
pocet odvozenych typu, ktere mohou byt od zakladnich odvozeny
nasledujicimi zpusoby:
pole /array/ objektu z ruznych typu
funkce, ktere vraci objekty danych typu
ukazovatka /pointers/ na objekty danych typu
struktury obsahujici seznam objektu ruznych typu
uniony /unions/ obsahujici soucasne nekolik objektu
ruznych typu
Vsechny tyto zpusoby mohou byt pouzity rekurzivne.
5. Objekty a l-hodnoty
Pojem objekt znamena urcitou cast pameti, ktera slouzi k
ulozeni nejake hodnoty a se kterou je mozne manipulovat.
l-hodnota je vyraz, ktery ukazuje na nejaky objekt.
Nejcastejsim prikladem takoveho vyrazu je identifikator.
Existuji operatory, ktere poskytuji l-hodnoty, napr:
jestlize E je vyraz typu ukazovatko, potom E je vyraz l-
hodnoty ukazujici na objekt na ktery ukazuje E. Nazev 'l-
hodnota' je odvozen z prikazu prirazeni napr: E1 = E2, ve
kterem levy operand E1 musi byt vyraz l-hodnoty. Diskuse
vsech operatoru, ktera nasleduje, poskytne informace o tom,
ktery operator ocekava operandy typu l-hodnota a ktery
l-hodnoty poskytuje.
L-hodnota je tedy vyraz, ktery muze byt na leve strane
prirazeni.
6. Konverze
Vetsina operatoru muze v zavislosti na jejich operandech
zpusobit konverzi hodnoty operandu daneho typu na jiny typ.
V teto casti prirucky bude objasneno, jake mohou byt vysled-
ky takovych konverzi. Odstavec 6.6 souhrne vysvetli pravid-
la, jaka jsou pozadovana jednotlivymi operatory. Dalsi infor-
mace jsou dale podany u vysvetleni jednotlivych operatoru.
6.1 Znaky a celá čísla
Znakove objekty a objekty typu short integer mohou byt pou-
zity vsude tam, kde je dovoleno pouzit objekty typu integer. Ve
vsech pripadech se vsak provadi konverze na typ integer. Pri
konverzi objektu typu short integer na objekt typu integer se
vzdy provadi znamenkove rozsireni, protoze objekty vsech typu
integer jsou hodnoty se znamenkem. U objektu znakovych se zna-
menkove rozsireni provadi v zavislosti na technickem vybaveni
pocitace. Vzdy je vsak zaruceno, ze znak ze standartniho sou-
boru znaku nema zapornou hodnotu. Z vyse uvedenych typu poci-
tacu pouze system PDP-11 provadi znamenkove rozsireni u objek-
tu typu char. Na tomto pocitaci je rozsah hodnot typu char
od -128 do 127, pricemz znaky souboru ASCII maji vzdy kladne
hodnoty. Znakova konstanta zadana pomoci zpetneho lomitka a
oktalovych cislic muze znamenkovemu rozsireni podlehat a stat
se tak negativni, napr. '377' ma hodnotu -1.
Kdykoliv je delsi objekt typu integer konvertovan na
objekt typu short integer nebo char je zkracen z leva,
bity navic jsou jednoduse ignorovany.
6.2 Plovoucí čárka a dvojnásobná přesnost
Veskera aritmetika v plovouci carce je v C jazyku provadena
v dvojnasobne presnosti. Kdykoliv se objekt typu plovouci car-
ka vyskytne ve vyrazu je konvertovan na objekt typu dvojnasob-
na presnost doplnenim nulami. Kdykoliv musi byt objekt dvojna-
sobne presnosti konvertovan na objekt typu plovouci carka,
napriklad pri pouziti prikazu prirazeni je objekt typu dvojna-
sobne presnosti zaokrouhlen predtim nezli je zkracen na objekt
typu plovouci carka.
6.3 Typy plovoucí čárky a typy celočíselné
Konverze typu plovouci carky na typy celociselne jsou zavis-
le na pouzitem technickem vybaveni, predevsim zkraceni zapor-
nych cisel je na ruznych pocitacich odlisne. Vysledek je nede-
finovany, jestlize konvertovana hodnota neni v rozsahu celych
cisel.
Konverze objektu celociselnych na objekty v plovouci carce
jsou bez problemu. Muze dojit ke ztrate presnosti, jestlize
objekt v plovouci carce nema dostatecny pocet bitu.
6.4 Ukazovátka a celá čísla
Objekty typu integer nebo long integer mohou byt scitany a
odcitany od objektu typu ukazovatko. V techto pripadech je
prvni operand konvertovan tak, jak je popsano v casti popisu-
jici operator scitani.
Mohou byt take odectena dve ukazovatka, ktera ukazuji na
objekty tehoz typu. Potom je vysledek konvertovan na celoci-
selny typ, tak jak je popsano v casti popisujici operator
rozdilu.
6.5 Čísla bez znamínka
Pri jakekoliv kombinaci celeho cisla beze znamenka a nor-
malniho celeho cisla je normalni cislo konvertovano na cislo
beze znamenka a vysledek je beze znamenka. Jeho hodnota je
nejmensi cele cislo beze znamenka, jehoz hodnota je kongruentni
s cislem typu signed /modulo 2 ** sirka_slova/. Pri pouziti
dvojkoveho doplnku pro cisla typu signed je konverze trivialni
a ve skutecnosti nedojde ke zmene bitu v danem cisle.
Pri konverzi objektu typu unsigned na objekt typu long, bude
hodnota vysledku numericky rovna hodnote objektu typu unsigned.
Pri konverzi se pouze doplni potrebny pocet nul z leva do cisla
typu long.
6.6 Aritmetické konverze
Velky pocet operatoru zpusobuje konverze a vytvari vysledny
typ podobnym zpusobem. Nasleduji tzv. obvykle aritmeticke kon-
verze.
- operandy typu char a short jsou konvertovany na typ int, ope-
randy typu float jsou konvertovany na typ double.
- jestlize jeden z operandu je typ double, jsou na tento typ konvertovany i zbyvajici operandy a take typ vysledku je
double.
- jinak, jestlize je jeden z operandu typu long, jsou na tento typ konvertovany i zbyvajici operandy a take typ vysledku
long.
- jinak, jestlize je jeden z operandu typu unsigned, jsou na tento typ konvertovany i zbyvajici operandy a take typ vy-
sledku je unsigned.
- jinak musi byt operandy typu int a to je take typ vysledku.
7. Výrazy
Priorita operatoru ve vyrazech je ta sama jako je poradi
odstavcu v teto casti prirucky /tj. c.7/. Odstavec, ktery pred-
chazi jine, popisuje operatory, ktere maji vyssi prioritu.
Napriklad odstavec 7.4 popisuje operatory, ktere maji vyssi
prioritu nezli operatory, ktere jsou popisovany v odstavcich
7.5 a dalsich. Operatory uvnitr kazdeho odstavce maji totoznou
prioritu. Asociativita operatoru zleva ci zprava je uvedena pro
kazdou skupinu operatoru zvlast. Priorita a asociativita vsech
operatoru je shrnuta v odstavci c. 18.
Mimo pravidla vyse uvedena je poradi vyhodnoceni vyrazu
nedefinovano. Prekladac sam rozhodne o takovem vyhodnoceni
castecnych vyrazu, ktere povazuje za efektivni, bez ohledu na
vedlejsi efekt takovehoto vyhodnoceni. Poradi ve kterem dojde
k vedlejsim efektum neni specifikovano. Vyrazy, ktere obsahuji
komutativni a asociativni operatory (*,+,&,|,`) mohou byt
prekladacem preskupeny, dokonce i kdyz obsahuji zavorky. Pri
snaze dodrzet zadane poradi vyhodnoceni vyrazu musely by byt
pouzity prechodne promenne.
Osetreni situaci aritmetickeho preteceni a deleni nulou
pri vyhodnoceni vyrazu je zavisle na technickem vybaveni.
Vsechny existujici implementace jazyka C ignoruji celocisel-
ne preteceni. Osetreni deleni nulou a vsech vyjimecnych situa-
ci pri zpracovani cisel v plovouci carce je na ruznych poci-
tacich odlisne a je obvykle umisteno v knihovne aritmetickych
funkci jazyka C.
7.1 Základní výrazy
Zakladni vyrazy vcetne . , - >, indexovych vyrazu a funk-
cnoch volani jsou nasledujici:
zakladni_vyraz: identifikator
konstanta
retezec
(vyraz)
zakladni_vyraz[vyraz]
zakladni_vyraz(seznam_vyrazu???)
zakladni_lhodnota.identifikator
zakladni_vyraz->identifikator
seznam_vyrazu:
vyraz
seznam_vyrazu, vyraz
Identifikator je zakladnim vyrazem, jestlize je deklarovan
vhodnym zpusobem, tak jak bude popsano dale. Typ identifikatoru
je dan jeho deklaraci. Jestlize typ identifikatoru je 'array
of', hodnota identifikatoru ve vyrazu je hodnotou ukazovat-
ka na prvni objekt pole a typ vyrazu je 'pointer to'. Navic
identifikator pole neni vyraz typu lhodnoty. Podobne identifi-
kator deklarovany jako 'function returnig' pouzity mimo
funkcni volani je chapan jako 'pointer to function retur-.po 12
ning '.
Konstanta je zakladni vyraz. Jeji typ muze byt int, long
nebo double. Znakova konstanta ma typ int a konstanta v
plovouci carce ma typ double.
Retezec je zakladnim vyrazem. Jeho typ je vpodstate
'array of char ', ale plati pro nej ta sama pravidla,
jaka byla jiz popsana pro identifikatory. Tj. typ je
modifikovan na 'pointer to char' a vysledkem je ukazovatko
na prvni znak retezce. Existuje vyjimka z techto pravidel,
ktera se uplatnuje pri inicializaci retezce, viz. odstavec
Ozavorkovany vyraz je zakladnim vyrazem jehoz typ je iden-
ticky s typem neozavorkovaneho vyrazu. Pritomnost zavorek ne-
ma vliv na to, je-li vyraz hodnotou nebo neni.
Zakladni vyraz nasledovany vyrazem v hranatych zavorkach
je zakladnim vyrazem. Intuitni pojeti takoveho vyrazu je, ze
se jedna o indexovy vyraz. Zakladni vyraz ma typ 'pointer
to ', indexovy vyraz ma typ int a typ vysledku je ''.
Vyraz E1 [E2] je identicky /svoji definici/ s vyrazem
*((E1) + (E2)). Dalsi informace potrebne pro pochopeni vyse
uvedene notace jsou uvedeny v odstavcich 7.1, 7.2 a 7.4.
Celkove shrnuti je dale obsazeno v odstavci 14.3.
Funkcni volani nasledovane seznamem vyrazu, ktere jsou
uzavreny v zavorkach, je zakladnim vyrazem. Seznam vyrazu muze
byt prazdny. Zakladni vyraz je typu 'function returning '
a vysledek vraceny funkcnim volanim je typu ''. Identifi-
kator, ktery je bezprostredne nasledovan levou zavorkou je
kontexove chapan jako funkce, ktera vraci hodnotu typu int
a nemusi byt proto deklarovan.
Vsechny skutecne argumenty typu float jsou konvertovany
pred vyvolanim funkce na typ double. Argumenty typu char a
short jsou obdobne konvertovany na typ int a nazvy poli jsou
konvertovany na typ pointer. Zadne dalsi konverze se automa-
ticky neuskutecnuji, prekladac nekontroluje, zdali typy sku-
tecnych argumentu odpovidaji typum argumentu formalnich.
Jestlize je takova konverze potrebna je nutne pouzit explicitni
konverzi, viz. odstavce 7.2 a 8.7.
Pri priprave funkcniho volani se vytvori kopie kazdeho
skutecneho argumentu, jedna se tedy o prenos argumentu jejich
hodnotou. Funkce smi zmenit hodnoty vsech svych formalnich
argumentu aniz dojde ke zmene hodnot skutecnych argumentu.
Na druhe strane je mozne prenaset ukazovatka na dane objekty,
ktere pak mohou byt funkci meneny. Nazev pole je chapan jako
ukazovatko. Poradi vyhodnoceni argumentu neni jazykem defino-
vano, zavisi od pouziteho prekladace. Jsou dovolena rekur-
zivni volani libovolne funkce.
Zakladni vyraz bezprostredne nasledovany teckou a identifi-
katorem je zakladnim vyrazem. Prvni vyraz musi byt lhodnotou
urcujici strukturu nebo union. Identifikator musi byt nazev
polozky struktury nebo unionu.
Zakladni vyraz nasledovany sipkou /je vytvorena ze znaku
'-' a '>'/ a identifikatorem je vyrazem. Prvni vyraz musi byt
lhodnotou urcujici strukturu nebo union. Identifikator musi
byt nazev polozky struktury nebo unionu. Vysledkem je lhodnota
urcujici danou polozku struktury nebo unionu, na kterou ukazuje
vyraz typu ukazovatko..po 3
Vyse uvedena pravidla znamenaji ze vyrazy E1->MOS a
(*E1).MOS jsou totozne. Struktury a uniony jsou objasneny
v odstavci c.8.5.
7.2 Unární operátory
Vyrazy s unarnimi operatory jsou vyhodnocovany zprava doleva.
unarni vyraz:
*vyraz
&lhodnota
-vyraz
!vyraz
^vyraz
++lhodnota
--lhodnota
lhodnota++
lhodnota--
(nazev_typu) vyraz
SIZEOF vyraz
SIZEOF (nazev_typu)
Unarni operator '*' znamena tzv. neprimy pristup. Vyraz musi
byt ukazovatko a vysledkem je lhodnota urcujici objekt, na
ktery vyraz ukazuje. Jestlize typ vyrazu je 'pointer to ',
potom typ vysledku je ''.
Vysledkem unarniho operatoru '&' je ukazovatko na objekt ur-
ceny lhodnotou. Jestlize typ lhodnoty je '', potom typ vys-
ledku je 'pointer to '.
Vysledek unarniho operatoru '-' je negace jeho operandu.
Jsou uskutecneny obvykle aritmeticke konverze. Negace hodnoty
bez znamenkoveho typu je vypoctena tak, ze hodnota vyraz je
odectena od hodnoty 2**n, kde n je pocet bitu cisla typu int.
Neexistuje unarni operator +.
Vysledkem operatoru logicke negace '!' je hodnota 1, jestli-
ze hodnota operandu je 0 a hodnota 0, jestlize hodnota operandu
je ruzna od hodnoty 0. Typ vysledku je int. Muze byt pouzit ve
vyrazech s libovolnym aritmetickym typem nebo ve vyrazech s
ukazovatky.
Operator '^' vytvari jednotkovy komplement sveho operandu.
Pri jeho pouziti se uskutecni obvykle aritmeticke konverze.
Operand musi byt typu int.
Operator '++', ktery predchazi svuj operand zpusobi jeho
inkrementaci. Tato hodnota se stane novou hodnotu objektu,
ktery je urcen lhodnotou, ale samotna tato hodnota neni lhodno-
tou. Vyraz ++x je rovnoceny vyrazu x += 1. Dalsi informace jsou
v odstavcich 7.4 /scitani/ a 7.14 /operatory prirazeni/.
V techto odstavcich jsou take informace o konverzich,ktere se
pri pouziti tohoto operatoru uskutecni.
Operator '--', ktery predchazi svuj operand, zpusobi jeho
dekrementaci. Ostatni vlastnosti jsou analogicke vlastnostem
predchazejiciho operatoru.
Pri pouziti operatoru '++', ktery nasleduje svuj operand
se stane vysledkem hodnota objektu, ktera je urcena lhodnotou
a teprve potom dojde k inkrementaci objektu. Typ vysledku je.po 12
tentyz jako je typ lhodnoty.
Operator '--', ktery nasleduje svuj operand ma vlastnosti
analogicke vlastnostem predchazejiciho operatoru.
Jmeno typu, ktere je uzavreno mezi zavorky a je nasledovano
vyrazem, zpusobi ze hodnota vyrazu bude konvertovana na odpo-
vidajici hodnotu daneho typu. Konstrukce tohoto typu se nazyva
'cast'. Jmena typu jsou vysvetlena v odstavci 8.7.
Operator SIZEOF poskytuje hodnotu, ktera udava velikost
operandu /ve slabikach/. Termin slabika /byte/ neni jazykem
definovan vyjma vztahu k hodnote, ktera je poskytovana opera-
torem SIZEOF. Nicmene, ve vsech existujicich implementacich
jazyka C je byte chapan jako rozmer objektu char. Pokud je ten-
to operator pouzit na objekt typu pole, je vracena hodnota uda-
vajici celkovy pocet slabik pole. Velikost operandu je urcena
prostrednictvim deklaraci jednotlivych objektu ve vyrazu. Tento
vyraz je semanticky celociselnou konstantou a muze byt pouzit
vsude tam, kde je dovoleno pouzit konstanty. Hlavni pouziti
tohoto operatoru je v komunikacich s podprogramy jako jsou
alokacni podprogramy a podprogramy systemoveho vstupu a vystu-
pu.
Operator SIZEOF muze byt take pouzit se jmenem typu, ktere
je uzavreno v zavorkach. V tomto pripade poskytne velikost
daneho typu ve slabikach. Konstrukce SIZEOF (type) je chapana
jako celek. Vyraz SIZEOF (type)-2 je totozny s vyrazem
(SIZEOF(type))-2.
7.3 Multiplikativní operátory
Multiplikativni operatory jsou *, / a %. Jsou vyhodnocovany
zleva doprava a taky se pri jejich zpracovani uskutecni obvyk-
le aritmeticke konverze.
Multiplikativni_vyraz:
vyraz * vyraz
vyraz / vyraz
vyraz % vyraz
Binarni operator '*' oznacuje soucin. Tento operator je
asociativni a vyraz, obsahuje nekolik soucinu na te same urovni
muze byt prekladacem preskupen.
Binarni operator '/' oznacuje deleni. Pri deleni kladnych
cisel je vysledek zaokrouhlen smerem k nule, pri deleni zapor-
nych cisel je zpusob zaokrouhleni zavisly na technickem vyba-
veni pocitace. Na vsech jiz vyse uvedenych pocitacich ma zbytek
po deleni vzdy stejne znamenko jako delenec. Tzn. ze plati
nasledujici vztah: (a/b)*b + a%b = a /pokud je b ruzne od 0/.
Binarni operator '%' poskytuje zbytek po deleni prvniho ope-
randu druhym. Uskutecni se obvykle aritmeticke konverze. Ope-
randy nesmeji byt typu float.
7.4 Aditivní operátory
Aditivni operatory jsou + a -. Jsou vyhodnocovany zleva dop-
rava a take se pri jejich zpracovani uskutecni obvykle aritme-
ticke konverze. V nekterych pripadech mohou byt operandy ruz-
nych typu.
Aditivni_vyraz:
vyraz + vyraz
vyraz - vyraz
Operator '+' poskytuje sumu svych operandu. Muze byt vytvo-
ren soucet ukazovatka ukazujiciho na dany objekt a hodnoty
celociselneho typu. Druhy operand je ve vsech pripadech kon-
vertovan na posunuti ve slabikach, ktere je ziskano soucinem
druheho operandu s velikosti objektu, na ktery ukazuje ukazo-
vatko. Vysledkem je ukazovatko na objekt tehoz typu. Tedy
jestlize P je ukazovatko na objekt v poli, potom P+1 je ukazo-
vatko na nasledujici objekt pole. Zadne dalsi kombinace typu
operandu jiz nejsou dovoleny.
Operator '+' je asociativni a vyraz, ktery obsahuje neko-
lik souctu na teze same urovni muze byt prekladacem preskupen.
Operator '-' poskytuje rozdil operandu. Pri jeho vyhodno-
ceni se uskutecni obvykle aritmeticke konverze. Hodnota libo-
volneho celociselneho typu muze byt odectena od ukazovatka,
potom se uskutecni analogicke konverze, ktere se uskutecni
pri pouziti operatoru '+'.
Jestlize se uskutecni rozdil dvou ukazovatek, ktere ukazuji
na objekty stejneho typu, potom je vysledek konvertovan, dele-
nim velikosti objektu, na hodnotu typu int, ktera predstavuje
pocet objektu, ktere oddeluji objekty, na ktere ukazuji ukazo-
vatka. Tato konverze bude davat necekane vysledky pokud ukazo-
vatka nebudou ukazovat do tehoz pole, protoze potom, i v
situaci, kdy pole budou obsahovat objekty tehoz typu, neni
zaruceno, ze rozdil ukazovatek bude celistvym nasobkem delky
objektu.
7.5 Operatory posuvu
Operatory posuvu << a >> se vyhodnocuji zleva doprava. Pri
jejich vyhodnoceni se uskutecni obvykle aritmeticke konverze.
Oba operandy musi byt celociselnych typu. Pravy operand je
vzdy konvertovan na typ int. Typ vysledku je vzdy roven typu
leveho operandu. Vysledek neni definovan, jestlize pravy ope-
rand je negativni, nebo je vetsi anebo roven velikosti leveho
operandu v bitech.
vyraz_posuvu:
vyraz << vyraz
vyraz >> vyraz
Hodnota vyrazu E1<<E2 je rovna hodnote E1, ktera je v tomto
pripade chapana jako retezec bitu, posunute o E2 bitu vlevo.
Hodnota vyrazu E1>>E2 je rovna hodnote E1 posunute o E2 bitu.po 12
vpravo. Posuv vpravo je logickym posuvem /uvolnene bitove po-
zice jsou doplnovany 0/, jestlize je operand na leve strane
typu unsigned. Pokud neni typu unsigned muze se jednat /napr.
na systemu PDP-11/ o posuv aritmeticky /uvolnene bity se zapl-
nuji kopii znamenkoveho bitu/.
7.6 Relacni operatory
Relacni operatory jsou vyhodnocovany zleva doprava, ale
tato skutecnost neni priliz uzitecna. Vyraz a<b<c nema ten
samy vyznam jako v algebre.
relacni_vyrazy
vyraz < vyraz
vyraz > vyraz
vyraz <= vyraz
vyraz >= vyraz
Operatory < /mensi nez/, > /vetsi nez/, <= /mensi nebo rovno/
a >= /vetsi nebo rovno/ poskytuji hodnotu 0, jestlize je dany
vyraz nepravdivy a hodnotu 1, jestlize je dany vyraz pravdivy.
Typ vysledku je int. Pri vyhodnoceni se uskutecni obvykle
aritmeticke konverze. Mohou byt porovnavany take dve ukazovat-
ka, vysledek je potom zavisly na umisteni danych objektu, na
ktere obe ukazovatka ukazuji, v adresnim prostoru pocitace.
Porovnavani ukazovatek je prenositelne pouze v tom pripade, kdy
se jedna o ukazovatka ukazujici do tehoz pole.
7.7 Operatory porovnani
vyraz_porovnani:
vyraz == vyraz
vyraz != vyraz
Operatory == /rovno/ a != /nerovno/ jsou analogicke operatorum
relacnim, vyjma jejich nizsi priority. Tedy vyraz a<b == c>d
je roven hodnote 1, kdykoliv vyrazy a<b a c>d poskytuji tutez
pravdivostni hodnotu.
Objekt typu ukazovatko muze byt porovnavan s hodnotou typu
int, ale vysledek je zavisly na technickych prostredcich vypo-
cetniho systemu, pokud se nejedna o porovnani s konstantou
typu int rovnou 0. Ukazovatko, ktere ma hodnotu 0 neukazuje
na zadny objekt. V beznem pouziti se predpoklada, ze takove
ukazovatko ma hodnotu NULL.
7.8 Bitovy operator AND
AND_vyraz:
vyraz & vyraz
Operator '&' je asociativni a vyraz obsahujici vice techto ope-
ratoru muze byt preskupen. Uskutecni se obvykle aritmeticke
konverze. Vysledek je provedeni funkce AND na vsech bitech ope-.po 3
randu. Operator muze byt pouzit pouze s operandy typu int.
7.9 Bitovy operator XOR
XOR_vyraz:
vyraz ^ vyraz
Operator '^' je asociativni a vyraz obsahujici vice techto ope-
ratoru muze byt preskupen. Uskutecni se obvykle aritmeticke
konverze. Vysledek je provedeni funkce XOR na vsech bitech ope-
randu. Operator muze byt pouzit pouze s operandy typu int.
7.10 Bitovy operator OR
OR_vyraz:
vyraz | vyraz
Operator '|' je asociativni a vyraz obsahujici vice techto ope-
ratoru muze byt preskupen. Uskutecni se obvykle aritmeticke
konverze. Vysledek je provedeni funkce OR na vsech bitech ope-
randu. Operator muze byt pouzit pouze s operandy typu int.
7.11 Logicky operator AND
logicky_and_vyraz:
vyraz && vyraz
Operator '&&' je vyhodnocovan zleva doprava. Vrati hodnotu 1,
jestlize jsou oba operandy ruzne od 0, jinak vrati hodnotu 0.
Na rozdil od operatoru '&' zarucuje operator '&&' vyhodnoceni
vyrazu zleva doprava. Navic druhy operand neni vubec vyhodnoco-
van, jestlize jiz byla vyhodnocena hodnota prvniho operandu
rovna 0.
Operandy nemusi byt tehoz typu, ale musi se jednat o typy
zakladni nebo ukazovatka. Vysledek je vzdy typu int.
7.12 Logicky operator OR
logicky_or_vyraz:
vyraz || vyraz
Operator '||' je vyhodnocovan zleva doprava. Vrati hodnotu 1,
jestlize aspon jeden z jeho operandu je ruzny od hodnoty 0,
jinak vrati hodnotu 0. Na rozdil od operatoru '|' zarucuje
operator '||' vyhodnoceni vyrazu zleva doprava. Navic druhy
operand neni vubec vyhodnocovan, jestlize jiz byla vyhodnocena
hodnota prvniho operandu ruzna od 0.
Operandy nemusi byt tehoz typu, ale musi se jednat o
zakladni typy nebo ukazovatka. Vysledek je vzdy typu int.
.pa.po 12
7.13 Operator podminky
vyraz_podminky:
vyraz ? vyraz : vyraz
Vyrazy s podminkou jsou vyhodnocovany zprava doleva. Nejprve
je vyhodnocen prvni vyraz, a jestlize je ruzny od 0 je vysled-
kem hodnota druheho vyrazu, jinak je vysledkem hodnota
tretiho vyrazu. Jestlize je to mozne, uskutecni se obvyk-
le aritmeticke konverze tak, aby druhy a treti vyraz mely
tentyz typ. Jestlize se jedna o ukazovatka na objekty tehoz
typu, vysledek ma tentyz typ. Pokud je jeden vyraz ukazovat-
ko a druhy konstanta rovna 0, ma vysledek typ ukazovatka.
Vyhodnuceje se vzdy pouze jeden z vyrazu za znakem '?'.
7.14 Operatory prirazeni
Je velky pocet operatoru prirazeni, ktere jsou vyhodnoco-
vany zprava doleva. Vsechny vyzaduji na sve leve strane lhod-
notu a vysledny typ prirazeni je dan take operandem na leve
strane. Hodnota operatoru prirazeni je hodnota, ktera je
ulozena do operandu na leve strane. Obe dve casti slozeneho
operatoru prirazeni jsou samostatne syntakticke jednotky.
vyraz prirazeni:
lhodnota = vyraz
lhodnota += vyraz
lhodnota -= vyraz
lhodnota *= vyraz
lhodnota /= vyraz
lhodnota %= vyraz
lhodnota >>= vyraz
lhodnota <<= vyraz
lhodnota &= vyraz
lhodnota ^= vyraz
lhodnota != vyraz
Pri jednoduchem prirazeni /=/ je hodnota vyrazu umistena
do objektu, ktery je urcen lhodnotou. Jestlize oba operandy
maji aritmeticky typ, je pravy operand pred uskutecnenim
prirazeni konvertovan na typ leveho operandu.
Vyraz prirazeni ve forme E1 op = E2 je analogicky vyrazu
E1 = E1 op(E2), vyjma toho faktu, ze vyraz E1 je vyhodnocovan
pouze jednou. U slozenych vyrazu += a -= muze byt levy operand
ukazovatkem. Potom je pravy /celociselny/ operand konvertovan
tak, jak je vysvetleno v odstavci 7.4. Vsechny prave operandy
a vsechny leve operandy, ktere nejsou ukazovatky musi mit ari-
tmeticke typy.
Prekladac skutecne umoznuje, aby hodnota ukazovatka mohla
byt prirazena promenne typu int, aby hodnota typu int mohla
byt prirazena k ukazovatku, a hodnota ukazovatka ukazovatku
jineho typu. Operace prirazeni je pouhym prekopirovanim hod-
not bez konverzi. Takoveto pouziti neni prenositelne, a mohou
vzniknout ukazovatka, ktera nemaji smysl. Nicmene pri prirazeni.po 3
konstanty 0 ukazovatku je zaruceno, ze ukazovatko nebude ukazo-
vat na zadny objekt.
7.15 Operator carky
vyraz_s_carkou:
vyraz , vyraz
Dvojice vyrazu oddelenych carkou je vyhodnocena zleva doprava
a hodnota leveho vyrazu je zapomenuta. Typ a hodnota vysledku
je stejna jako typ a hodnota praveho operandu. V takovem
kontextu, kde carka muze mit nejaky specialni vyznam, napriklad
v seznam skutecnych argumentu funkce /odstavec 7.1/ nebo
seznamu pri inicializaci /odstavec 8.6/, muze se operator ','
ve_vyznamu, ktery je popisovan v tomto odstavci vyskytovat
pouze mezi zavorkami. Napriklad:
f(a, (t=3, t+2), c)
Tato funkce ma tri argumenty, druhy z nich ma hodnotu 5.
8. Deklarace
Deklarace jsou uzivany pro specifikaci jednotlivych identi-
fikatoru C jazyka. Nemusi vzdy uskutecnit soucasne rezervaci
mista pameti pro dany identifikator. Deklarace maji nasledujici
formu:
deklarace:
specifikator_deklarace seznam_deklarace???;
Seznam_deklarace obsahuje nazvy identifikatoru, ktere maji byt
deklarovany.
Specifikator_deklarace se sklada ze specifikace typu a tri-
dy ulozeni.
specifikator_deklarace:
specifikace_typu specifikator_deklarace???
specifikace_ulozeni specifikator_deklarace???
Seznam deklaraci musi byt usporadan danym zpusobem, ktery bude
dale vysvetlen.
8.1 Specifikace tridy ulozeni
specifikace_ulozeni:
AUTO
STATIC
EXTERN
REGISTER
TYPEDEF
Specifikace TYPEDEF nerezervuje zadnou pamet pro ulozeni.po 12
objektu a je zarazena mezi specifikace ulozeni pouze z duvodu
syntakticke konvence. Tato specifikace je vysvetlena v odstav-
ci c. 8.8. Vyznam zbyvajicich specifikaci byl objasnen v
odstavci c. 4.
Specifikace AUTO, STATIC a REGISTER jsou soucasne i defini-
cemi a zpusobuji rezervovani prislusneho mnozstvi pameti. V
pripade specifikace EXTERN se jedna o externi definici /viz.
odstavec c.10./ identifikatoru definovaneho mimo funkci ve
ktere je deklarovan.
Deklarace REGISTER je nejlepsi zpusob deklarace objektu,
pro ktery by mohla byt pouzita deklarace AUTO. Tato deklara-
ce naznaci prekladaci, ze se jedna o promennou, ktera bude
velmi casto pouzivana. Pouze nekolik prvnich takovych dekla-
raci vsak bude efektivnich. Navic, pouze nektere typu promen-
nych mohou byt ulozeny v registrech. Na systemu PDP-11 se
jedna o typy int, char a ukazovatko. Dalsi vyjimka, ktera
plati pro registrovane promenne je ta, ze na ne nelze pouzit
adresovy operator '%'. Pri pouziti registrovych promennych
je mozne ocekavat mensi a rychlejsi programy, ale budouci
zlepseni generace strojoveho kodu prekladacem je mohou ucinit
zbytecnymi.
Nejvyse jedna specifikace_ulozeni muze byt v jedine deklara-
ci. Jestlize specifikace_ulozeni chybi, predpoklada se specifi-
kace AUTO uvnitr funkci a extern mimo ne. Vyjimkou jsou dekla-
race funkci, ktere nikdy nejsou AUTO.
8.2 Specifikace typu
Specifikace_typu:
CHAR
SHORT
INT
LONG
UNSIGNED
FLOAT
DOUBLE
specifikator_structury
specifikator_unionu
typedef_jmeno
Nazvy LONG, SHORT a UNSIGNED mohou byt pouzita jako adjektiva.
Jsou dovoleny nasledujici kombinace:
SHORT INT
LONG INT
UNSIGNED INT
LONG FLOAT
Vyznam posledni kombinace je tentyz jako specifikace DOUBLE.
Jinak smi byt v deklaraci pouzit pouze jedna specifikace typu.
Jestlize specifikace typu v deklaraci chybi, predpoklada se
specifikace INT.
Specifikace struktur a unionu jsou vysvetleny v odstavci
c. 8.5, deklarace s konstrukci typedef_jmeno jsou objasneny v
odstavci c. 8.8..po 3
8.3 Deklaratory
Seznam deklaratoru je seznam polozek oddelenych carkami, z
nichz kazda muze obsahovat inicializacni cast.
seznam_deklaratoru:
init_dekladator
init_deklarator seznam_deklaratoru
init_deklarator:
deklarator iniciator???
Inicializatory jsou vysvetleny v odstavci c. 8.6. Specifikace
v deklaraci urcuji typ a tridu ulozeni objektu, ktere jsou
urceny deklaratory.
Syntaxe deklaratoru je nasledujici:
deklarator:
identifikator
(deklarator)
*deklarator
deklarator()
deklarator [konstantni vyraz???]
8.4 Vyznam deklaratoru
Kazdy deklarator je v podstate tvrzenim o vlastnostech
nejakeho objektu. Pokud se takovy deklarator te same formy
vyskytne ve vyrazu, urcuje objekt daneho typu a tridy ulozeni.
Kazdy deklarator obsahuje pouze jediny identifikator, a to
ten, ktery je prave deklarovan.
Pokud ma deklarator tvar pouheho identifikatoru, ziska
tento identifikator vlastnosti urcene specifikacni casti
deklarace.
Deklarator uzavreny mezi zavorkami je identicky s deklara-
torem, ktery mezi zavorkami uzavren neni. Moznost zavorek je
zde proto, aby bylo mozne vytvaret slozene deklaratory, je-
jichz vyznam je urcen umistenim zavorek.
Predpokladejme nasledujici deklaraci:
T D1
Kde T je specifikator typu /napriklad INT atd./ a D1 je dekla-
rator. Predpokladejme, ze tato deklarace vytvori identifikator
majici typ 'T', kde '' je prazdny retezec pokud se jedna
o zakladni identifikator. Tedy typ identifikatoru x je INT,
pokud byl deklarovan deklaraci: 'INT x'. Pokud ma deklarator
D1 tvar:
*D
typ identifikatoru je:' pointer to T'. Jestlize ma deklara-
tor D1 tvar:
.po 12
D()
Typ identifikatoru je ' function returning T'. Jestlize ma
deklarator D1 tvar:
D [konstantni_vyraz]
nebo
D[]
typ identifikatoru je ' array of T'. V prvnim pripade je
konstantni vyraz vyhodnocen v dobe prekladu a jeho typ je int.
Konstantni vyrazy jsou definovany v odstavci c. 15. Pokud je
nekolik specifikaci 'array of' vedle sebe, je vytvoreno vice-
rozmerne pole. Konstantni vyraz, ktery urcuje velikost pole
je nepovinny pouze u prvniho clenu vicenasobne specfikace
'array of'. Tato moznost je vyhodna,pokud se jedna o externi
pole a jeho skutecna definice je v jine casti programu. Prvni
konstantni vyraz muze chybet take v pripade, kdy je deklarator
nasledovan inicializatorem. V takovem pripade je velikost pole
urcena z poctu polozek inicalizatoru.
Pole smi byt utvoreno z jednoho ze zakladnich typu, z ukazo-
vatek, ze struktur nebo unionu, anebo z jineho pole /potom se
jedna o pole vicerozmerne/.
Ve skutecnosti vsak nejsou dovoleny vsechny moznosti vytvo-
reni deklaratoru, ktere povoluje vyse uvedena syntaxe. Omezeni
jsou nasledujici:
- funkce nesmeji vracet objekty typu pole, struktury, unionu
nebo funkce, ackoliv mohou vracet ukazovatka na vyse uvedene
objekty
- neexistuji pole funkci, ale mohou existovat pole ukazova-
tek na funkce
- struktury a uniony nesmeji obsahovat funkce, ale mohou obsa-hovat ukazovatka na funkce.
Napriklad deklarace:
INT i, *ip, f(), *fip(), (*pfi)();
deklarujici celociselnou promennou i, ukazovatko ip na promen-
nou typu int, funkci f, ktera vraci promennou typu int, funkci
fip, ktera vraci ukazovatko na promennou typu int a ukazovatko
pfi na funkci, ktera vraci promennou typu int. Je velmi uzi-
tecne srovnat posledni dve deklarace. Deklaraci *fip() je moz-
ne take napsat ve tvaru *(fip()), ze ktereho je videt, ze po-
dobne jako pri vyhodnoceni vyrazu se nejprve vola funkce fip
a terpve pote se uskutecni pristup pomoci ukazovatka. Pri
deklaraci (*pfi)() jsou zavorky navic nutne, protoze podobne
jako pri vyhodnoceni vyrazu, se uskutecni nejprve pristup
pomoci ukazovatka na funkci, ktera vraci hodnotu typu int.
Uvedeme dals priklady:
FLOAT fa [17], *afp[17];
Vyse uvedena deklarace deklaruje pole cisel typu float a pole
ukazovatek na cisla typu float. .po 3
Konecne deklarace:
STATIC INT x3d[3][5][7];
deklaruje staticke trirozmerne pole promennych typu int,s
celkovym rozmerem 3x5x7. Pole x3d se sklada ze tri polozek,
kazda tato polozka je polem, ktere se sklada z peti polozek,
ktere jsou take poli, ktera maji 7 polozek typu int. Ve vyra-
zech se muze vyskytovat libovolny z nasledujicich vyrazu, ktere
odkazuji na dane pole: x3d, x3d[i], x3d[i][j], x3d[i][j][k].
Prve tri maji typ 'array' a posledni ma typ int.
8.5 Deklarace struktur a unionu
Struktura je objekt skladajici se z rady pojmenovanych po-
lozek. Kazda polozka muze mit libovolny typ. Union je objekt,
ktery muze mit v danem okamziku jeden z vice libovolnych typu.
Struktury a uniony se specifikuji tou samou formou.
specifikator_struktury nebo unionu:
struktura/union
struktura/union identifikator
struktura/union identifikator
struktura/union:
STRUCT
UNION
Seznam_struktury je rada deklaraci pro jednotlive cleny struk-
tury nebo unionu.
seznam_struktury:
deklarace_struktury
deklarace_struktury seznam_struktury
deklarace_struktury:
specifikator_typu seznam_ deklaratoru;
seznam_deklaratoru:
deklarator_struktury
deklarator_struktury, seznam_deklaratoru
V obvyklem pripade je deklarator_struktury obycejnym deklara-
torem jednoduche struktury nebo unionu. Struktura se muze
ale take skladat ze specifikovaneho poctu bitu. Takova polozka
se nazyva 'pole bitu'. Jeho velikost je dana konstantnim vyra-
zem, ktery je od nazvu pole bitu oddelen znakem ':'.
deklarator_struktury:
deklarator
deklarator: konstantni_vyraz
: konstantni vyraz
Objekty uvnitr struktury jsou umisteny na adresy podle toho,
jake je jejich poradi v deklaraci struktury, a to tak, ze .po 12
objekty, ktere jsou v deklaraci vice vpravo, maji vyssi adre-
sy. Kazdy clen struktury, ktery neni polem bitu je umisten
na adresu podle sveho typu. Proto mohou vzniknout uvnitr
struktury mista, ktera nejsou prirazena zadne promenne. Pole
bitu jsou zarovnany do objektu typu int. Jedno pole bitu
nesmi byt nikdy soucasne umisteno ve dvou takovych objektech.
Pole bitu, ktere se nevejde do prostoru, ktery v jiz pouzi-
tem objektu typu int zbyva, je umisteno cele do dalsich objek-
tu. Zadne pole bitu nemuze byt vetsi nezli je objekt typu int,
ktery je dan technickymi prostredky vypocetnho systemu. Pole
bitu jsou objektum typu int prirazovana zprava doleva na sys-
temu PDP-11 a zleva doprava na ostatnich vyse uvedenych vypo-
cetnich systemech.
Deklarator_struktury, ktery se sklada pouze ze znaku ':'
a definice velikosti pole bitu ustanovuje nepojmenovane pole
ktere muze byt pouzito pro uspokojeni pozadavku, ktere
jsou dany vnejsim vybavenim systemu. Specialnim pripadem je
pole bitu delky 0. Takova definice vynuti umisteni dalsiho po-
le bitu na pocatku noveho objektu typu int. Normalni cleny
struktury, tj. nikoliv pole bitu, se vzdy umistuji od pocat-
ku objektu typu int.
Jazyk C nedefinuje, jakeho typu maji byt pole bitu, ale na
implementacich jazyka C neni pozadovano, aby dovolovaly jina
pole bitu, nez celociselneho typu. Tato pole mohou byt vsak
povazovana za pole typu unsigned. Na systemu PDP-11 jsou pole
bitu implementovana jako promenne typu int unsigned. Ve vsech
vyse uvedenych implementacich neexistuji pole poli bitu a
adresovy operator '&' nemuze byt na pole bitu aplikovan, ani
nemohou existovat ukazovatka na pole bitu.
Union predstavuje ve sve podstate strukturu, jejiz vsechny
cleny pocinaji na te same adrese/relativni adresa od pocatku
unionu je vzdy rovna 0/ a velikost unionu je vzdy takova, aby
byla dostatecna i pro nejrozmernejsiho clena unionu. V jedinem
okamziku muze byt v unionu ulozen pouze jediny clen jeho struk-
tury.
Specifikator struktury nebo unionu, ktery ma nasledujici
formu:
STRUCT identifikator seznam_struktury
UNION identifikator seznam_struktury
deklaruje identifikator, jako tzv. znacku struktury /znacku
unionu/, ktera je specifikovana danym seznamem struktury.
Dalsi deklarace jiz mohou pouzivat treti formu specifikace
struktury nebo unionu:
STRUCT identifikator
UNION identifikator
Znacky struktury umoznuji definovat struktury, ktere se odka-
zuji na sebe same. Jejich vyhoda spociva take v tom, ze dovolu-
ji, aby dlouhe definice struktury byly zapsany pouze jedinkrat
a posleze pouzity vicekrat. Je zakazano deklarovat struktury
nebo uniony, ktere mohou obsahovat samy sebe, ale je mozne
deklarovat struktury nebo uniony, ktere obsahuji ukazovatka
na sebe sama. .po 3
Nazvy clenu struktury nebo nazev znacky struktury je vytvo-
ren podle stejnych pravidel jaka plati pro nazvy promennych.
Nicmene nazev znacky struktury musi byt rozdilny od nazvu clenu
struktury.
Dve struktury mohou mit spolecne pocatecni polozky, tj. ty
same cleny se mohou vyskytovat ve dvou rozdilnych strukturach,
jestlize jsou tehoz typu a vsechny predchazejici polozky jsou
totozne. Ve skutecnosti prekladac kontroluje, zdali totozna
jmena ve dvou rozdilnych strukturach jsou tehoz typu a maji
stejne relativni posunuti od pocatku struktury. Pokud jsou
predchazejici cleny struktury rozdilne, neni takova konstrukce
prenositelna.
Nasleduje jednoduchy priklad deklarace struktury:
STRUCT tnode;
Tato struktura obsahuje znakove pole o 20 znacich, celocisel-
nou promennou typu int a dve ukazovatka na tutez strukturu.
Jakmile je jednou takova struktura definovana, je mozne ji pou-
zit v dalsich deklaracich:
STRUCT tnode s,*sp;
Vyse uvedena deklarace deklaruje strukturu s s vyse jiz uvede-
nou strukturou a ukazovatko sp, ktere ukazuje na strukturu vyse
jiz uvedene struktury.
S vyuzitim jiz vyse uvedenych deklaraci potom nasledujici vy-
raz:
sp -> count;
urcuje polozku count struktury, ktera je urcena ukazovatkem sp.
s.left
Tento vyraz urcuje ukazovatko, ktere urcuje levou vetev struk-
tury s.
s.right -> tword[0];
V tomto vyrazu ukazovatko prave vetve struktury s ukazuje na
prvni clen polozky tword dane struktury.
8.6 Inicializace
Pri deklaraci je mozne specifikovat pocatecni hodnoty jedno-
tlivych promennych, ktere jsou deklarovany. Inicializator po-
cina znakem '=' a sestava se z vyrazu nebo seznamu hodnot uza-
vrenych mezi zavorky.
.po 12
Inicializator:
= vyraz
= (seznam_inicializaci)
= (seznam inicializaci,)
Seznam_inicializaci:
vyraz
seznam_inicializaci,seznam_inicializaci
(seznam_inicializaci)
Vsechny inicalizace statickych nebo externich promennych mu-
si byt slozeny z konstantnich vyrazu /viz. odstavec c. 15/ nebo
vyrazu, ktere urcuji adresu jiz drive deklarovane promenne,
pripadne jeji relativni umisteni a konstantni vyraz. Automa-
ticke nebo registrove promenne mohou byt inicializovany vyrazy
obsahujici konstanty a jiz drive deklarovane promenne a fun-
kce.
Staticke a externi promenne, ktere nejsou inicializovane,
maji pocatecni hodnotu rovnu 0. Automaticke a registrove pro-
menne, ktere nejsou inicializovane maji pocatecni hodnotu
nedefinovanou /nahodnou/.
Pokud je inicializovan skalar /ukazovatko nebo objekt ari-
tmetickeho typu/, sklada se inicializace z jednoducheho vyra-
zu, ktery muze byt uzavren do zavorek.
Pocatecni hodnota daneho objektu je urcena timto vyrazem, pri
jeho vycisleni se uskutecni stejne konverze jako pri uskutec-
neni prirazeni.
Pokud deklarovana hodnota je agregat /tj. struktura nebo
pole/, inicializator je
vytvoren ze seznamu inicializaci jednotlivych polozek agre-
gatu oddelenych carkami a uzavrenych mezi zavorky, ktere jsou
psany dle vzrustajiciho indexu nebo poradi polozek. Pokud agre-
gat obsahuje vcelenene agregaty, mohou se tato pravidla apli-
kovat rekurzivne. Jestlize je inicializovano mene polozek nez
agregat obsahuje, jsou zbyvajici polozky vyplneny 0. Neni
dovoleno inicializovat uniony a automaticke agregaty.
Zavorky smi byt vynechany za nasledujicich podminek. Jestli-
ze inicializator zacina levou zavorkou, nasledujici seznam
inicializaci oddelenych carkami inicializuje agregat. Je chy-
bou pokud tento seznam obsahuje vice clenu nezli je polozek
agregatu. Naopak, jestlize inicializator nepocina levou zavor-
kou jsou inicializovany pouze nezbytne polozky daneho agre-
gatu a zbyvajici inicializace jsou ponechany pro inicializaci
polozek agregatu, ktereho je vyse mineny agregat.
Je take mozne inicializovat podle znaku /char array/ retez-
cem. V takovem pripade jednotlive znaky retezce inicializuji
jednotlive polozky znakoveho pole.
Nasledujici priklady na inicializace:
INT x[] = (1, 3, 5);
Uvedeny prikaz deklaruje a inicializuje x jako jednorozmerne
pole, ktere ma tri cleny urcene poctem inicializaci.
.pa.po 3
FLOAT y[4][3] = (
(1, 3, 5),
(2, 4, 6),
(3, 5, 7),
);
Uvedena inicalizace plne vyuziva zavorek. Hodnoty 1, 3 a 5
inicializuji prvni radku pole y 0 /jmenovite polozky y[0][0],
y[0][1], y[0][2]. Podobne jsou inicializovany i dve nasledujici
radky pole y[1] a y[2]. Protoze inicializace je zakoncena
predcasne, je posledni radka pole inicializovana hodnotami 0.
Totez je mozne uskutecnit nasledovne:
FLOAT y[4][3] = (
);
Inicializace pro pole y pocina levou zavorkou, ale inicializace
pro radku y[0] nikoliv, proto jsou pouzity pri inicializaci ze
seznamu. Podobne dalsi tri jsou pouzity pro inicalizaci y[1]
a dalsi tri pro inicializaci y[2].
FLOAT y[4][3] =(
);
Tato inicializace inicializuje prvni sloupec pole y /jedna se o
dvourozmerne pole/ a zbyvajici sloupce pole vyplni hodnotami 0.
CHAR msg[] = 'Syntax error on line %sn';
Zde se jedna o inicializaci pole retezcem znaku.
8.7 Nazvy typu
Nazvy typu dat je nutne uvadet ve dvou kontextech. Pri spe-
cifikaci typu konverze v konstrukci cast a jako argument opera-
toru SIZEOF. Je to mozne pouzitim nazvu typu, coz je v podstate
cast deklarace typu objektu, ve kterem chybi jmeno vlastniho
objektu.
Jmeno typu:
specifikace_typu abstraktni_deklarator
Abstraktni_deklarator:
prazdny
(abstraktni_deklarator)
*abstraktni_deklarator
abstraktni_deklarator()
abstraktni_deklarator
[konstantni_vyraz???]
Aby nedoslo ke dvojznacnosti v konstrukci typu:
.po 12
(abstraktni_deklarator)
je pozadovano, aby v teto konstrukci nebyl abstraktni_deklara-
tor prazdny. S timto omezenim je mozne vytvorit stejne konstru-
kce s abstraktnim deklaratorem, jako kdyby se jednalo o skutec-
ny deklarator pri deklaraci. Typ teto konstrukce je stejny jako
typ hypotetickeho identifikatoru.
Nasledujici priklady:
INT
INT *
INT *[3]
INT (*)[3]
INT *()
INT (*)()
Jedna se zde postupne o typy: 'integer', 'ukazovatko na inte-
ger', 'pole tri ukazovatek na integer', 'ukazovatko na pole
tri integer', 'funkce vracejici ukazovatko na integer', 'ukazo-
vatko na funkci vracejici integer'.
8.8. TYPEDEF
Deklarace, jejiz trida ulozeni je TYPEDEF ve skutecnosti
zadnou pamet neprideluje, namisto toho definuje identifikator,
ktery muze byt pozdeji pouzit jako definice zakladniho ci
odvozeneho typu.
typedef_name:
identifikator
Uvnitr typedef deklarace se kazdy identifikator, ktery je sou-
casti nektereho deklaratoru, stava pozdeji syntaktickym ekvi-
valentem klicoveho slova pojmenujiciho typ, ktery je identifi-
katoru prirazen. Stejnym zpusobem jak bylo vysvetleno v odstav-
ci c. 8.4. Napriklad:
TYPEDEF INT miles, *klicksp;
TYPEDEF STRUCT complex;
uzite v konstrukcich:
miles distance;
EXTERN klicksp metricp;
complex z, *zp;
jsou spravne deklarace. Typ distance je int, typ metricp je
'pointer to int', typ z je vyse uvedena struktura a typ zp je
ukazovatko na danou strukturu.
Konstrukce TYPEDEF nevytvari nove typy, pouze synonyma ty-
pu, ktere by mohly byt utvoreny normalnim postupem. Napriklad
distance ma uplne stejny typ jako objekt, ktery byl deklarovan
s typem int.
.pa.po 3
9. Prikazy
Prikazy jsou normalne vykonavany v tom poradi, v jakem jsou
zapsany.
9.1 Vyrazove prikazy
Zakladnimi prikazy jsou vyrazove prikazy, ktere maji nasleduji-
ci formu:
vyraz;
Nejcastejsim typem techto prikazu jsou prirazeni nebo funkcni
volani.
9.2 Slozene prikazy nebo bloky
Je mozne pouzit nekolik prikazu tam, kde by mel byt pouzit
pouze jediny. Je to mozne pomoci slozenych prikazu, ktere jsou
take nazyvany bloky.
slozeny_vyraz:
seznam_deklaraci??? seznam_prikazu???
seznam_deklaraci:
deklarace
deklarace seznam_deklaraci
seznam_prikazu:
prikaz
prikaz seznam_prikazu
Jestlize je nektery z identifikatoru, ktere jsou deklarovany
totozny s identifikatorem jiz drive deklarovanym, je vnejsi
deklarace behem vykonavani bloku potlacena a po ukonceni bloku
opet obnovena.
Vsechny inicializace promennych typu auto nebo register jsou
vzdy pri opetovnem pristupu do bloku provedeny, pokud je pri-
stup uskutecnen na zacatek bloku. Je mozne, ale neni to dobra
praxe, prenest rizeni primo doprostred bloku, potom k iniciali-
zacim nedojde. Inicializace promennych typu static se uskutecni
pouze pri spusteni programu. Uvnitr bloku externi deklarace
nerezervuji zadne pametove misto a inicializace neni proto
dovolena.
9.3 Podminkove prikazy
Podminkove prikazy existuji ve dvouch tvarech:
IF(vyraz) prikaz
IF(vyraz) ELSE prikaz.po 12
V obou pripadech je nejprve vyhodnocen vyraz, jestlize je ruzny
od nuly, je vykonan prvni prikaz. V pripade 2. tvaru podminko-
veho prikazu se vykona druhy prikaz, jestlize ma vyraz
hodnotu 0.
Obvykla dvojznacnost konstrukce s else pri vnorenych podminko-
vych prikazech je resena tim, ze cast else je spojena vzdy s
poslednim prikazem if, ke kteremu dosud zadna cast else nena-
lezi.
9.4 Prikaz WHILE
Prikaz WHILE ma tvar:
WHILE(vyraz) prikaz
prikaz je opakovane vyhodnocovan dokud je hodnota vyrazu nenu-
lova. Test teto hodnoty se uskutecni vzdy pred zapocetim vyko-
nani prikazu.
9.5 Prikaz DO
Prikaz DO ma tvar:
DO prikaz WHILE(vyraz);
Prikaz je opakovane vykonavan dokud hodnota vyrazu neni nulova.
Test se provadi vzdy po uskutecneni prikazu.
9.6 Prikaz FOR
Prikaz FOR ma tvar:
FOR(vyraz-1??? ; vyraz-2??? ; vyraz-3??? ) prikaz
Prikaz FOR je ekvivalentni nasledujicim prikazum:
vyraz-1;
WHILE(vyraz-2)
To znamena, prvni prikaz inicializuje smycku. Druhy vyraz
specifikuje test, ktery se vykona pred kazdym opakovanim a
zpusobi ukonceni smycky pokud je hodnota vyrazu rovna 0.
Treti vyraz nejcasteji specifikuje inkrementaci, ktera se
uskutecni po kazdem opakovani smycky.
Libovolny nebo vsechny z vyrazu mohou chybet. Chybejici
vyraz-2 zpusobi, ze je prikaz FOR ekvivalentni WHILE(1).
.pa.po 3
9.7 Prikaz SWITCH
Prikaz SWITCH zpusobi, ze rizeni programu bude preneseno
na jeden z nekolika prikazu v zavislosti na hodnote vyrazu.
Prikaz SWITCH ma nasledujici tvar:
SWITCH(vyraz) prikaz
Pri zpracovani vyrazu se uskutecni obvykle aritmeticke konver-
ze, ale vysledna hodnota musi byt typu int. Prikaz je vetsinou
slozenym prikazem. Libovolny prikaz uvnitr tohoto prikazu muze
byt oznacen nasledujici formou:
CASE konstantni_vyraz:
Konstantni vyraz musi byt typu int. V jedinem prikazu SWITCH
nesmeji mit konstantni vyrazy u ruznych prikazu CASE tutez hod-
notu. Konstantni vyrazy jsou definovany v odstavci c. 15.
Nejvice jeden prikaz uvnitr prikazu SWITCH muze byt oznacen
nasledujici formou:
DEFAULT
Pri vykonavani prikazu SWITCH je nejprve vyhodnocen vyraz a
jeho hodnota je porovnavana s kazdym konstantnim vyrazem, ktery
prislusi prikazu CASE. Jestlize je hodnota vyrazu rovna nekte-
remu z konstantnich vyrazu je rizeni programu preneseno na pri-
kazy, ktere nasleduji za prislusnym prikazem CASE. Jestize hod-
nota vyrazu neni rovna zadnemu konstantnimu vyrazu a nektery
vyraz je oznacen prikazem DEFAULT, potom je rizeni preneseno na
prikazy, ktere nasleduji za prikazem DEFAULT. Jestlize hodnota
vyrazu neni rovna zadnemu konstantnimu vyrazu ani zadny prikaz
neni oznacen DEFAULT, je rizeni preneseno za prikaz SWITCH.
Oznaceni CASE a DEFAULT samy o sobe nemeni rizeni programu,
ktery tato oznaceni ignoruje. Pro ukonceni jedne vetve prikazu
SWITCh je pak nutne pouzit prikaz BREAK, viz odstavec c. 9.8.
Ve vetsine pripadu je prikaz SWITCH prikazem slozenym. Na
zacatku takoveho prikazu se mohou vyskytovat deklarace, ale
inicializace automatickych i registrovanych promennych je
neucinna.
9.8. Prikaz BREAK
Prikaz BREAK ma tvar:
BREAK;
Tento prikaz zpusobi ukonceni nejvnitrnejsiho prikazu WHILE,
DO, FOR nebo SWITCH. Rizeni programu se prenese bezprostredne
za ukonceny prikaz.
.pa.po 12
9.9 Prikaz CONTINUE
Prikaz CONTINUE ma tvar:
CONTINUE;
Tento prikaz zpusobi preneseni rizeni na konec nejvnitrnejsi
smycky WHILE, DO, nebo FOR. Podrobneji vysvetleno, v kazdem z
nasledujicich prikazu:
WHILE() DO FOR()
} }
je prikaz CONTINUE ekvivalentni prikazu GOTO contin. Prikaz,
ktery nasleduje navesti contin je tzv. prazdny prikaz, viz
odstavec c. 9.13.
9.10 Prikaz RETURN
Prikaz RETURN vrati rizeni programu z vnitrku funkce do
mista odkud byla tato funkce vyvolana. Ma nasledujici formy:
RETURN;
RETURN vyraz;
V prvem tvaru je vracena hodnota nedefinovana. Ve druhem je
hodnota vyrazu vracena te casti programu, ktera danou funkci
vyvolala. Jestlize je to nutne, je hodnota vyrazu konverto-
vana, jako pri prikazu prirazeni na typ shodny s typem funkce.
Preneseni rizeni za konec funkce je ekvivalentni navratu bez
zadne vracene hodnoty.
9.11 Prikaz GOTO
Rizeni programu muze byt nepodminene preneseno pomoci prika-
zu GOTO, ktery ma nasledujici formu:
GOTO identifikator;
Identifikator musi byt navestim /viz odstavec c. 9.12/ umiste-
ne v prave vykonane funkci.
9.12 Prikaz navesti
Libovolny prikaz smi byt oznacen navestim, ktere ma nasledu-
jici formu:
identifikator:
.po 3
Tento prikaz zpusobi, ze dany identifikator je deklarovan jako
navesti. Hlavni pouziti prikazu navesti je pri pouziti prikazu
GOTO. Platnost daneho navesti je omezena na prave vykonavanou
funkci, vyjma takove bloky do ni vnorene, kde je tentyz iden-
tifikator opet deklarovan. Viz odstavec c. 11.
9.13 Prikaz NULL
Prikaz NULL ma nasledujici formu:
;
Prikaz NULL se pouziva s navestim bezprostredne pred koncem
slozeneho prikazu nebo jako prazdne telo prikazu smycky, jako
je napriklad prikaz WHILE.
10. Externi definice
Program v jazyku C obsahuje radu externich definic. Exter-
ni definice deklaruji identifikatory, ktere maji tridu ulozeni
EXTERN pripadne STATIC a dany typ. Specifikace typu muze byt
prazdna /viz odstavec c. 8.2/, v takovem pripade se predpoklada
typ int. Rozsah externich definici je po celem souboru, ve
kterem se takova definice vyskytne. Syntaxe externich definici
je ta sama jako je syntaxe normalnich deklaraci.
10.1 Definice externich funkci
Definice funkce ma nasledujici tvar:
definice_funkce:
specifikator_deklarace??? deklarator_funkce telo_funkce
Jedine specifikace ulozeni, ktere jsou povoleny v specifika-
toru_deklarace jsou EXTERN nebo STATIC, podrobneji viz odsta-
vec c. 11.2. Deklarator funkce je podobny deklaratoru typu
'function returning ' az na definici seznamu formalnich
parametru.
deklarator_funkce:
deklarator (seznam_parametru???)
seznam_parametru:
identifikator
identifikator, seznam_parametru
Telo funkce ma tvar:
telo_funkce:
seznam_deklaraci slozeny_vyraz
Pouze identifikatory ze seznamu_parametru mohou byt deklaro-
vany v seznamu_deklaraci. Kazdy identifikator, ktereho typ neni
urcen, ma typ int. Jedina trida ulozeni, ktera je dovolena ve.po 12
specifikaci, je trida REGISTER. Jestlize je tato trida speci-
fikovana, je odpovidajici skutecny parametr zkopirovan, pokud
je to mozne, primo do registru vypocetniho systemu vne funkce.
Uvedeme jednoduchy priklad kompletni definice funkce:
INT max (a, b, c,)
INT a, b, c;
Ve vyse uvedenem prikladu je INT specifikator typu funkce,
max(a,b,c) je deklarator_funkce, INT a,b,c je seznam_para-
metru a je blok, ktery tvori telo funkce.
Protoze jazyk C konvertuje vsechny skutecne parametry, kte-
ke maji typ FLOAT, na typ DOUBLE, jsou formalni parametry,
ktere maji typ FLOAT, schopne cist parametry typu DOUBLE.
Protoze vsechny odkazy na pole jsou chapany jako odkazy na
prvni prvek takoveho pole, deklarace formalniho paramet-
ru jako 'array of ' je takova deklarace chapana jako
deklarace parametr s typem 'pointer to '. Protoze struk-
tury, uniony a funkce nemohou byt funkcemi jako parametry
akceptovany, nemohou byt formalni parametry deklarovany s ty-
pem struktury, unionu nebo funkce. Samozrejme je dovoleno
deklarovat ukazovatka na takove typy.
10.2 Externi definice dat
Definice externich dat ma nasledujici formu:
definice_dat:
deklarace
Trida ulozeni externich dat muze byt EXTERN nebo STATIC, ale
nesmi byt AUTO nebo REGISTER. Pri neuvedeni tridy ulozeni se
predpoklada trida EXTERN.
.pl 60
.po 12
.mt 2
.mb 2
.pn 185
11. Pravidla rozsahu platnosti
Program v C jazyku nemusi byt kompilovan najednou v celku.
Zdrojovy text programu muze byt ulozen v nekolika souborech a
predem prelozene programy mohou byt pripojeny z knihoven. Ko-
munikace mezi funkcemi programu se muze uskutecnit pomoci funk-
cnich volani nebo pomoci externich dat.
Existuji dva pohledy na pojem rozsahu platnosti. Prvni
z nich urcuje lexikalni rozsah platnosti identifikatoru. Ktery
by mohl byt intuitivne chapan jako usek programu, ve kterem
muze byt tento identifikator pouzit, aniz dojde k chybe 'nede-
finovany identifikator'. Druhy pohled je spojen s pouzitim ex-
ternich identifikatoru, ktere jsou charakterizovany pravidlem,
ktere tvrdi, ze totozne externi identifikatory urcuji tytez
objekty.
11.1 Lexikalni rozsah platnosti
Lexikalni rozsah platnosti externich identifikatoru je od
mista jejich definice do konce souboru ve kterem jsou definova-
ny. Lexikalni rozsah formalnich parametru je funkce, ve ktere
jsou definovany. Lexikalni rozsah identifkatoru definovanych
na pocatku bloku je od mista jejich definovani do konce bloku.
Lexikalni rozsah navesti je cela funkce, ve ktere jsou defino-
vana.
Protoze vsechny odkazy na tentyz externi identifikator musi
urcovat tentyz objekt /viz odstavec c. 11.2/, prekladac kontro-
luje vsechny deklarace tehoz externiho identifkatoru na kompa-
tibilitu. Vysledkem je pak rozsah platnosti takoveho identifi-
katoru po celem souboru, ve ktere se jeho deklarace vyskytuji.
Dale plati nasledujici pravidlo. Jestlize je nejaky identi-
fikator deklarovan na pocatku bloku, vcetne bloku tvoriciho
definici funkce, jakakoliv deklarace tehoz identifikatoru vne
tohoto bloku je potlacena do konce daneho bloku.
Je nutne pripomenout /viz odstavec c. 8.5/, ze identifika-
tor prirazeny skutecne promenne a identifikator prirazeny
polozce struktury
nebo unionu ci znacce struktury nebo unionu patri do dvou ruz-
nych trid, ktere jsou bezkonfliktni. Pro polozky a znacky
struktur a unionu plati stejna pravidla rozsahu jako pro osta-
tni identifikatory. Mohou byt znovu deklarovany ve vnitrnim
bloku, ale musi byt pri nove deklaraci explicitne uveden typ.
TYPEDEF FLOAT distance;
Poznamenavame, ze funkce f musi byt deklarovana explicitne ve
vyvolavajici casti programu, protoze nazev funkce f neni pri
vyvolani nasledovan levou zavorkou.
14.3 Pole, ukazovatka a indexy
Kdykoliv se ve vyrazu objevi identifikator pole, je konver-
tovan na ukazovatko ukazujici na prvni polozku tohoto pole. Ta-
to konverze je nutna, protoze pole nejsou l-hodnotami. Podle
definice je operator indexu '[]' interpretovan takovym zpuso-
bem, ze vyraz E1[E2] je totozny s vyrazem *((E1) + (E2)).
Jestlize E1 je pole a E2 cele cislo, potom tyto vyrazy urcuji
E2 polozku pole E1. Indexovani je komutativni operace. .po 3
Podobna pravidla plati i pro vicerozmerna pole. Jestlize
E je n-rozmerne pole velikosti i x j x x k, potom je iden-
tifikator E vyskytujici se ve vyrazu konvertovan na ukazovatko
ukazujici na (n-1) -rozmerne pole o velikosti j x x k.
Jestlize je potom operator '&', at uz vyjadreny explicitne nebo
implicitne jako vysledek indexovani, pouzit s takovym ukazovat-
kem, vysledek bude ukazovat na (n-1) -rozmerne pole, a bude
okamzite konvertovan na ukazovatko. Uvedeme priklad:
INT x[3][5];
V tomto pripadu se jedna o pole velikosti 3x5 s polozkami typu
int. Pokud se identifikator x objevi ve vyrazu, je konvertovan
na ukazovatko, ktere ukazuje na prvni polozku /prvni ze tri/,
kterou je pole obsahujici 5 clenu typu int. Ve vyrazu x[i],
ktery je ekvivalentni vyrazu *(x+i), je identifikator x nejprve
konvertovan na ukazovatko, jak bylo jiz vyse popsano a potom je
index i konvertovan na typ objektu x, coz znamena ze i je naso-
beno delkou objektu, na ktery ukazuje vyse zminene ukazovatko,
tzn. na objekt obsahujici 5 objektu typu int. Vysledek je pri-
cten k ukazovatku. Timto zpusobem je neprimo urceno pole /o5-ti
celociselnych polozkach/, ktere je opet chapano jako ukazovat-
ko na prvni jeho polozku. Jestlize se tentyz argument pouzije
na dalsi index, vyse zminena pravidla budou pouzita znovu. Vy-
sledkem bude ale objekt typu int.
Nasledek vsech vyse uvedenych pravidel je ten, ze pole v jazy-
ku C jsou ukladana po radkach a posledni index se meni nejrych-
leji. Prvni index se podili na urceni velikosti pole, ale nepo-
dili se na indexovych vypoctech.
14.4 Explicitni konverze ukazovatek
Nektere konverze ukazovatek jsou sice dovoleny, ale jsou za-
visle na implementaci jazyka. Takove konverze jsou specifikova-
ny pomoci explicitniho operatoru typu konverze, viz. odstavec
c. 7.2 a 8.7.
Ukazovatko muze byt konvertovano na libovolny celociselny
typ, ktery ma vhodnou velikost. Je zavisle na technickych pro-
stredcich systemu, jestli bude vyzadovan typ int nebo long.
Mapovaci funkce je take zavisla na technickem vybaveni, ale ne-
mela by byt neobvykla pro uzivatele, kteri jsou obeznameni s
adresni strukturou vypocetniho systemu. Nektere podrobnosti pro
vyse jiz zminene vypocetni systemy budou uvedeny dale.
Objekty celociselnych typu mohou byt konvertovany na ukazo-
vatka. Mapovaci funkce by mela zajistit, aby cele cislo ziskane
konverzi ukazovatka zpetnou konverzi poskytlo totozne ukazovat-
ko. Jinak je ovsem zpusob mapovani zcela zavisly na technickych
prostredcich systemu.
Ukazovatko na objekt jednoho typu smi byt konvertovano na
ukazovatko na objekt jineho typu. Pri pouziti vysledneho ukazo-
vatka mouhou vzniknout chyby, pokud se nejedna o primerene
objekty. Musi byt zaruceno, ze ukazovatko na objekt dane veli-
kosti muze byt konvertovano na objekt v mensi velikosti a zpet
beze zmeny.
Napriklad, funkce pro pridelovani pameti prijima jeden argu-.po 12
ment, ktery urcuje velikost objektu, kteremu ma byt pridelena
pamet /ve slabikach/ a vraci ukazovatko na typ CHAR. Tato
funkce muze byt pouzita nasledujicim zpusobem:
EXTERN CHAR *ALLOC();
DOUBLE *DP;
DP = (DOUBLE*)ALLOC(SIZEOF(DOUBLE));
*DP = 22.0/7.0;
Funkce ALLOC musi zajistik technickymi prostredky systemu, ze
hodnota, kterou vraci, je vhodna pro konverzi na ukazovatko na
objekt typu DOUBLE, potom je pouziti takove funkce prenositel-
ne.
Reprezentace ukazovatek na systemu PDP-11 koresponduje 16-ti
bitovymi celymi cisly a zakladni jednotkou jsou slabiky /byte/.
Objekty typu CHAR mohou byt ukladany na libovolne adresy,
ostatni objekty musi byt ukladany na sude adresy.
Na systemu Honeywell 6000 ukazovatka koresponduji s 36-ti
bitovymi celymi cisly. Slovo je ulozeno v levych 18 bitech a
dalsi dva bity jsou urceny k vyberu znaku ze slova, ktere lezi
nalevo. Zakladni jednotka pro ukazovatka na objekty typu CHAR
ma rozsah 2**16 slabik, zakladni jednotka pro ukazovatka na
zbyvajici objekty ma rozsah 2**18 strojnich slov. Objekty typu
DOUBLE a agregaty, ktere takove objekty obsahuji, musi byt ulo-
zeny na sudych adresach pocitanych ve slovach /mod 2**19/.
Systemy IBM 370 a Interdata 8/32 jsou si podobne. Na obou
systemech jsou zakladni jednotkou slabiky. Zakladni objekty
musi byt ulozeny na adresy, ktere odpovidaji jejich delce.
Takze ukazovatka na objekty typu SHORT musi splnit podminku
0 mod 2, objekty typu INT a FLOAT 0 mod 4 a objekty typu DOUBLE
0 mod 8. Agregaty jsou umisteny tak, aby jejich umisteni vyho-
vovalo vsem jejich polozkam.
15. Konstantni vyrazy
V nekterych pripadech je pozadovano, aby vysledkem nejakeho
vyrazu byla konstanta. Napriklad po prikazu CASE, jako veli-
kost poli a pri inicializaci. V prvnich dvou pripadech takovy
vyraz muze obsahovat pouze celociselnou konstantu, znakovou
konstantu a operator SIZEOF, ktere mohou byt spojeny nasleduji-
cimi binarnimi operatory:
+ - * / % & << >> =x != < > <= >=
nebo unarnim operatorem: -
nebo ternarnim operatorem: ? :
Ve vyrazu mohou byt pouzity zavorky, ale nikoliv funkcni vola-
ni.
Vetsi moznosti jsou dovoleny pri inicializaci. Mimo konstan-
tnich vyrazu, tak jak je uvedeno vyse je mozne pouzit unarniho
operatoru '&' na objekty typu EXTERN nebo STATIC nebo na pole
typu EXTERN nebo STATIC s indexem, ktery je dan konstantnim vy-
razem. Unarni operator '&' muze byt take pouzit implicitne, tim.po 3
ze je uveden nazev pole bez indexu ci nazev funkce. Zakladni
pravidlo je nasledujici: vyhodnoceny vyraz musi byt bud kon-
stanta nebo adresa jiz drive deklarovane objektu typu EXTERN ci
STATIC, ke ktere muze byt prictena nebo odectena konstanta.
16. Uvahy o prenositelnosti
Nektere aspekty jazyka C jsou zavisle na technickych pro-
stredcich. Nasledujici vyklad nekterych potizi z toho vyply-
vajicich si necini narok na uplnost, ale naznaci nejdulezitejsi
z nich.
Ciste technicke problemy jako je velikost slova a zvlastnos-
ti aritmetiky plovouci carky a celociselneho deleni se ukazaly
v praxi nevelkymi. Ostatni vlastnosti technickeho vybaveni se
odrazeji v rozdilnych implementacich. Nektere z nich, zejmena
rozsireni znamenka /pri konverzi zaporneho znaku do zaporneho
cisla/ a poradi ve kterem jsou slabiky ukladany do slova, jsou
drobnosti, ktere musi byt peclive studovany. Mhohe dalsi pro-
blemy jiz nejsou zavazne.
Pocet registrovych promennych, ktere mohou byt skutecne
umisteny v registrech, je rozdilny na ruznych systemech, podob-
ne jako soubor platnych typu. Je veci prekladace, aby osetril
spravne takove situace. Prebyvajici nebo neplatne registrove
deklarace jsou jednoduse ignorovany.
Nektere problemy mohou vzniknout pri pouziti pochybnych pro-
gramovych praktik. Je velice nerozumne psat takove programy,
ktere zavisi na takovych vlastnostech.
Poradi vyhodnoceni argumentu funkci neni v definici jazyka
specifikovano. Na systemu PDP-11 je zprava a na ostatnich sys-
temech zleva doprava. Poradi, ve kterem muze dojit k vedlejsim
efektum, neni take specifikovano.
Protoze znakove konstanty jsou ve skutecnosti objekty typu
INT, jsou dovoleny i viceznakove konstanty. Skutecna implemen-
tace je zavisla na technickych prostredcich, protoze poradi
ve kterem jsou znaky prirazovany slovum se na ruznych systemech
lisi.
Bitova pole jsou prirazena slovum a znaky jsou prirazeny
celym cislum zprava doleva na systemu PDP-11 a zleva doprava
na ostatnich systemech. Tyto rozdily jsou neviditelne samotnym
programum, pokud si nedopravaji rafinovanosti s typy dat /nap-
riklad, kdyz uskutecni konverzi ukazovatka na objekt typu INT
na ukazovatko na objekt typu CHAR, ktere potom pouzije k
prohlizeni puvodniho objektu/, ale musi odpovidat vlastnostem
technickeho vybaveni.
Jazyk prijimany ruznymi prekladaci se lisi nepatrnymi detai-
ly. Napriklad prekladac implementovany na systemu PDP-11 nemuze
inicializovat struktury, ktere obsahuji bitova pole.
.pa.po 12
17. Anachronismy
Protoze jazyk C je jazyk, ktery je ve vyvoji, mohou byt ve
starsich programech nalezeny nektere neobvykle konstrukce.
Ackoliv nektere prekladace dovoluji tyto anachronismy pouzi-
vat, budou nakonec odstraneny a problem prenositelnosti bude
prekonan.
Drivejsi verze jazyka C pouzivaly formu =op namisto formy
op= pro operatory prirazeni. Tim byla zpusobena dvojznacnost
ve vyrazu:
x=-1
Tento vyraz opravdu bude dekrementovat promennou x, protoze
znaky '=' a '-' jsou v bezprostrednim sousedstvi, ale mohl by
byt velmi snadno zamenen za prikaz prirazeni hodnoty -1 prome-
nne x.
Byla take zmenena syntaxe inicializace, drive nebyl pouzi-
van pri inicializaci znak prirazeni '='. Tedy drivejsi zapis:
INT x 1;
je nyni nahrazen zapisem:
INT x = 1;
Obsah
Předmluva.1
Kapitola 0: Úvod2
Kapitola 1: Úvod k výuce.5
1.1 Začínáme.5
1.2 Proměnné a aritmetika7
1.3 Příkaz for.11
1.4 Symbolické konstanty12
1.5 Výběr uitečných programů12
1.6 Pole.18
1.7 Funkce..20
1.8 Argumenty - volaní hodnotou.21
1.9 Znaková pole..22
1.10 Externí proměnné25
1.11 Shrnutí27
Kapitola 2: Typy, operátory a výrazy29
2.1 Názvy proměnných.29
2.2 Typy dat a jejich délka29
2.3 Konstanty..30
2.4 Deklarace..32
2.5 Aritmetické operátory32
2.6 Relační operátory33
2.7 Konverze typu.34
2.8 Operátory přičtení a odečtení jedničky37
2.9 Bitové logické operátory..39
2.10 Operátory přiřazení a výrazy40
2.11 Podmíněné výrazy42
2.12 Priorita a pořadí vyhodnocovaní43
Kapitola 3: Větvení programu..45
3.1 Příkazy a bloky..45
3.2 If - Else..45
3.3 Else - if..46
3.4 Přepínač48
3.5 Cykly while a for49
3.6 Cykly do - while.52
3.7 Break53
3.8 Continue54
Příkaz goto a návěsti..55
Kapitola 4: Funkce a struktura programu57
4.1 Základy.57
4.2 Funkce,které nevracejí celá čísla..60
4.3 Více o argumentech funkcí.62
4.4 Externí proměnné.63
4.5 Pravidla pole působnosti..66
4.6 Statické proměnné70
4.7 Proměnné typu registr..71
4.8 Blokové struktury71
4.9 Inicializace..72
4.10 Rekurze74
4.11 Preprocesor jazyka C..75.po 3
Kapitola 5: Pointry a pole79
5.1 Pointry a adresy79
5.2 Pointry a argumenty funkcí..81
5.3 Pointry a pole..83
5.4 Adresová aritmetika85
5.5 Znakové pointry a funkce.88
5.6 Pointry nejsou celá čísla90
5.7 Vícerozměrná pole..91
5.8 Pole pointru. Pointry na pointry..93
5.9 Inicializace pole pointru95
5.10 Pointry a vícedimenzionální pole.96
5.11 Argumenty ve tvaru příkazové řádky..97
5.12 Pointry funkcí100
Kapitola 6: Struktury.103
6.1 Základy..103
6.2 Struktury a funkce105
6.3 Pole struktur..107
6.4 Pointry na struktury.110
6.5 Struktury odkazující samy na sebe111
6.6 Prohledávani tabulky.115
6.7 Pole bitu117
6.8 Uniony118
6.9 Příkaz typedef.120
Kapitola 7: Vstup a výstup..123
7.1 Přístup do standardní knihovny123
7.2 Stand. vstup a výstup - getchar a putchar..123
7.3 Formátový výstup - printf..125
7.4 Formátový vstup - scanf.126
7.5 Formátová konverze u paměti129
7.6 Přístup k souborům129
7.7 Oetřování chyb - stderr a exit..132
7.8 __ dkový vstup a výstup..133
7.9 Některý dalí funkce.. .134
Kapitola 8: Systémový souvislosti s oper.systémem UNIX137
8.1 Deskriptory souboru..137
8.2 Vstup a výstup nejnií úrovně READ a WRITE138
8.4 Náhodný přístup - SEEK a LSEEK140
8.5 Příklad - Implementace FOPEN a GETC.141
8.6 Příklad - Výpis adresáře145
8.7 Příklad - Přidělování paměti..148
Příloha A: Referenční popis jazyka C.153
1. Úvod.153
2. Lexikální konvence..153
2.1. Komentáře153
2.2. Identifikátory ( jména )153
2.3. Klíčová slova..154
2.4. Konstanty154
2.4.1 Celočíselné konstanty..154
2.4.2 Explicitní konstanty typu long..154
2.4.3 Znakové konstanty155
2.4.4 Konstanty v plovoucí čárce155
2.5 Řetězce155 2.6 Technické vybavení.156
3. Syntaktický zápis156
4. Co je to jméno ?.156
5. Objekty a l-hodnoty.157
6. Konverze158
6.1 Znaky a celá čísla.158
6.2 Plovoucí čárka a dvojnásobná přesnost158
6.3 Typy plovoucí čárky a typy celočíselné158
6.4 Ukazovátka a celá čísla..159
6.5 Čísla bez znamínka.159
6.6 Aritmetické konverze..159
7. Výrazy..160
7.1 Základní výrazy.160
7.2 Unární operátory162
7.3 Multiplikativní operátory163
7.4 Aditivní operátory.164
7.6 Relační operátory..165
7.7 Operátory porovnání165
7.8 Bitový operátor AND165
7.9 Bitový operátor XOR166
7.10 Bitový operátor OR166
7.11 Logický operátor AND.166
7.12 Logický operátor OR..166
7.13 Operátor podmínky.167
7.14 Operátory přirazení..167
7.15 Operátor __ rky.168
8. Deklarace..168
8.1 Specifikace třídy uloení168
8.2 Specifikace typu169
8.3 Deklarátory..170
8.4 Význam deklarátoru.170
8.5 Deklarace struktur a union_.172
8.6 Inicializace.174
8.7 Názvy typu176
8.8 TYPEDEF177
9. Příkazy.178
9.1 Výrazové příkazy178
9.2 Sloené příkazy nebo bloky..178
9.3 Podmínkové příkazy.178
9.4 Příkaz WHILE.179
9.5 Příkaz DO.179
9.6 Příkaz FOR179
9.7 Příkaz SWITCH180
9.8 Příkaz BREAK.180
9.9 Příkaz CONTINUE.181
9.10 Příkaz RETURN..181
9.11 Příkaz GOTO.181
9.12 Příkaz n_ v__¨t_t.181
9.13 Příkaz NULL.182
10. Externí definice182
10.1 Definice externích funkcí..182
10.2 Externí definice dat.183
11. Pravidla rozsahu platnosti..185
11.1 Lexikální rozsah platnosti.185
11.2 Rozsah platnosti externích proměnných..186
12. __td_tc_t _Š_ dky překladače..186 12.1 Z_ m_na syntaktické jednotky186
12.2 Vkládání souboru..187
12.3 Podmíněná kompilace..187
12.4 __tzení _Š_ dkov_ n_t..188
13. Implicitní deklarace..188
14. Dalí informace o typech.188
14.1 Struktury a uniony188
14.2 Funkce189
14.3 Pole, ukazovátka a indexy..189
14.4 Explicitní konverze ukazovátek190
15. Konstantní výrazy..191
16. Úvahy o přenositelnosti..192
17. Anachronizmy.193
Obsah195
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 1242
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved