*********************************************************
* *
* *
* CCCC *
* C C *
* C *
* C *
* C C *
* CCCC *
* *
* *
*********************************************************
* *
* UCEBNICE PROGRAMOVACIHO JAZYKA *
* *
* Brian W. Kernighan Denis M. Ritchie *
* *
*********************************************************
Obsah
-----
Predmluva.................................................1
Kapitola 0: Uvod..........................................2
Kapitola 1: Uvod k vyuce..................................5
1.1 Zaciname..................................5
1.2 Promenne a aritmetika.....................7
1.3 Prkaz for ...............................11
1.4 Symbolicke konstanty.....................12
1.5 Vyber uzitecnych programu................12
1.6 Pole.....................................18
1.7 Funkce...................................20
1.8 Argumenty-volani hodnotou................21
1.9 Znakove pole.............................22
1.10 Externi promenne........................25
1.11 Shrnuti.................................27
Kapitola 2: Typy, operatory a vyrazy.....................29
2.1 Nazvy promennych.........................29
2.2 Typy dat a jejich delka..................29
2.3 Konstanty................................30
2.4 Deklarace................................32
2.5 Arimeticke operatory.....................32
2.6 Relacni operatory........................33
2.7 Konverze typu............................34
2.8 Operatory picteni a odecteni jednicky ...37
2.9 Bitove logicke operatory.................39
2.10 Operatory prirazeni a vyrazy............40
2.11 Podminene vyrazy........................42
2.12 Priorita a poradi vyhodnocovani.........43
Kapitola 3: Vetveni programu.............................45
3.1 Prikazy a bloky..........................45
3.2 If - else................................45
3.3 Else - if................................46
3.4 Prepinac.................................48
3.5 Cykly while a for........................49
3.6 Cykly do - while.........................52
3.7 Break....................................53
3.8 Continue.................................54
3.9 Prikaz goto a navesti....................55
Kapitola 4: Funkce a struktura programu..................57
4.1 Zaklady..................................57
4.2 Funkce,ktere nevraceji cele cisla........60
4.3 Vice o argumentech funkci................62
4.4 Externi promenne.........................63
4.5 Pravidla pole pusobnosti.................66
4.6 Staticke promenne........................70
4.7 Promenne typu registr....................71
4.8 Blokove struktury........................71
4.9 Inicializace.............................72
4.10 Rekurze.................................74
4.11 Preprocesor jazyka C....................75
Kapitola 5: Pointry a pole..............................79
5.1 Pointry a adresy........................79
5.2 Pointry a argumenty funkci..............81
5.3 Pointry a pole..........................83
5.4 Adresova aritmetika.....................85
5.5 Znakove pointry a funkce................88
5.6 Pointry nejsou cele cisla...............90
5.7 Vicerozmerna pole.......................91
5.8 Pole pointru. Pointry na pointry........93
5.9 Inicializace pole pointru...............95
5.10 Pointry a vicedimenzionalni pole.......96
5.11 Argumenty ve tvaru prikazove radky.....97
5.12 Pointry funkci........................100
Kapitola 6: Struktury..................................103
6.1 Zaklady................................103
6.2 Struktury a funkce.....................105
6.3 Pole struktur..........................107
6.4 Pointry na struktury...................110
6.5 Struktury odkazujici samy na sebe......111
6.6 Prohledavani tabulky...................115
6.7 Pole bitu..............................117
6.8 Uniony.................................118
6.9 Prikaz typedef.........................120
Kapitola 7: Vstup a vystup.............................123
7.1 Pristup do standardni knihovny.........123
7.2 Stand. vstup a vystup-getchar,putchar..123
7.3 Formatovy vystup - printf..............125
7.4 Formatovy vstup - scanf................126
7.5 Formatova konverze u pamati............129
7.6 Pristup k souborum.....................129
7.7 Osetrovani chyb - stderr a exit........132
7.8 Radkovy vstup a vystup.................133
7.9 Nektere dalsi funkce...................134
Kapitola 8: Systemove souvislosti s oper.systemem UNIX.137
8.1 Deskriptory souboru....................137
8.2 Vstup a vystup na nejnissi urovni......138
8.4 Nahodny pristup - SEEK a LSEEK.........140
8.5 Priklad - implementace FOPEN a GETC....141
8.6 Priklad - vypis adresace...............145
8.7 Priklad - pridelovani pamati...........148
Priloha A: Referencni popis jazyka C...................153
1. Uvod.....................................153
2. Lexikalni konvence.......................153
2.1. Komentare..............................153
2.2. Identifikatory ( jmena )...............153
2.3. Klicova slova..........................154
2.4. Konstanty..............................154
2.4.1 Celociselne konstanty.................154
2.4.2 Explicitni konstanty typu long........154
2.4.3 Znakove konstanty.....................155
2.4.4 Konstanty v plovouci carce............155
2.5 Retezce.................................155
2.6 Technicke vybaveni......................156
3. Syntakticky zapis........................156
4. Co je jmeno ?............................156
5. Objekty a l-hodnoty......................157
6. Konverze.................................158
6.1 Znaky a cele cisla......................158
6.2 Plovouci carka a dvojnasobna presnost...158
6.3 Typy plovouci carky a typy celociselne..158
6.4 Ukazovatka a cela cisla.................159
6.5 Cisla bez znamenka......................159
6.6 Aritmeticka konverze....................159
7. Vyrazy...................................160
7.1 Zakladni vyrazy.........................160
7.2 Unarni operatory........................162
7.3 Multiplikativni operatory...............163
7.4 Aditivni operatory......................164
7.6 Relacni operatory.......................165
7.7 Operatory porovnani.....................165
7.8 Bitovy operator AND.....................165
7.9 Bitovy operator XOR.....................166
7.10 Bitovy operator OR.....................166
7.11 Logicky operator AND...................166
7.12 Logicky operator OR....................166
7.13 Operator podminky......................167
7.14 Operatory prirazeni....................167
7.15 Operator carky.........................168
8. Deklarace................................168
8.1 Specifikace tridy ulozeni...............168
8.2 Specifikace typu........................169
8.3 Deklaratory.............................170
8.4 Vyznam deklaratoru......................170
8.5 Deklarace struktur a unionu.............172
8.6 Inicializace............................174
8.7 Nazvy typu..............................176
8.8 TYPEDEF.................................177
9. Prikazy..................................178
9.1 Vyrazove prikazy........................178
9.2 Slozene prikazy nebo bloky..............178
9.3 Podminkove prikazy......................178
9.4 Prikaz WHILE............................179
9.5 Prikaz DO...............................179
9.6 Prikaz FOR..............................179
9.7 Prikaz SWITCH...........................180
9.8 Prikaz BREAK............................180
9.9 Prikaz CONTINUE.........................181
9.10 Prikaz RETURN..........................181
9.11 Prikaz GOTO............................181
9.12 Prikaz navesti.........................181
9.13 Prikaz NULL............................182
10. Externi definice........................182
10.1 Definice externych funkci..............182
10.2 Externi definice dat...................183
11. Pravidla rozsahu platnosti..............185
11.1 Lexikalni rozsah platnosti.............185
11.2 Rozsah platnosti externych promennych..186
12. Ridici radky prekladace.................186
12.1 Zamena syntakticke jednotky............186
12.2 Vkladani souboru.......................187
12.3 Podminena kompilace....................187
12.4 Rizeni radkovani.......................188
13. Implicitni deklarace....................188
14. Dalsi informace o typech................188
14.1 Struktury a uniony.....................188
14.2 Funkce.................................189
14.3 Pole, ukazovatka a indexy..............189
14.4 Explicitni konverze ukazovatek.........190
15. Konstantni vyrazy.......................191
16. Uvahy o prenositelnosti.................192
17. Anachronizmy............................193
Obsah...................................................195
PREDMLUVA
---------
C je univerzalni programovaci jazyk, ktery charakterizuji
usporne vyrazy, moderni rizeni behu a struktura udaju, bohatstvi
operatoru. C neni jazyk "na vysoke urovni" ani neni "velky" a
neni specializovan na pouziti pouze v urcite oblasti.
Tato obecnost jazyka C jej cini vhodnejsim a efektivnejsim pro
mnoho uloh nez jine "mocnejsi" jazyky.
Autorem jazyka C je Denis Ritchie, ktery puvodne koncipoval
tento jazyk pro operacni system UNIX na pocitaci DEC PDP-11.
Operacni system, prekladac jazyka C a vlastne vsechny aplikacni
programy systemu UNIX /i veskere programove vybaveni pripravene
pro tutu knihu/, jsou napsany v jazyku C. Ucinne prekladace
existuji pro dalsi pocitace, napr. IBM System 1370, Honeywell
6000 a Interdata 8/32. Jazyk C neni svazan s urcitym konstruk-
cnim resenim pocitacu.
Tato kniha je koncipovana tak, aby pomohla naucit se ctenari
programovat v jazyku C. Obsahuje ucebni uvod, umoznujici 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: UVOD
-----------------
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-
temu UNIX je psan v jazyku C. Prevazna vetsina uzivatelu syste-
mu UNIX /vcetne autoru teto knihy/ nezna assembler pocitace
PDP - 11.
Prestoze jazyk C pracuje na mnoha pocitacich, je nezavisly
na architekture daneho pocitace, a tak je mozne s trochou pece
psat "prenositelne" programy. V nasem oddeleni je software,
ktery je vyvinut pod systemem UNIX, prenasen na pocitace
HONEYWELL, IBM a Interdata system. Ve skutecnosti jsou prekla-
dace a prostredky jazyka C na techto ctyrech 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
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: UVOD K VYUCE
-------------------------
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.
1.1 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 ()
{
printf ("hello, world\n");
}
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"
/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, world\n");
je volanim funkce, ktera se jmenuje printf a jedinym argumentem
je retezec znaku "hello, world\n".
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-
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()
{
printf ("hello,");
printf (" world");
printf ("\n");
}
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.
1.2. Promenne 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 ();
{
float fahr, celsius;
lower = 0; /* dolni mez tabulky teplot */
upper = 300; /* horni mez */
step = 20; /* hodnota kroku */
fahr = lower;
while(fahr <= upper)
{
celsius = (5.0/9.0) * (fahr - 32.0);
printf ("%4.0f %6.1f\n",fahr,celsius);
fahr = fahr + step;
}
}
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 znamena, ze vsechny uvedene promenne budou celoci-
selne promenne ; f l o a t znamena, ze se jedna o promenne s
pohyblivou radovou carkou. Presnost a rozsah obou typu je za-
visly na pouzitem typu pocitace. Napr. na pocitaci PDP-11 je
int 16-ti bitove cislo se znamenkem a lezi v rozsahu -32768 a
+32767. Cislo typu f l o a t je 32 bitove, ma 7 vyznamovych
cislic a lezi v rozsahu 10 E-38 do 10 E+38. V kapitole 2 je
uveden rozsah pro ostatni vybrane typy pocitacu.
Dalsi z typu promennych v jazyce C jsou napr.:
char znak - jeden byte
short kratke cele cislo
long dlouhe cele cislo
double cislo s pohyblivou carkou a dvo-
jitou presnosti
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 ;
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 { a }/ 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.
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.1f\n" , 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
Celsia v zavislosti na stupnich Fahrenheita.
1.3 Prikaz for
--------------
Jak je mozno ocekavat, je mnoho zpusobu, jak napsat program;
uvedeme jinou variantu naseho programu pro konverzi teplot
main () /* tabulka prevodu Fahrenheit-Celsius/
{
int fahr
for (fahr = 0; fahr <= 300; fahr = fahr + 20)
printf("%4d %6.1f\n", fahr, (5.0/9.0) * (fahr - 32));
}
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.
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.
1.4 Symbolicke 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/
{
int fahr
for (fahr=LOWER; fahr <= UPPER; fahr=fahr + STEP)
printf("%4d %6.1f\n", fahr, (5.0/9.0) * (fahr-32));
}
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.
1.5 Vyber uzitecnych programu
-----------------------------
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.
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*/
{
int c;
c = getchar();
while (c != EOF)
{
putchar (c);
c = getchar();
}
}
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.
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 */
{
int c;
while((c = getchar()) != EOF)
putchar (c);
}
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*/
{
long nc;
nc = 0;
while (getchar(), = EOF)
++nc;
printf("%1d\n",nc);
}
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*/
{
double nc;
for(nc = 0; getchar() != EOF; ++nc)
;
printf("%.0f\n", nc);
}
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*/
{
int c, nl;
nl = 0;
while((c = getchar()) != EOF)
if(c == '\n')
++nl;
printf("%d\n", nl);
}
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*/
{
int c, nl, nw, nc, inword;
inword = NO;
nl = nw = nc = 0;
while((c=getchar()) != EOF)
{
++nc;
if(c == '\n')
++nl;
if(c == ' ' || c == '\n' || c == '\t')
inword = NO;
else if(inword == NO)
{
inword = YES;
++nw;
}
}
printf( "%d %d %d\n", 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-
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.
1.6 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*/
{
int c, i, nwhite, nother;
int ndigit[10];
nwhite = nother = 0;
for(i = 0; i<10; ++i)
ndigit[i] = 0;
while((c = getchar()) != EOF)
if (c >= '0' && c <= '9')
++ndigit [c-'0'];
else if(c == ' ' || c == '\n' || c == '\t')
++nwhite;
else
++nother;
printf ("digits =");
for (i = 0; i<10; ++i)
printf (" %d", ndigit[i]);
printf ( "\nwhite space = %d, other = %d\n",
nwhite, nother);
}
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-
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,
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.
1.7 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*/
{
int i;
for (i = 0; i = 10; ++i)
printf("%d %d %d\n",
i, power(2,i), power(-3,i));
}
power(x, n) /*funkce power*\
int x, n;
{
int i, p;
p = 1;
for(i = 1; i <= n; ++i)
p = p * x;
return (p);
}
Vsechny funkce maji stejnou strukturu:
jmeno (seznam argumentu, pokud jsou nejake)
deklarace argumentu, pokud jsou nejake;
{
deklarace
prikazy
}
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 %d\n", 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.
1.8 Argumenty - volani 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
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;
{
int p;
for (p = 1; n>0; --n)
p = p * x;
return (p);
}
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.
1.9 Znakova 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.
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*/
{
int len; /*delka bezne radky*/
int max; /*maximalni dosazena velikost*/
char line[MAXLINE]; /*bezna radka*/
char save[MAXLINE]; /*nejdelsi radka uchovana*/
max = 0;
while ((len = getline (line, MAXLINE)) > 0)
if (len > max)
{
max = len;
copy(line, save);
}
if (max>0) /*byla nactena radka/*
printf("%s", save);
}
getline (s, lim) /*nacti radku do s, vrat delku/*
char s [];
int lim;
{
int c, i;
for(i=0; i 0)
if (len > max)
{
max = len;
copy();
}
if (max> 0) /*byla nactena veta*/
printf("%s", save);
}
getline() /*zvlastni verze*/
{
int c, i;
extern char line[];
for(i = 0; i < MAXLINE-1
&& (c=getchar()) != EOF && c != '/n'; ++i)
line[i] = c;
if (c == '/n')
{
line [i] = c;
++i;
}
line[i] = '/0';
return (i);
}
copy() /*zvlastni verze*/
{
int i;
extern char line[], save[];
i = 0;
while ((save[i] = line[i]) != '\0')
++i;
}
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-
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 Shrnuti
-------------
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, OPERATORY A VYRAZY
------------------------------------
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 Nazvy promennych
--------------------
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 delka
---------------------------
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
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-
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 [];
{
int i;
i = 0;
while (s[i] != '\0')
++i;
return (i);
}
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. Aritmeticke operatory
---------------------------
Binarni aritmeticke operatory jsou +, -, *, / a operator
modulo %. Existuje unarni - ale neexistuje unarni +.
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. Relacni operatory
----------------------
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
a 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[];
{
int i, n;
n = 0;
for(i=0; s[i] >= '0' && s[i] <= '9'; ++i)
n = 10 * n + s[i] - '0';
return(n);
}
Jak bylo ukazano v kapitole 1, vyraz:
s[i] - '0'
je numerickou hodnotou znaku ulozeneho v s[i], protoze hodnoty
znaku '0', '1',... tvori vzestupnou, neprerusenou radu kladnych
cisel.
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;
{
if(c >= 'A' && c <= 'Z')
return(c + 'a' - 'A');
else
return(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-
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.
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. Operatory pricteni a odecteni jednicky
-------------------------------------------
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.
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;
{
int i, j,
for(i = j = 0; s[i] != '\0'; i++)
if (s[i] != c)
s[j++]=s[i];
s[j] = '\0';
}
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)
{
s[j]=s[i];
j++;
}
Jinym prikladem je cast funkce getline, kterou jsme vytvo-
rili v kapitole 1, kde muzeme prikazy
if (c == '\n')
{
s[i] = c;
++i;
}
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*/
{
int i, j;
i = j = 0;
while(s[i] != '\0') /*nalezeni konce s*/
i++;
while((s[i++]=t[j++]) != '\0') /*kopiruj t*/
;
}
Kdyz jsou znaky kopirovany z t do s, tak operater ++ za pro-
mennymi i, j pripravuje bezprostredne kopirovani dalsiho znaku.
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 Bitove logicke operatory
----------------------------
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
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
{
return((x >> (p+1-n)) & `(`0 << n));
}
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<> & ^ |
Jestlize (e1) a (e2) jsou vyrazy, potom
e1 op= e2
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;
{
int b;
for(b = 0; n != 0; n >>= 1)
if (n & 01)
b++;
return(b);
}
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. Podminene vyrazy
----------------------
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 . 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
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("%d\n", 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: VETVENI 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 Prikazy 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 { a } 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.
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)
{
if(a > b)
z = a;
}
else
z = b;
Dvojznacnost je zvlaste nebezpecna v situacich jako:
if(n > 0)
for(i = 0; i < n; i++)
if(s[i] > 0
{
printf("...");
return(i);
}
else /*chyba*/
printf("error - n is zero\n");
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
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;
{
int low, high, mid;
low = 0;
high = n - 1;
while(low <= high)
{
mid = (low+high) / 2;
if(x < v[mid])
high = mid - 1;
else if(x > v[mid])
low = mid + 1;
else /*found match*/
return(mid);
}
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 Prepinac
------------
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*/
{
int c, i, nwhite, nother, ndigit [10];
nwhite = nother = 0;
for(i = 0; i < 10; i++)
ndigit [i] = 0;
while((c = getchar ()) != EOF)
switch(c)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
ndigit[c-'0']++;
break;
case ' ':
case '\n':
case '\t':
nwhite++;
break;
default:
nother++;
break;
}
printf("digits = ");
for(i = 0; i<10; i++)
printf(" %d ", ndigit[i]);
printf("\nwhite space = %d, other = %d\n",
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
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 C y k l y w h i l e a f o r
-----------------------------------
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 )
{
p r i k a z
v y r a z 3
}
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-
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.
atoi(s) /*konvertovani s na cislo*/
char s[];
{
int i, n; sign;
for(i=0;s[i] == ' ' || s[i] == '\n'|| s[i] =='\t';i++)
; /*preskoceni oddelovacu*/
sign = 1;
if(s[i] == '+' || s[i] == '-') /*znak*/
sign = (s[i++] == '+') ? 1 : -1;
for(n=0; s[i] >= '0' && s[i] <= '9'; i++)
n = 10 * n + s[i] - '0';
return(sign * n);
}
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;
{
int gap, i, j, temp;
for(gap = n/2; gap > 0; gap /= 2)
for(i = gap; i < n; i++)
for(j=i-gap; j>=0 && v[j] >
v[j+gap]; j-=gap)
{
temp = v[j];
v[j] = v[j + gap];
v[j + gap] = temp;
}
}
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.
reverse(s) /*invertovan retezec s*/
char s[];
{
int c, i, j;
for(i = 0, j = strlen(s) - 1; s < j; i++, j--)
{
c = s[i];
s[i] = s[j];
s[j] = c;
}
}
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
abc...xyz 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;
{
int i, sign;
if((sign = n) < 0) /*zjisteni znamenka*/
n = -n; /*n bude kladne*/
i = 0;,
do /*generovani cislic v opacnem poradi*/
{
s[i++] = n % 10 + '0';/*dalsi cislice*/
}
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*/
{
int n;
char line[MAXLINE];
while((n = getline(line, MAXLINE)) > 0)
{
while(--n >= 0)
if(line [n] != ' ' && line
[n] != '\t')
break;
line[n + 1] = '\0';
printf("%s\n", line);
}
}
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)
{
while(--n > = 0
&& (line [n] == ' ' || line[n] == '\t' ||
line[n] == '\n'))
;
...
}
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++)
{
if(a[i] < 0) /*preskoc zaporne prvky*/
continue;
... /*kladne prvky*/
}
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./
3.9 Prikaz goto a navesti
-------------------------
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(...)
{
...
if(katastrofa)
goto error;
}
...
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*/
...
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. Zaklady
------------
Pro zacatek napiseme program, ktery tiskne kazdou radku
ze vstupu, ktera ma jistou vlastnost nebo obsahuje urcity
retezec znaku. /To je specialnim pripadem obsluzneho programu
grep v systemu UNIX./ Napr. hledejme radky, ktere obsahuji
retezec "the" v souboru radku
Now is the time
for all good
men to come to the aid
of their party.
vysledkem bude:
Now is the time
men to come to the aid
of their party.
zakladni strukturu muzeme popsat ve trech castech
while (je jeste dalsi radka)
if (radka obsahuje dany retezec)
vytiskni ji
Prestoze by bylo jiste mozne napsat tento program jako celek,
lepsi cestou je vyuzit prirozene struktury a pouzit pro
kazdou cast oddelenou funkci. Se tremi mensimi useky se
lepe zachazi nez s jednim velkym celkem, protoze nepodstat-
ne detaily mohou byt do nich "utopeny" a mohou byt minimali-
zovany nezadouci interakce. Jednotlive useky take mohou
byt uzitecne pro svou cinnost.
"Je jeste dalsi radka" je funkce getline, ktera je po-
psana v kap. 1 a "vytiskni ji" je funkce printf, kterou
jiz pro nas nekdo napsal. To znamena, ze musime pouze
napsat funkci, ktera rozhoduje, zda radka obsahuje urcity
retezec znaku. Tento problem muzeme vyresit tim, ze ukradne-
me ideu z jazyka PL/1; funkce index (s,t) vraci pozici
v retezci s, kde zacina retezec t, nebo -1, kdyz t neni
obsazeno v s. Pro zacatek v retezci s pouzijeme spise
0 nez 1, protoze pole v jazyku C zacinaji rovnez nulou.
Kdyz pozdeji budeme potrebovat dokonalejsi vyhledavani,
staci pouze zmenit funkci index; zbytek programu zustane
nezmenen.
Rozhodneme-li se pro tento postup, lze program primo
napsat. V programu je zretelne videt, jak spolu jeho casti
souvisi. Pro tentokrat bude retezec, ktery hledame, znakovou
konstantou v argumentu funkce index, coz neni uplne obecny
zpusob. Kratce se vratime k tomu, jak inicializovat znakova
pole a v kap. 5 ukazeme, jak muzeme nastavit pozadovany
retezec pro vyber, kdyz je program vyvolan. Popsana je rov-
nez nova verze funkce getline. Porovnejte ji s funkci
z kap. 1.
# define MAXLINE 1000
main() /*najdi vsechny radky obsahujici retezec */
{
char line [MAXLINE];
while (getline(line, MAXLINE) > 0)
if (index(line, "the") >= 0)
printf ("%s",line);
}
getline(s,lim) /*nacti radku do s, vrat delku*/
char s[];
int lim;
{
int c, i;
i=0;
while(--lim > 0 && (c=getchar()) != EOF && c
!= '\n')
s[i++]=c;
if (c == '\n')
s[i++]=c;
s[i]='\0';
return(i);
}
index(s,t) /* vrat index vyskytu t v s */
char s[], t [];
{
int i,j,k;
for (i=0; s[i] != '\0'; i++)
{
for(j=i, k=0; t[k] != '\0' && s[j] == t[k];
j++,k++)
;
if(t[k] == '\0')
return(i);
}
return(-1);
}
Kazda funkce ma strukturu
nazev (seznam argumentu, jsou-li nejake)
deklarace argumentu, jsou-li nejake
{
deklarace a prikazy, jsou-li nejake
}
Jak je naznaceno, ruzne casti mohou byt vynechany; mini-
malni funkce vypada takto:
dummy () {}
a nedela nic. /Funkce, ktera nedela nic je uzitecna proto,
ze "drzi misto" nove funkci pri vyvoji programu./ Pred
jmenem funkce muze byt rovnez uveden typ, kdyz funkce vraci
neco jineho nez celociselnou hodnotu; o tom budeme
hovorit v teto sekci.
Program je jen soubor definic jednotlivych funkci.
Komunikace mezi funkcemi je /v tomto pripade/ pomoci argu-
mentu a hodnot jimi vracenymi; muze byt rovnez provadena
externimi promennymi. Funkce se ve zdrojovem souboru mohou
objevit v libovolnem poradi. Zdrojovy program muze byt roz-
delen do mnoha souboru, pokud neni nektera funkce roztrzena.
Prikazem return vraci volana funkce hodnotu jednotce
volajici. Za prikazem return se muze objevit libovolny vyraz:
return (vyraz)
Volajici jednotka muze dle libosti hodnotu ignorovat.
Navic po prikazu return nemusi nasledovat zadna hodnota:
v tomto pripade neni zadna hodnota vracena. Rizeni se
rovnez volajici jednotce vraci tehdy, dospela-li volana
funkce "na konec", tj. k prave uzavirajici slozene zavorce.
Obecne neni zakazano, aby funkce z jednoho mista hodnotu
vracela a z jineho ne, ale muze to zpusobit potize.
V pripade, ze funkce zadnou hodnotu nevraci, je "hodnota"
funkce nahodna, nedefinovana, program LINT jazyka C lokali-
zuje takoveto chyby.
Zpusob, jak prekladat a sestavovat program v jazyku C,
ktery je v nekolika zdrojovych souborech, se lisi system
od systemu. Napr. v systemu UNIX je provadeno prikazem
cc, jak jsme ukazali v kap. 1. Predpokladejme, ze tri funkce
jsou ve trech souborech nazvanych main.c, getline.c,
index.c. Potom prikaz
cc main.c getline.c index.c
prelozi tyto tri soubory a ulozi premistitelny kod do souboru
main.o, getline.o, index.o a sestavi je vsechny do pro-
veditelneho programu nazvaneho a.out.
Jestlize se napr. v main.c vyskytne chyba, tak muze
byt pozdeji prelozen sam a vysledek sestav spolu s drive
prelozenymi soubory
cc main.c getline.o index.o
Prikaz cc uziva ".c" a ".o" k odliseni zdrojovych a premisti-
telnych souboru.
C v i c e n i 4 - 1. Napiste funkci rindex (s,t), ktera vra-
ci pozici posledniho vyskytu retezce t v s, nebo -1, kdyz
s neobsahuje t.
4.2. Funkce, ktere nevraceji cela cisla
---------------------------------------
Doposud v zadnem nasem programu nebyla pouzita deklarace
funkce. To proto, ze kazda funkce je implicitne deklarovana
podle toho, v jakem vyrazu nebo prikazu se vyskytuje.
jako napr.
while (getline(line, MAXLINE)>0)
Jestlize se jmeno, ktere nebylo dosud deklarovano, objevi
ve vyrazu a bezprostredne za nim nasleduje leva zavorka,
tak je podle kontextu definovano jako jmeno funkce. Navic
implicitne se predpoklada, ze funkce vraci hodnotu int.
Protoze char je ve vyrazech brano jako int, tak neni potreba
deklarovat funkci jako char. Tyto predpoklady pokryvaji
vetsinu pripadu a zahrnuji rovnez nase priklady.
Co se ale stane, ma-li funkce vracet hodnoty jineho
typu? Mnoho numerickych funkci jako jsou sqrt, sin a cos
vraci hodnotu typu double; jine specialni funkce vraceji
jine typy. Abychom si to ilustrovali napisme funkci atof(s),
ktera konvertuje retezec s na ekvivalentni cislo typu
double. Funkce atof je rozsirenim funkce atoi, kterou jsme
v kapitolach 2 a 3 vytvorili v nekolika verzich. Pracuje
se znamenkem a desetinnou teckou a cela a desetinne casti
mohou nebo nemusi byt uvedeny. /Neni to konverzni program
vysoke kvality./
Za prve: funkce atof musi sama deklarovat typ, ktery
vraci, protoze to neni typ int. Protoze float je konvertovano
ve vyrazech na double, neni namitek proti tomu, ze vraci
float; stejne ale muzeme vyuzit zvysene presnosti a proto
deklarovat funkci jako double. Prikaz typu je uveden pred
jmenem funkce, jako
double atof(s) /*konvertovan retezec s na double */
char s[];
{
double val, power;
int i, sign;
for (i=0; s[i] == ' ' || s[i] == '\n' || s[i]
== '\t'; i++)
; /*preskoc oddelovac*/
sign = 1;
if (s[i] == '+' || s[i] == '-') /*znak*/
sign = (s[i++] == '+') ? 1 : -1;
for (val = 0; s[i] >= '0' && s[i] <= '9'; i++)
val = 10 * val + s[i] - '0';
if (s[i] == '.')
i++;
for (power = 1; s[i] >= '0' && s[i] <= '9';i++)
{
val = 10 * val s[i] - '0';
power *= 10;
}
return (sign * val / power);
}
Za druhe: ve volajici jednotce je nutno urcit, ze atof
vraci necelociselnou hodnotu. Tato deklarace je ukazana
v nasledujicim programu simulujicim primitivni stolni
kalkulator, ktery cte jedno cislo z radky a secita je
a tiskne predbezne soucet pred kazdym vstupem.
#define MAXLINE 100
main() /*primitivni stolni kalkulator*/
{
double sum atof ();
char line[MAXLINE];
sum = 0;
while (getline(line,MAXLINE)>0)
printf ("\t%.2f\n", sum += atof(line));
}
Deklarace
double sum, atof();
rika, ze sum je promenna s dvojnasobnou delkou a atof je
funkce, ktera vraci hodnotu double. Jako mnemoniku doporu-
cujeme, aby sum a atof byly oboje typu double.
Jestlize je atof explicitne deklarovana na obou mistech,
prekladac jazyka C predpoklada, ze vraci hodnotu integer,
a vysledek bude nesmyslny. Jestlize funkce atof a program
main jsou ve stejnem souboru a typ funkce same a volani
v jednotce main sobe neodpovidaji, tak predkladac bude hlasit
chybu. Ale jestlize /coz je castejsi pripad/ atof byla
predkladana oddelene, nesoulad nebude detekovan, atof bude
vracet hodnotu double, se kterou program main bude pracovat
jako s promennou integer a dostaneme nesmyslny vysledek
/obsluzny program LINT odhali tuto chybu/.
Mame-li vytvorenou funkci atof, muzeme napsat funkci
atoi, ktera konvertuje retezec znaku na cislo tytu int:
atoi(s) /*konvertuje retezec s na integer*/
char s[];
{
double atof ();
return (atof(s));
}
Vsimnete si struktury deklaraci a prikazu return. Hodnota
vyrazu v
return (vyraz)
je vzdy konvertovana na typ funkce predtim, nez je prikaz
return vykonan. Proto hodnota atof, ktera je typu double,
je automaticky konvertovana na int, kdyz se objevi v prika-
zu return, protoze funkce atoi vraci int. /Konverze cisla
s pohyblivou radovou carkou na cele cislo orezava desetinnou
cast, jak jsme ukazali v kap. 2./
C v i c e n i 4 - 2. Rozsirte funkci atof tak, aby umela
zpracovavat cisla ve vedecke notaci, jako napr.
123.45e-6
kde cislo s pohyblivou desetinnou teckou muze byt nasledovano
pismenem e nebo E a exponentem se znamenkem /ktere nemusi byt
uvedeno/.
4.3. Vice o argumentech funkci
------------------------------
V kapitole 1 jsme uvedli skutecnost, ze argumenty funkci
jsou predavany hodnotou, coz znamena, ze volana funkce obdrzi
"soukromou", prechodnou kopii kazdeho argumentu a ne adresu.
To znamena, ze funkce nemuze zmenit puvodni argument ve vola-
jici jednotce. Ve funkci je argument ve skutecnosti lokalni
promenna, ktera je inicializovana na hodnotu, kterou je
funkce vyvolana.
Kdyz se jako argument funkce objevi identifikator pole,
je predano misto zacatku tohoto pole, prvky pole nejsou
kopirovany. Funkce muze menit hodnoty prvku pole. Pole jsou
tedy predavany adresou. V kapitole 5 budeme hovorit o pou-
ziti pointru, ktere umoznuji funkcim menit take jednotlive
promenne.
Mimochodem, neexistuje uspokojivy zpusob, jak napsat
obecnou funkci, ktera by mohla mit promenny pocet argumentu,
protoze volana funkce nemuze urcit, kolik argumentu bylo
skutecne pri vyvolani predano. Proto nemuzete opravdu napsat
obecnou funkci, ktera bude pocitat maximum libovolneho
mnozstvi argumentu jako to je u funkci MAX ve FORTRANU
nebo PL/1.
Bezpecne muzeme nakladat s promennym mnozstvim parametru,
kdyz volana funkce nepouziva ty argumenty, ktere ji nebyly
ve skutecnosti dodany a kdyz typy sobe odpovidaji. Funkce
printf, ktera je nejobecnejsi funkci jazyka C pouzivajici
promenny pocet argumentu, uziva prvni parametr k urceni
poctu a typu argumentu. Skonci to ovsem spatne, kdyz volaji-
ci jednotka nedoda dostatecne mnozstvi argumentu, nebo kdyz
neodpovidaji jejich typy. Je take neprenosna a musi byt
modifikovana pro ruzne typy pocitacu.
Jestlize jsou typy argumentu zname, je mozne oznacit
konec seznamu argumentu nejakym dohodnutym zpusobem, napr.
pouzit specialni hodnotu /casto se uziva nuly/, ktera ukoncu-
je seznam argumentu.
4.4. Externi promenne
---------------------
Program v jazyku C je slozen z mnoziny exernich objektu,
ktere jsou bud promenne nebo funkce. Pridavne jmeno "externi,
vnejsi" je uzito v kontrastu se slovem "interni, vnitrni",
ktere popisuje argumenty a automaticke promenne uvnitr funkci.
Externi promenne jsou definovany mimo funkce a jsou potenci-
nalne dosazitelne mnoha funkcim. Samotne funkce jsou vzdy
externi, protoze jazyk C nedovoluje definovat funkci uprostred
jine funkce. Implicitne jsou externi promenne take "globalni",
takze odkazy na tyto promenne jsou mozne i z funkci, ktere jsou
prelozeny oddelene. V tomto smyslu jsou externi promenne analo-
gii bloku COMMON ve FORTRANu nebo EXTERNAL v PL/1. Uvidime
pozdeji, jak je mozne definovat externi promenne, ktere nejsou
obecne dosazitelne, ale jsou dosazitelne pouze z jednoho zdro-
joveho souboru.
Protoze externi promenne jsou vseobecne dosazitelne, je
mozne je pouzivat pro komunikaci mezi funkcemi namisto seznamu
argumentu. Libobolna funkce muze dosahnout externi promennou
odkazem na jeji identifikator, jestlize tento identifikator
byl nejak deklarovan.
Jestlize musi byt funkcemi sdilen velky pocet promennych,
tak pouziti externich promennych je vyhodnejsi a efektivnejsi
nez dlouhe seznamy argumentu. Avsak jak jsme poznamenali
v kap. 1. musi byt tento zpusob pouzivan opatrne, protoze
pusobi spatne na strukturu programu a vede k programum,
ktere maji mnoho datovych spojeni mezi jednotlivymi funkcemi.
Druhy duvod, proc pouzivat externi promenne je problem
inicializace. Externi pole mohou byt na rozdil od automatic-
kych /lokalnich/ poli inicializovany. O tom budeme hovorit
skoro na konci teto kapitoly.
Tretim duvodem pro pouzivani externich promennych je je-
jich obor pusobnosti a zivotnost. Automaticke promenne jsou
vnitrni, interni, dane funkci; zacnou existovat pri vyvolani
funkce a zmizi, kdyz skonci cinnost funkce. Externi promenne
jsou stale. Nevznikaji, nekonci a udrzuji hodnotu od volani
jedne funkce do volani funkce dalsi. Proto, jestlize dve
funkce musi sdilet urcita data a jedna nevola druhou, je casto
pohodlnejsi, kdyz jsou tyto promenne uvedeny jako externi
nez predavat tato data sem a tam argumenty.
Vyzkousejme tyto navrhy na vetsim prikladu. Napiseme ji-
ny programovy kalkulator, ktery bude lepsi nez predchozi.
Bude dovolovat operatory +,-,*,/ a = /pro vytisteni vy-
sledku/. Kalkulator bude pouzivat obracenou polskou notaci
/dale RPN/ namisto notace infix, protoze je snadneji imple-
mentovatelna. /RPN pouzivaji napr. kalkulatory firmy Hewlett-
Packard./ V RPN kazdy operator nasleduje za operandy; vyraz
v infixove notaci jako
(1-2) * (4+5) =
je v RPN napsan takto
1 2 - 4 5 + * =
neni nutne pouzivat zavorky.
Implementace je opravdu jednoducha. Kazdy operand je
ulozen do zasobniku; kdyz se ve vyrazu vyskytne operator, tak
odpovidajici pocet operandu /dva pro binarni operace/ je
vytazen ze zasobniku, operator je na ne aplikovan a vysledek
je ulozen do zasobniku. Napr. v predchozim priklade jsou
1 a 2 ulozeny do zasobniku a potom jsou nahrazeny jejich
rozdilem. Dale 4 a 5 jsou ulozeny a potom nahrazeny
jejich souctem. Nasobek -1 a 9, ktery je -9 je v zasobniku
nahradi. Operand = zobrazi vrchni prvek zasobniku, aniz
ho vyjme /takto muzeme kontrolovat mezivysledky./
Operace pro ukladani a vyjimani ze zasobniku jsou jedno-
duche, ale kdyz jsou pridana chybova hlaseni, tak jsou dosta-
tecne dlouha na to, aby byly soustredeny do funkci namisto
opakovani v programu. Rovnez by mela byt pouzita oddelena
funkce pro nacteni dalsiho vstupniho operatoru nebo operandu.
Proto struktura programu bude nasledujici:
while (dalsi vstup neni konec souboru)
if (cislo)
uloz je
else if (operator)
vyjmi operandy
proved operaci
uloz vysledek
else
chyba
Hlavnim rozhodnutim pri navratu, coz jsme zatim nedisku-
tovali, je to, kde je zasobnik ulozen a ktera funkce k nemu
ma primy pristup. Jednou z moznosti je drzet jej v jednotce
main a predavat jeho pozici funkcim, ktere s nim pracuji.
Ale v jednotce main neni treba urcovat promenne, ktere ridi
cinnost ukladani a vyjimani ze zasobniku, mely by se pouzit
pouze operace "uloz" a "vyjmi". Takze se rozhodneme, ze
zasobnik a jemu pridruzene promenne budou externi a dosazi-
telne jen funkcim push - pop.
Tento postup je snadno naprogramovatelny. Jednotka main
bude velky prikaz switch, ktery pracuje s operatory a operan-
dy. To bude typictejsi priklad pro pouziti prikazu switch, nez
jaky byl uveden v kap. 3.
#define MAXOP 20 /*maximalni mnozstvi operandu,
operatoru*/
#define NUMBER '0' /*symbol pro cislo*/
#define TOOBIG '9' /*symbol pro prilis dlouhy rete-
zec*/
main () /* RPN kalkulator*/
{
int type;
char s[MAXOP];
double op2(), pop(), push();
while ((type = getop(s,MAXOP)) != EOF)
switch (type)
{
case NUMBER:
push (atof(s));
break;
case '+':
push (pop() + pop());
break;
case '-':
op2 = pop();
push (pop() - op2);
break;
case '/':
op2 = pop();
if (op2 != 0.0)
push (pop() / op2);
else
printf("zero divisor
popped\n");
break;
case '=':
printf("\t%\n", push (pop()));
break;
case 'c':
clear();
break;
case TOOBIG:
printf("%.20s ... is too
long\n", s);
break;
default:
printf("unknown command %c\n",
type);
break;
}
}
#define MAXVAL 100 /*maximalni velikost zasobniku*/
int sp = 0; /*ukazatel zasobniku*/
double val[MAXVAL]; /*zasobnik cisel*/
double push(f) /*uloz f do zasobniku*/
double f;
{
if (sp < MAXVAL)
return (val[sp++] = f);
else
{
printf ("error: stack full\n");
clear ();
return (0);
}
}
double pop () /*vyjmi vrsek zasobniku*/
{
if (sp > 0)
return (val [--sp];
else
{
printf ("error: stack empty\n");
clear ();
return (0);
}
}
clear () /*vymaz zasobnik*/
{
sp = 0;
}
Prikaz c vymaze zasobnik. Uziva k tomu funkci clear, kterou
rovnez pouzivaji funkce push a pop v pripade chyby. Za chvili
se vratime k funkci getop.
Jak bylo uvedeno v kap. 1, promenna je externi, je-li
definovana mimo tela funkce. Proto musi byt ukazatel zasob-
niku, ktery je sdilen funkcemi push, pop a clear, definovan
mimo tyto funkce. Ale jednotka main se na tento ukazatel
zasobniku neodkazuje - jeho reprezentace je skryta. Proto
cast programu pro operator = musi byt napsana takto
push (pop());
abychom dostali pouze hodnotu vrcholu zasobniku bez jejiho
vyjmuti.
Uvedomte si rovnez, ze + a - jsou komutativni operatory
a nezalezi na poradi, v jakem jsou operandy vyjimany. Avsak
pro operandy - a / musi byt operandy rozliseny.
C v i c e n i 4 - 3. Mame-li napsan zakladni ramec kalkulato-
ru, je snadne jej rozsirit. Pridejte operatory % pro zbytek po
deleni a unarni minus. Pridejte prikaz "erase", ktery vymaze
vrchol zasobniku. Pridejte prikazy pro pouzivani promennych.
/26 jednoduchych promennych muzete snadno pridat./
4.5. Pravidla pole pusobnosti
------------------------------
Funkce a externi promenne, ktere tvori program v jazyku C
nemusi byt prekladany najednou; zdrojovy text programu
muze byt v nekolika souborech a drive prelozene funkce mohou
byt pripojovany z knihoven. Dve hlavni otazky, ktere klademe,
jsou:
- Jakym zpusobem jsou napsany deklarace, aby promenne byly
radne deklarovany v prubehu prekladu?
- Jak jsou deklarace udelany, aby vsechny casti programu
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
a deklarace jsou nezbytne:
V souboru 1
int sp = 0; /*ukazatel zasobniku*/
double val[MAXVAL]; /*zasobnik*/
V souboru 2
extern int sp;
extern double val [];
double push (f) (...)
double pop () (...)
clear () (...)
Protoze deklarace extern je v souboru 2 uvedena drive a mimo
funkce, tak plati pro tyto funkce; tedy v souboru 2 staci
jen jedna deklarace.
Pro vetsi program je mozne pouzit prikazu #include
/pro vkladani souboru, podrobne o tom pozdeji v teto kapito-
le/ a tak mit jen jednu deklaraci extern pro cely program a tu
vkladat timto prikazem jen pri prekladu.
Nyni obratme pozornost k implementaci funkce getop,
ktera nacita dalsi operator nebo operand. Zakladni funkce
je jasna: preskoc mezery, tabelatory a symboly pro novou
radku. Jestlize nacteny znak neni ani cislo ani desetinna
tecka, vrat jej. Jinak nacti retezec cislic /kde rovnez
muze byt desetinna tecka/ a vrat NUMBER, coz je signal
pro to, ze bylo nacteno cislo.
Podprogram je ponekud komplikovany, protoze musi spravne
fungovat, je-li nactene cislo prilis dlouhe. Funkce getop cte
cislice /i s desetinnou teckou/. Jestlize nedoslo k precteni
vraci NUMBER a retezec cislic. Jestlize cislo bylo prilis
dlouhe, getop ignoruje zbytek vstupu, takze uzivatel muze
prepsat radku od mista chyby; bylo-li cislo prilis dlouhe,
vraci TOOBIG.
getop (s, lim) /*nacten operator nebo operand*/
char s[];
int lim;
{
int i, c;
while ((c=getch())==' '||c=='\t'||c=='\n')
;
if (c != '.' && (c < '0' || c > '9'))
return (c);
s [0] = c;
for (i = 1;(c=getch()) >= '0' && c<='9';i++)
if (i < lim)
s [i] = c;
if (c == '.') /*desetinna cast*/
{
if (i < lim)
s [i] = c;
for(i++; (c=getch()) >= '0' &&
c <= '9';i++)
if (i < lim)
s [s] = c;
}
if ( i< lim) /*cislo je ok*/
{
ungetch (c);
s [i] = '\';
return (NUMBER);
}
else /*je prilis dlouhe, preskoc zby-
tek radky*/
{
while (c != '\' && c != EOF)
c = getch();
s [lim-1] = '\0';
return (TOOBIG);
}
}
Co delaji funkce getch a ungetch? Casto se vyskytne situ-
ace, ze program nacitajici vstup nemuze rozhodnout, zdali jiz
nacetl dost a pritom nenasel vice nez je potreba. Jednim z ta-
kovych pripadu je cteni znaku, ktere tvori cislo: dokud nebyl
nacten znak, ktery neni cislici, tak cislo neni kompletni. Po-
tom ale program precetl jeden znak navic. Problem by byl snadno
vyresen, kdyby bylo mozno tento znak "vratit zpatky". Tedy vzdy
kdyz program nacte o znak vice, muze tento znak vratit zpatky
do vstupu, takze se tento znak pro zbytek programu jevi tak,
jako kdyby nebyl nikdy nacten. Nastesti je tento problem
snadno resitelny pomoci dvojice doplnujicich se funkci. Funkce
getch nacita dalsi znak ze vstupu; ungetch jej vraci zpet do
vstupu, takze pri dalsim volani funkce getch vezme opet tento
znak.
Jejich spoluprace je jednoducha a ungetch vrati znak do
spolecne sdileneho bufferu - znakoveho pole. getch cte z tohoto
pole tehdy, kdyz v nem neco je, v opacnem pripade zavola
getchar. Musi rovnez existovat index, ktery urcuje pozici
znaku v bufferu.
Protoze buffer a index jsou sdileny funkcemi getch a
ungetch a musi uchovavat svoji hodnotu mezi vyvolanim techto
funkci musi byt definovany jako externi pro obe funkce..
Potom muzeme napsat getch a ungetch zakto:
# define BUFSIZE 100
char buf[BUFSIZE]; /*definice bufferu*/
int bufp = 0; /*volna pozice v buf*/
getch () /*nacteni vraceneho znaku*/
{
return((bufp > 0) ? buf[--bufp] : getchar());
}
ungetch (c) /*vrat znak zpet do vstupu*/
int c;
{
if ( bufp > BUFSIZE)
printf("ungetch: too many characters\n");
else
buf [bufp++] = c;
}
Pro vracene znaky jsme pouzili pole misto jednoho znaku
pro obecnost a pozdejsi pouziti.
C v i c e n i 4 - 4. Napiste funkci ungets (s), ktera vraci
cely retezec zpatky do vstupu. Musi ungets neco vedet o poli
buf a indexu bufp nebo staci pouzit pouze ungetch?
C v i c e n i 4 - 5. Predpokladejme, ze nikdy nebude potreba
vracet vice nez jeden znak. Modifikujte odpovidajicim zpusobem
funkce getch a ungetch.
C v i c e n i 4 - 6. Nase funkce getch a ungetch nefunguji
spravne, je-li nacteno EOF. Rozhodnete, co by se melo stat,
je-li EOF vraceno zpet a zduvodnete a implementujte svuj
nazor.
4.6. Staticke promenne
-----------------------
Staticke promenne jsou tretim druhem promennych vedle
externich a automatickych promennych, se kterymi jsme se
jiz seznamili. Staticke promenne mohou byt bud i n t e r n i
nebo e x t e r n i. Interni staticke promenne jsou lokalni,
mistni, dane funkci tak jako automaticke promenne, ale na
rozdil od nich existuji trvale. To znamena, ze interni staticke
promenne tvori stalou a privatni "pamet" funkce. Znakove
retezce, ktere se vyskytuji uvnitr funkce jsou interni a sta-
ticke /napr. argument funkce printf/.
Externi staticke promenne maji platnost ve zbytku zdrojo-
veho souboru, ve kterem jsou deklarovany, ale v jinych soubo-
rech jiz platnost nemaji. Externi staticke promenne slouzi
tedy k uschovani jmen jako buf a bufp ve funkcich getch
a ungetch. Tato jmena musi byt externi, aby je bylo mozno
sdilet, ale nemela by byt pristupna uzivateli funkci getch
a ungetch, aby nevznikl konflikt. Jestlize jsou tyto dve funkce
a tyto dve promenne prekladany soucasne v jednom souboru jako
static char buf[BUFSIZE]; /*guffer pro ungetch*/
static int bufp = 0; /*dalsi volna pozice v buf*/
getch () (...)
ungetch (c) (...)
potom zadna dalsi funkce nemuze pracovat s promennymi buf a
bufp; tato jmena tedy vlastne nebudou kolidovat se jmeny stej-
nymi v jinych souborech tehoz programu.
Staticka pamet, at jiz interni nebo externi, je speciali-
zovana slovem s t a t i c , ktere se uvede pred normalni de-
klaraci. Tyto promenne jsou externi, jsou-li definovany mimo
funkce a interni, jsou-li definovany uvnitr nejake funkce.
Funkce samy jsou vlastne externi objekty: jejich jmena maji
obecnou platnost. Je ovsem take mozne, aby funkce byla defino-
vana jako staticka. Potom ma platnost pouze v tom souboru,
kde je deklarovana.
"Staticke" neznamena v C pouze stalost, ale take vlastnost,
ktera muze byt nazvana "privatnost". Interni staticke promenne
patri jen jedne funkci, externi staticke objekty /tj. promenne
nebo funkce/ maji platnost pouze ve zdrojovem souboru, kde jsou
definovany a jejich jmena nemaji zadnou souvislost se stejnymi
jmeny v jinych souborech.
E x t e r n i s t a t i c k e promenne a funkce umoz-
nuji uschovavat data a staticke funkce, ktere s nimi manipuluji
tak, ze nemuze dojit ke kolizi s jinymi funkcemi. Napr. getch
a ungetch tvori "modul" pro vstup a navraceni znaku; promenna
buf a bufp by mely byt staticke, aby nebyly dosazitalne z ven-
ku. Funkce push a pop a clear vytvareji stejnym zpusobem
"modul" pro operace se zasobnikem a tak promenne val a sp
jsou definovany jako externi staticke.
4.7. Promenne typu registr
--------------------------
Ctvrty a posledni typ promenne je nazyvan r e g i s t r .
Deklarace register rika prekladaci, ze promenna bude hodne
vyuzivana. Kdyz je to mozne, tak jsou tyto promenne ulozeny
primo do registru pocitace, coz se muze projevit zmensenim
a zrychlenim programu. Deklarace register ma nasledujici formu
register int x;
register char c;
atd.
Slovo int muze byt vynechano. Typ register muze byt pouzit
pouze pro automaticke promenne a pro formalni parametry funk-
ci. V druhem pripade vypada deklarace takto
f (c,n)
register int c,n;
{
register int i;
...
}
Ve skutecnosti pro tyto promenne plati urcita omezeni,
ktera zavisi na pouzitem pocitaci. Pouze nekolik promennych
ve funkci muze byt tohoto typu a rovnez plati omezeni pro
typ promenne. Slovo register je ignorovano pokud bylo nesprav-
ne aplikovano nebo pokud pocet techto promennych prekracuje
urcitou mez. Neni mozne rovnez urcit adresu promenne typu
register /o tom vice v kapitole 5/. Omezeni se meni s typem
pocitace. Napr. na PDP-11 jsou brany v uvahu pouze prvni tri
deklarace register. Typ promennych musi byt int, char nebo
pointer.
4.8. Blokove struktury
----------------------
Jazyk C neni jazykem blokovych struktur v tom smyslu slova
jako ALGOL nebo PL/1, v nemz funkce nemohou byt definovany
uvnitr jinych funkci. Na druhe strane mnohou byt ale promenne
definovany do blokovych struktur. Za deklaracemi promenych
/vcetne inicializace/ muze nasledovat leva zavorka, ktera
uvadi l i b o v o l n y slozeny prikaz, ne pouze ten prikaz
kterym zacina funkce. Promenne deklarovane timto zpusobem pre-
kryvaji stejne nazvane promenne ve vnejsim bloku a zustavaji
v platnosti az do vyskytu prave zavorky. Napr. v
if (n>0)
{
int i; /*definice nove promenne*/
for (i = 0; i 0); /*deleni*/
while (--i >= 0)
putchar (s[i]);
}
Druha verze pouziva rekurzi. Funkce printd pri kazdem
vyvolani nejprve vola sama sebe.
printd (n) /* tisk n(rekurzivne)*/
int n;
{
int i;
if (n < 0)
{
putchar ('-');
n = -n;
}
if ((i = n / 10) != 0)
printd (i);
putchar (n % 10 + '0');
}
Kdyz funkce vola sebe sama, tak pokazde ma "cerstvou" sadu
automatickych promennych, ktere jsou uplne nezavisle na pred-
chozi sade. Tak pri printd (123) ma prvni printd n = 123.
Ta vola druhou printd s n = 12 a potom tiskne 3. Stejnym zpu-
sobem druha printd predava 1 treti printd /ta ji vytiskne/
a potom sama tiskne 2.
Obecne vzato, rekurze nesetri pamet, protoze nekde musi
existovat zasobnik, kam se promenne ukladaji. Navic neni ani
rychlejsi. Rekurze je ale zato mnohem kompaktnejsi a umoznuje
snazsi zapis a lepsi porozumeni. Rekurze je specialne vyhodna
pro rekuzivne definovane struktury dat jako jsou stromy.
O tom vice v kapitole 6.
C v i c e n i 4. 7. Pouzijte idei z printd a napiste novou
funkci itoa, tj. prevedte integer na retezec znaku pouzitim
rekurze.
C v i c e n i 4. 8. Napiste rekurzivni verzi funkce
reverse(s), ktera obraci retezec s.
4. 11. Preprocesor jazyka C
---------------------------
Jazyk C umoznuje rozsireni jazyka uzitim jednoduchych
makroinstrukci. Prikaz #define je jednou z nejrozsirenejsich.
Dalsi makroinstrukci je vkladani obsahu jinych souboru v pru-
behu prekladu.
V k l a d a n i s o u b o r u
-------------------------------
Kazda radka, ktera ma nasledujici tvar
#include "filename"
je nahrazena obsahem souboru filename. /Uvozovky jsou povinne/.
V souboru se na zacatku casto objevuji jeden dva takove
radky. Je tim vkladan common, prikazy #define a deklarace
extern pro globalni promenne. Vkladany soubor muze obsahovat
dalsi #include.
Prikaz #include je doporucovan pro propojeni deklaraci
velkeho programu. Zarucuje totiz, ze vsechny zdrojove soubory
budou obsahovat shodne definice a deklarace promennych a tak se
eliminuje moznost osklivych chyb. Zmeni-li se ovsem obsah
vkladaneho souboru, vsechny soubory na nem zavisle musi byt
znovu prelozeny.
M a k r o i n s t r u k c e
---------------------------
Definice ve tvaru
#define YES 1
je tou nejjednodussi formou makroinstrukce - nahrazuje jmeno
retezcem znaku. Jmena v definici #define maji stejnou formu
jako identifikatory v jazyku C. Text, kterym jsou nahrazovany
je libovolny. Normalne je text cely zbytek radky. Dlouhe defi-
nice mohou pokracovat na dalsim radku, maji-li jako posledni
znak \. "Obor pusobnosti" jmena definovanoho #define je od
bodu definice do konce souboru. Jmena mohou byt definovana
znovu a definice mohou pouzivat definice predchozi. Jmena
nejsou nahrazovana jestlize se vyskytuji v uvozovkach. Napr.
je-li YES definovane jmeno, tak v prikazu printf ("YES")
nebude nahrazeno. Protoze implementace prikazu #define je
makroinstrukce a neni soucasti prekladace, neni mnoho grama-
tickych omezeni. Napr. vyznavaci ALGOLU mohou definovat:
#define then
#define begin {
#define end ; }
a psat
if (i > 0) then
begin
a = 1;
b = 2
end
Rovnez je mozne definovat makra s argumenty, takze nahrada je
zavisla na zpusobu volani. Jako priklad uvedeme makro max:
#define max(A, B ) ((A) > (B) ? (A) : (B))
Potom radka
x = max (p+q,r+s);
bude nahrazena radkou
x = ((p+q) > (r+s) ? (p+q) : (r+s) ;
Je to funkce, ktera ze dvou promennych vybira vetsi. Nakladame
-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;
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("%d\n", *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
py = px;
zkopiruje obsah promenne px do py. Potom py ukazuje na stejne
misto jako px.
FF
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;
{
int temp;
temp = x;
x = y;
y = temp;
}
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;
{
int temp;
temp = *px;
*px = *py;
*py = temp;
}
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
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;
{
intc, sign;
while((c = getch()) == ' ' || c == '\n' ||
c =='\t')
; /*ignoruj oddelovace*/
sign = 1;
if(c == '+' || c == '-')
{ /*znamenko*/
sign = (c == '+') ? 1 : -1;
c = getch();
}
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
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;
{
int n;
for(n = 0; *s != n '\0'; s++)
n++;
return(n);
}
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
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 Adresova 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.
#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;
{
if(allocp + n <= allocbuf + ALLOCSIZE)
{ /*sedi to*/
allocp += n;
return(allocp - n); /*stare p*\
}
else /*malo mista*/
return(NULL);
}
free(p) /*volna pamet*/
char *p;
{
if(p >= allocbuf && p < allocbuf + ALLOCSIZE)
allocp = 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
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;
{
char *p = s;
while(*p != '\0')
p++;
return(p-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 Znakove 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, world\n");
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[];
{
int i;
i = 0;
while((s[i] = t[i]) != '\0')
i+++;
}
Pro srovnani nyni strcpy s pointry
strcpy(s, t) /*kopiruj t do s; verze s pointry*/
char *s, *t;
{
while((*s = *t) != '\0')
{
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;
{
while((*s++ = *t++) != '\0')
;
}
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;
{
while(*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 s0 kdyz s>t*/
char s[], t[];
{
int i;
i = 0;
while(s[i] == t[i])
if(s[i++ == '\0')
return(0);
return(s[i] - t[i]);
}
Verze s pouzitim pointru:
strcmp(s, t) /*dtto*/
char *s, *t;
{
for(; *s == *t; s++, t++)
if(*s == '\0')
return(0);
return(*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 cela cisla
-----------------------------
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;
{
char *p, *alloc();
if((p = alloc(strlen(s)+1)) != NULL)
strcpy(p, s);
return(p);
}
Prakticky se ale vynechava deklarace
strsave(s) /*uloz nekam retezec*/
{
char *p;
if((p = alloc(strlen(s)+1)) != NULL)
strcpy(p, s);
return(p);
}
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 Vicerozmerna 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] =
{
(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
(0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
}
;
day_of_year(year, month, day)
int year, mont, day;
{
int i, leap;
leap = year % 4 == 0 && year % 100 != 0 || year %
400 ==0;
for(i = 1; i < month; i++)
day += day_tab[leap] [i];
return(day);
}
month_day(year, yearday, pmonh, pday)
int year, yearday, *pmonth, pday;
{
int i, leap;
leap = year % 4 == 0 && year % 100 != 0 || year %
400 == 0;
for(i = 1; yeardy > day_tab[leap] [i]; i++)
yearday -=day_tab[leap] [i];
*pmonth = i;
*pday = yearday;
}
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*/
{
char *lineptr[LINES]; /*pointry na radky*/
int nlines: /*pocet nactenych radek*/
if((nlines = readlines(lineptr, LINES)) >= 0)
{
sort(lineptr, nlines);
writelines(lineptr, nlines);
}
else
printf("input too big to sort\n");
}
#define MAXLEN 1000
readlines(lineptr, maxlines) /*cti vsupni radky*/
char *lineptr[];
int maxlines;
{
int len, nlines;
char *p, *alloc(), line[MAXLEN];
nlines = 0;
while((len = getline(line,MAXLEN)) > 0)
if(nlines >= maxlines)
return(-1);
else if((p = alloc(len)) == NULL)
return(-1);
else
{
line[len-1]='\0';/*novy radek*/
strcpy(p, line);
lineptr[nlines++] = p;
}
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;
{
int i;
for(i = 0; i= 0)
printf("%s\n", *lineptr++);
}
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
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;
{
int gap, i, j;
char *temp;
for(gap = n\2; gap>0; gap /= 2)
for(i = gap; i= 0; j -= gap)
{
if(strcmp(v[j], v[j+gap]) <=0)
break;
temp = v[j];
v[j] = v[j+gap];
v[j+gap] = temp;
}
}
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:
char *month_name(n) /*vrat jmeno n-teho mesice*/
int n;
{
static char *name[] =
{
"illegal month",
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
}
;
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 vicedimenzionalni 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.
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 prikazove radky
----------------------------------------
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[];
{
int i;
for(i = 1; i < argc; i++)
printf("%s%c", argv[i], (i < argc-1) ?
' ' : '\n');
}
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[];
{
while(--argc > 0)
printf("%s%c, *++argv,(argc > 1) ? '': '\n');
}
Protoze argv je pointrem na zacatek pole retezcu argumentu,
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[];
{
while(--argc > 0)
printf((argc > 1) ? "%s" : "%s\n", *++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[];
{
char line[MAXLINE]
if(argc != 2)
printf("Usage: find pattern\n");
else
while(getline(line, MAXLINE) > 0)
if(index(line, argv[1] >= 0)
printf("%s, line);
}
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[];
{
char line[MAXLINE], *s;
long lineno = 0;
int except = 0, number = 0;
while(--argc > 0 && (*++argv)[0] == '-')
for(s = argv[0]+1; *s != '0'; s++)
switch(*s)
{
case 'x':
except = 1;
break;
case 'n':
numer = 1;
break;
default:
printf("find: illeagal
option%c\n",*s);
argc = 0;
break;
}
if(argc != 1)
printf("Usage: find -x -n pattern\n");
else
while(getline(line, MAXLINE) > 0)
{
lineno++;
if(index(line, *argv) >= 0)
!= except)
{
if(number)
printf("%1d: ",
lineno);
printf("%s", line);
}
}
}
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
*++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.
5.12 Pointry funkci
--------------------
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[];
{
char *lineptr[LINES];/*pointry na text. radky*/
int nlines; /*pocet nacteych radek*/
int strcmp(),numcmp(); /*porovnavaci funkce*/
int swap(); /*funkce vymeny*/
int numeric = 0; /*1 pro numerickou vymenu*/
if(argc > 1 && argv[1][0] == '-' && argv[1][1]
== 'n')
numeric = 1;
if((nlines = readlines(lineptr,
LINES)) >= 0)
{
if(numeric)
sort(lineptr,nlines,numcmp,swap);
else
sort(lineptr,nlines,strcmp,swap);
writelines(lineptr,nlines);
}
else
printf("input too big to sort\n");
}
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)();
{
int gap, i, j;
for(gap = n/2; gap > 0; gap /= 2)
for(i = gap; i= 0;j -= gap)
{
if((*comp)(v[j],v[j+gap])
<= 0)
break;
(*exch)(&v[j],&v[j+gap]);
}
}
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.
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;
{
double atof(), v1, v2;
v1 = atof(s1);
v2 = atof(s2);
if(v1 < v2)
return(-1);
else if(v1 > v2)
return(1);
else
return(0);
}
Nakonec uvedeme funkci swap, ktera meni dva pointry:
swap(px, py) /*vymena *px a *py*/
char *px[], *py[];
{
char *temp;
temp = *px;
*px = *py;
*py = temp;
}
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 Zaklady
-------------
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
{
int day;
int month;
int year;
int yearday;
char mon_name [4];
};
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,
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 = {4,7,1776,186,"Jul"};
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
{
char name [NAMESIZE];
char address [ADRSIZE];
long zipcode;
long ss number;
double salary;
struct date birthdate;
struct date hiredate;
};
Struktura person obsahuje dva datumy. Jestlize budeme deklaro-
vat emp jako
struct person emp;
potom
emp.birthdate.month
udava mesic narozeni. Operator "." pracuje zleva doprava.
6.2 Struktury a funkce
-----------------------
V jazyku C existuji omezeni pro struktury. Zakladnim pravi-
dlem je to, ze jedine operace, ktere muzeme se strukturami
provadet, je zjisteni adresy operatorem & a pristup k jejim
clenum. Z toho plyne, ze struktury nemohou byt prirazovany nebo
kopirovany jako obycejne 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;
{
int i, day, leap;
day = pd -> day;
leap = pd -> year % 4 == 0 && pd -> year % 100
!= 0 || pd -> year % 400 == 0;
for (i=1; i < pd -> month; i++)
day += day_tab[leap] [i];
return (day);
}
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;
{
int i, leap;
leap = pd -> year % 4 == 0 && pd -> year % 100
!= 0 || pd -> year % 400 == 0;
pd -> day = pd -> yearday;
for (i = 1; pd -> day_tab[leap] [i]; i++)
pd -> day -= day_tab[leap] [i];
pd -> month = i;
}
Operator -> a ., spolu s () pro seznam argumentu a []
pro indexy maji ze vsech operatoru nejvyssi prioritu. Napr.
mame-li danu deklaraci
struct
{
int x;
int *y;
}*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.
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
{
char *keyword;
int keycount;
}
keytab [NKEYS];
Pole keytab alokuje pamet. Kazdy prvek pole je strukturou.
To muze byt napsano takto
struct key
{
char *keyword;
int keycount;
};
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
{
char *keyword;
int keycount;
}
keytab [] =
{
"break", 0,
"case", 0,
"char", 0,
"continue", 0,
"default", 0,
/* ... */
"unsigned", 0,
"while", 0
};
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 */
{
int n, t;
char word [MAXWORD];
while ((t = getword(word,MAXWORD)) != EOF)
if(t == LETTER)
if((n=binary(word,keytab,NKEYS)) >= 0)
keytab[n].keycount++;
for (n = 0; n < NKEYS; n++)
if (keytab[n].keycount > 0)
printf ("%4d %s\n",
keytab[n].keycount,
keytab[n].keyword);
}
binary (word,tab,n) /*najdi slovo v tab[0]...tab[n-1]*/
char *word;
struct key tab [];
int n;
{
int low, high, mid, cond;
low = 0;
high = n - 1;
while (low <= high)
{
mid = (low + high) / 2;
if((cond=strcmp(word,tab[mid].keyword))<0)
high = mid - 1;
else if(cond > 0)
low = mid + 1;
else
return (mid);
}
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;
{
if(c >= 'a' && c <= 'z' || c >= 'A'&& c <= 'Z')
return(LETTER);
else if(c >= '0' && c <= '9')
return(DIGIT);
else
return(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*/
{
int t;
char word[MAXWORD];
struct key *binary(), *p;
while ((t = getword(word,MAXWORD)) != EOF)
if (t == LETTER)
if ((p = binary(word,keytab,NKEYS))
!= NULL)
p -> keycount++;
for (p = keytab; p < keytab + NKEYS; p++)
if (p -> keycount > 0)
printf("%4d %s\n", p -> keycount,
p -> keyword);
}
struct key *binary(word, tab, n) /*najdi slovo*/
char *word;,
struct key tab[];
int n;
{
int cond;
struct key *low = &tab [0];
struct key *high = &tab[n-1];
struct key *mid;
while (low<=high)
{
mid = low + (high - low) / 2;
if ((cond = strcmp(word,mid
-> keyword))<0)
high = mid - 1;
else if (cond > 0)
low = mid + 1;
else
return (mid);
}
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 odkazujici 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-
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*/
{
char *word; /*pointr na text*/
int count; /*pocet vyskytu*/
struct tnode *left; /*levy poduzel*/
struct tnode *right; /*pravy poduzel*/
};
"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*/
{
struct tnode *root, *tree ();
char word [MAXWORD];
int t;
root = NULL;
while ((t = getword( word, MAXWORD)) != EOF)
if (t == LETTER)
root = tree (root,word);
treeprint (root);
}
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;
{
struct tnode *talloc ();
char *strsave();
int cond;
if (p == NULL) /*nove slovo*/
{
p = talloc(); /*vytvoreni noveho uzlu*/
p -> word = strsave (w);
p -> count = 1;
p -> left = p -> right = NULL;
}
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*/
{
if (p != NULL)
{
treprint (p -> left);
print ("%4d % s\n",p -> count, p -> word);
treeprint (p -> right);
}
}
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()
{
char *alloc();
return((struct tnode*)alloc(sizeof(struct
tnode)));
}
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 Prohledavani 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*/
{
char *name;
char *def;
struct nlist *next; /*dalsi vstup v retezci*/
};
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;
{
int hashval;
for(hashval = 0; *s != '\0';)
hashval += *s++;
return(hasval % HASHSIZE);
}
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;
{
struct nlist *np;
for(np = hashtab[ hash(s)];np == NULL;np=np -> next)
if (strcmp(s,np -> name) == 0 )
return(np); /*nalezeno*/
return(NULL); /*nenalezeno*/
}
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*/
{
struct nlist *np, *lookup();
char *strsave(), *alloc();
int hashval;
if((np = lookup (name)) == NULL) /*nenalezeno*/
{
np=(struct nlist*) alloc (sizeof(*np));
if (np == NULL)
return (NULL);
if ((np -> name = strsave (name)) == NULL
return (NULL);
hashval= hash (np -> name);
np -> next = hashtab [hashval];
hasthab [hashval] = np;
}
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
{
unsigned is_keyword : 1;
unsigned is_extern : 1;
unsigned is_static : 1;
} 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-
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
{
inf ival;
float fval;
char *pval;
} 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 ("%d\n", uval.fval);
else if (utype == FLOAT)
printf ("%f\n", uval.fval);
else if (utype == STRING)
printf ("%s\b",uval.pval);
else
printf ("bad type %d in utype\n", 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
{
char *name;
int flags;
int utype;
union
{
int ival;
float fval;
char *pval;
} 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 Prikaz 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*/
{
char *word; /*ukazatel na dalsi jmeno*/
int count; /*pocet vyskytu*/
struct tnode *left /*levy poduzel*/
struct tnode *right; /*pravy poduzel*/
}
TREENODE, *TREEPTR;
Tato definice vytvari dve nova slova: TREENODE /struktura/ a
TREEPTR /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 VYSTUP
--------------------------
Vstup a vystup neni casti jazyka C a tak jsme na ne nekladli
duraz. Nicmene opravdove programy spolupracuji se svym okolim
slozitejsim zpusobem, nez jak jsme doposud ukazali. V teto
kapitole popiseme standardni "knihovnu vstupu a vystupu" - sou-
bor funkci, ktere byly navrzeny pro standardni I/O system jazy-
ka C. Funkce byly navrzeny tak, aby umozovaly pohodlne
programovani. Funkce jsou dostatecne efektivni, takze uzivatel
temer neciti potrebu je "zrychlovat a zefektivnovat". A nakonec
budiz receno, ze funkce jsou "prenositelne", coz znamena, ze
existuji kompatibilne na pocitacich, kde je inplementovan
jazyk C. Mohou byt prevadeny z jednoho systemu do druheho bez
podstatnych zmen. Zde se nebudeme snazit o popis cele knihovny.
Vetsi duraz klademe na to, abychom ukazali zaklady psani
programu v jazyku C, nez abychom se soustredili na to, jak
programy spolupracuji se svym okolim.
7.1 Pristup do standardni knihovny
----------------------------------
Kazdy zdrojovy soubor, ktery potrebuje funkce ze standardni
knihovny musi obsahovat radek
#iuclude
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. Standardni 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 '' 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
main () /* konvertovani velkych pismen na mala
{
int c;
while ((c=getchar()) != EOF)
putchar (isupper(c)? tolower(c) : c);
}
"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. (program
cat 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. Formatovy vystup - 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. Formatovy 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
Thompson\0. 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
main() /*jednoduchy stolni kalkulator*/
{
double sum,v;
sum=0;
while(scanf("%lf",&v) != EOF)
printf("\t%.2f\n",sum+=v);
}
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. Formatove konverze u pameti
----------------------------------
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. Pristup 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, ale
stdin 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];
{
FILE *fp, *fopen();
if(argc == 1) /*zadne argumenty; kopiruj
standardni vstup */
filecopy (stdin);
else
while(--argc > 0)
{ if((fp=fopen(*++argv,"r")) == NULL
{ printf("cat:can't open %s\n",*argv);
break;
}
else { filecopy(fp);
fclose (fp);
}
}
}
filecopy (fp) /* zkopirovani fp do standardniho vystupu */
FILE *fp;
{
int c;
while ((c=getc(fp)) != EOF)
putc(c,stdout);
}
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 pouzivat
ty 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. Osetrovani 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*
main(argc, argv) /*cat:spojovani souboru*/
int argc;
char *argv[];
{
FILE *fp, *fopen();
if (argc == 1) /* zadne argumenty. Kopiruj
standardni vstup */
filecopy(stdin);
else
while(--argc > 0)
{ if((fp=fopen(*++argv,"r")) == NULL)
{ fprintf(stderr,
"cat:can't open %s\n",*argv);
exit(1);
}
else
{ filecopy(fp);
fclose(fp);
}
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 souboru a je
vyvolana funkce - exit. Funkce - exit okamzite ukoncuje cinnost
programu. Muze byt rovnez primo vyvolana.
7.8. Radkovy vstup a vystup
----------------------------
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
char *fgets(s,n,iop) /*nacteni n znaku z iop*/
char *s;
int n;
register FILE *iop;
{
register int c;
register char *cs;
cs = s;
while (--n > 0 && (c=getc(iop)) != EOF)
{ if((*cs++=c) == '\n') break;
*cs='\0';
return((c == EOF && cs == s) ? NULL:s);
}
}
fputs(s,iop) /* zapsani retezce s do souboru
iop */
register char *s;
register FILE *iop;
{
register int c;
while (c=*s++)
putc(c,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 vytisteno
jmeno 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. Nektere dalsi 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: SYSTEMOVE SOUVISLOSTI S OPERACNIM SYSTEMEM 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
souboru pomoci znaku < a > :
prog 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 vystup nejnizsi urovne - 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*/
{
char buf [BUFSIZE];
int n;
while ((n = read(0,buf,BUFSIZE)) > 0)
write(1,buf,n,);
}
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*/
{
char c;
return ((read(0,&c,1) > 0) ? c & CMASK : EOF);
}
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[];
{
int f1,f2,n;
char buf [BUFSIZE];
if(argc != 3)
error("Usage: cp from to", NULL);
if((f1 = open(argv[1],0)) == -1)
error("cp: can't open %s", argv[1]);
if((f2 = creat(argv[2],PMODE)) == -1)
error ("cp:can't create %s", argv[2]);
while((n = read(f1,buf,BUFSIZE)) > 0)
if(write(f2,buf,n,) != n)
error("cp:write error", NULL);
exit(0);
}
error(s1, s2) /*vytiskni chybovou zpravu a ukonci
cinnost*/
char *s1,*s2;
{
printf(s1, s2);
printf("\n");
exit(1);
}
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 Nahodny pristup - 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;
{
lseek(fd,pos,0); /*nastav ukazatel na pozici
pos*/
return(read(fd,buf,n));
}
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 Priklad - 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
souboru*/
typedef struct _iobuf {
char *_ptr; /*pozice nasledujiciho znaku*/
int _cnt; /*pocet jiz zpracovanych znaku*
char *_base; /*adresa I/0 buferu*/
int _flag; /*zpusob pristupu k souboru*/
int _fd; /*deskriptor souboru*/
} 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
#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;
{ register int fd;
register FILE *fp;
if(*mode != 'r' && *mode != 'w' && *mode != 'a')
{
fprintf(stderr,"illegal mode %s opening %s \n",
mode,name;
exit(1);
}
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')
{
if((fd = open(name,1)) == -1)
fd = creat(name,PMODE);
lseek(fd,0L,2);
}
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
_fillbuf (fp) /*pridel a napln vstupni bufer*/
register FILE fp;
{
static char smallbuf [_NFILE]; /*pro I/0 bez buferu*/
char *calloc();
if((fp -> _flag & _READ) == 0 || fp -> _flag & (_EOF
| _ERR)) != 0)
return(EOF);
while (fp -> _base==NULL) /*najdi misto pro bufer*/
if(fp -> _flag & _UNBUF) /*I/0 bez buferu*/
fp -> _base = &smallbuf[fp -> _fd];
else if((fp -> _base = calloc(_BUFSIZE,1)) == NULL)
fp -> _flag |= _UNBUF; /*nemohu najit misto
pro bufer*/
else
fp -> _flag |= _BIGBUF; /*misto pro bufer
bylo nalezeno*/
fp -> _ptr = fp -> _base;
fp -> _cnt = read(fp -> _fa,fp -> _ptr,
fp - >_flag & _UNBUF ? 1 : _BUFSIZE);
if(--fp -> _cnt < 0)
{
if(fp -> _cnt == -1)
fp -> _flag |= _EOF;
else
fp -> _flag |= _ERR;
fp -> _cnt = 0;
return(EOF);
}
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 Priklad - Vypis adresare
-----------------------------
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*/
{
ino_t d_ino; /*hodnota inode*/
char d_name [DIRSIZ]; /*jmeno souboru*/
} ;
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*/
{
dev_t st_dev; /*typ zarizeni*/
ino_t st_ino; /*hodnota inode*/
short st_mode /*zpusob pristupu*/
short st_nlink; /*pocet smerniku souboru*/
short st_uid; /*vlastnikuv identifikator*/
short st_gid; /*skupinovy identifikator*/
dev_t st_rdev; /*priznak zvlastnich souboru*/
off_t st_size /*delka souboru ve znacich*/
time_t st_atime; /*datum posledniho pristupu*/
time_t st_mtime; /*datum posledni modifikace*/
time_t st_ctime; /*datum vytvoreni souboru*/
} ;
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
#include /*definice typu*/
#include /*struktura polozky adresare*/
#include /*struktura vracena funkci stat*/
#define BUFSIZE 256
main (argc,argv) /*fsize: tiskni velikost souboru*/
char *argv[];
{
char buf[BUFSIZE];
if (argc == 1) /*default: aktualni adresar*/
{
strcpy (buf,".");
fsize (buf);
}
else
while (--argc > 0)
{
strcpy(buf,*++argv);
fsize(buf);
}
}
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;
{
struct stat stbuf;
if (stat (name,&stbuf) == -1)
{
fprintf (stderr,"fsize: can't find %s n", name);
return;
}
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;
{
struct direct dirbuf;
char *nbp, *nep;
int i, fd;
nbp = name + strlen (name);
*nbp++ = '/'; /*pridej znak "/" ke jmenu adresare*/
if (nbp + DIRSIZ + 2 >= name + BUFSIZE) /*jmeno je
priliz dlouhe*/
return;
if ((fd = open (name,0)) == -1)
return;
while (read (fd,(char*) & dirbuf,sizeof(dirbuf)) > 0)
{
if (dirbuf.d_ino == 0) /*polozka neni pouzita*/
continue;
if (strcmp (dirbuf.d_name,".") == 0
||strcmp (dirbuf.d_name,".." == 0)
continue; /*preskoc sama sebe
a sve rodice*/
for (i = 0, nep = nbp; i < DIRSIZ; i++)
*nep++ = dirbuf.d_name[i];
*nep++ = '\0';
fsize (name);
}
close (fd);
*--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 Priklad - Pridelovani pameti
---------------------------------
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
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 header{ /*volna hlavicka bloku*/
struc {
union header *ptr; /*nasledujici volny blok*/
unsigned size; /*velikost tohoto volneho
bloku*/
}s;
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;
{
HEADER *morecore();
register HEADER *p, *q;
register int nunits;
nunits = 1 + (nbytes + sizeof(HEADER) - 1)
/ sizeof(HEADER);
if (( p = allocp) == NULL) /*seznam volnych bloku
neexistuje*/
{
base.s.ptr = allocp = q = &base;
base.s.size = 0;
}
for (p = q -> s.ptr; ; q = p -> s.ptr)
{
if (p -> s.size >= nunits /*je vetsi*/
{
if (p -> s.size == nunits) /*je roven*/
q -> s.ptr = p -> s.ptr;
else /*pridel konec bloku*/
{
p -> s.size -= nunits;
p += p -> s.size;
p -> s.size = nunits;
}
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;
{
char *sbrk();
register char *cp;
register HEADER *up;
register int rnu;
rnu = NALLOC * ((nu + NALLOC - 1) / NALLOC);
cp = sbrk(rnu * sizeof(HEADER));
if ((int) sp == -1) /*neni zadna volna pamet*/
return (NULL);
up = (HEADER*) cp;
up -> s.size = rnu;
free ((char*)(up + 1));
return (allocp);
}
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;
{
register HEADER *p, *q;
p = (HEADER*) ap - 1; /*ukazovatko na hlavicku bloku*/
for (q = allocp; ! (p > q && p < q -> s.ptr);
q = q -> s.ptr)
if (q >= q -> s.ptr && (p > q || p < q -> s.ptr))
break; /*neprileha-li*/
if (p + p -> s.size == q -> s.ptr) /*pripoj k nasled.
bloku*/
{
p -> s.size += q -> s.ptr -> s.size;
p -> s.ptr = q -> s.ptr -> s.ptr;
}
else
p -> s.ptr = q -> s.ptr;
if (q + q > s.size == p) /*pripoj k predch. bloku*/
{
q -> s.size += p -> s.size;
q -> s.ptr = p -> s.ptr;
}
else
q -> s.ptr = p;
allocp = q;
}
Ackoliv je pridelovani pameti v podstate zavisle na technic-
kem vybaveni systemu, program vyse uvedeny ukazuje, jak tyto
zavislosti mohou byt rizeny a svereny nevelke casti progra-
mu. 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 vol-
ne 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 rozpoz-
navat 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 okam-
ziku.
PRILOHA A: REFERENCNI POPIS C JAZYKA
------------------------------------
1.Uvod
------
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. Lexikalni konvece
--------------------
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. Komentare
--------------
Komentar je uveden dvojici znaku "/*" a ukoncen dvojici zna-
ku "*/". Komentare nesmeji byt do sebe navzajem vnorene.
2.2. Identifiktory /jmena/
--------------------------
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 Klicova 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 Celociselne 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 Explicitni 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 Znakove 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 plovouci carce
---------------------------------
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 Retezce
------------
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 Technicke vybaveni
----------------------
Nasledujici tabulka shrnuje vlastnosti technickeho vybaveni
jiz vyse zminenych ctyr typu pocitacu. Ackoliv tyto vlastnosti
maji 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. Syntakticky zapis
--------------------
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 jmeno?
------------------
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, ale
jejich 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 cela cisla
----------------------
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 Plovouci carka a dvojnasobna presnost
-----------------------------------------
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 plovouci carky a typy celociselne
------------------------------------------
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 Ukazovatka a cela cisla
---------------------------
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 Cisla bez znamenka
----------------------
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 Aritmeticke 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. Vyrazy
---------
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 Zakladni vyrazy
-------------------
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-
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
8.6.
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.
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 Unarni operatory
---------------------
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
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 Multiplikativni operatory
------------------------------
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 Aditivni operatory
-----------------------
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 posunute o E2 bitu
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 menej b menej c
nema ten samy vyznam jako v algebre.
relacni_vyrazy
vyraz menej vyraz
vyraz viac vyraz
vyraz menej= vyraz
vyraz viac= 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 vacsie ako b
== c>d je roven hodnote 1, kdykoliv vyrazy a(vacsie)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-
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.
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
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
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.
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:
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.
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 {seznam_struktury}
struktura/union identifikator {seznam_struktury}
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
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.
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{
CHAR tword[20];
INT count;
STRUCT tnode *left;
STRUCT tnode *rignt;
};
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.
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.
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] = (
1, 3, 5, 2, 4, 6, 3, 5, 7
);
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] =(
(1), (2), (3), (4)
);
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 %s\n";
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:
(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 {DOUBLE re, im;} 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.
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
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)
{
prikaz
vyraz-3;
}
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).
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.
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(...)
{ { {
... ... ...
contin:; contin:; contin:;
WHILE(...);
} } }
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:
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
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;
{
INT m;
m=(a>b) ? a:b;
RETURN((m>c) ? m: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.
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;
...
{
AUTO INT distance;
...
Ve vyse uvedenem prikladu musi byt ve druhe definici explicit-
ne uveden typ int, jinak by tato definice byla pochopena jako
definice bez deklaratoru a s typem distance. Je nutne priznat,
ze tato vlastnost jazyka je ponekud problematicka.
11.2 Rozsah platnosti externich promennych
-------------------------------------------
Jestlize funkce pouziva identifikator, ktery je deklarovan
jako EXTERN, musi byt tento identifikator jako externi dekla-
rovan v nekterem souboru nebo knihovne ze kterych je vytvoren
kompletni program. Vsechny funkce, ktere pracuji s timze exter-
nim identifikatorem pracuji s timze objektem, musi byt tedy
zajisteno, ze typ a velikost dana definici identifikatoru je
kompatibilni s typem a velikosti specifikovane ve vsech funk-
cich, ktere tento identifikator pouzivaji.
Vyskyt klicoveho slova EXTERN v externi definici znamena,
ze pametove misto pro danou promennou, ktera je deklarovana,
bude rezervovano jinou casti programu, ktera je umistena v ji-
nem souboru. Program, ktery je slozen z vice casti, ktere jsou
umisteny v samostatnych souborech, musi obsahovat pouze jedi-
nou definici externich dat bez klicoveho slova EXTERN. V ostat-
nich souborech, ve kterych ma byt dana externi definice, musi
byt v ni uvedeno klicove slovo EXTERN.
Identifikatory, ktere jsou deklarovany na vnejsi urovni pro-
gramu s klicovym slovem STATIC, nejsou z ostatnich souboru pri-
stupne. Se specifikaci STATIC smeji byt deklarovany i funkce.
12. Ridici radky prekladace
---------------------------
Prekladac jazyka C obsahuje prekladac schopny makrosubstice,
podmineneho prekladu a zacleneni "include" souboru. Radky,
ktere pocinaji znakem "#", ridi cinnost tohoto predprekladace.
Tyto radky maji syntaxi nezavislou na syntaxi jazyka C. Mohou
se vyskytovat kdekoliv v programu a jejich ucinek je od radky,
ktera je nasleduje az do konce programu.
12.1 Zamena syntakticke jednotky
---------------------------------
Ridici radka predprekladace ma nasledujici tvar:
#DEFINE identifikator retezec_syntaktickych_jednotek
Radka nemusi byt ukoncena strednikem. Tato direktiva zpusobi,
ze kazdy vyskyt identifikatoru v programu bude nahrazen retez-
cem syntaktickych jednotek.
Radka muze mit alternativni formu:
#DEFINE identifikator(identifikator, ...,identifikator)rete-
zec_syn._jedn.
Nesmi byt mezera mezi prvnim identifikatorem a znakem "(".
V tom pripade se jedna o definici makra s argumenty. Rada polo-
zek, ktera se sklada s prvniho identifikatoru, leve zavorky,
seznamu syntaktickych polozek oddelenych carkami a prave za-
vorky je nahrazena retezcem syntaktickych polozek z ridici rad-
ky. Pri tom je kazdy vyskyt identifikatoru v seznamu formalnich
parametru v definici nahrazen odpovidajici syntaktickou poloz-
kou pouzitou pri vyvolani makra. Skutecne argumenty pri vyvola-
ni makra jsou retezce syntaktickych jednotek oddelenych carka-
mi. Carky, ktere se vyskytuji v retezci uzavrenych mezi uvozov-
ky, neoddeluji argumenty. Pocet formalnich a skutecnych argu-
mentu musi byt tentyz. Texty uvnitr retezcu nebo znakove kon-
stanty nejsou nahrazovany.
Pri obou formach je retezec po zamene znovu zpracovan pred-
prekladacem. Dlouhe definice mohou pokracovat na nasledujici
radce, jestlize se predchazejici radka ukonci znakem "\".
Tato schopnost prekladace je velmi vyhodna pri definici
casto pouzivanych konstant, jako napriklad:
#DEFINE tabsize 100
INT table[tabsize];
Ridici radka, ktera ma nasledujici formu:
#UNDEF identifikator
zpusobi, ze predesla definice identifikatoru pomoci ridici rad-
ky #DEFINE bude prekladacem zapomenuta.
12.2 Vkladani souboru
----------------------
Ridici radka, ktera ma nasledujici tvar:
#INCLUDE "nazev_souboru"
zpusobi nahrazeni teto radky obsahem souboru, jehoz nazev je
nazev_souboru. Tento soubor je nejprve hledan v adresari, ve
kterem je umisten zdrojovy soubor, ktery je prave prekladan.
Pokud neni v tomto adresari nalezen, je postupne hledan v dal-
sich standartnich adresarich . Existuje alternativni forma te-
to ridici radky, ktera ma nasledujici tvar:
#INCLUDE
V tomto pripade jsou prohledavany pouze standartni adresare a
neni prohledavan adresar ve kterem je umisten zdrojovy soubor.
"Include" soubor muze obsahovat dalsi prikaz INCLUDE.
12.3 Podminena kompilace
-------------------------
Ridici radka prekladace, ktera ma nasledujici tvar:
#IF konstantni_vyraz
Jestlize kontrolovana podminka je pravdiva, jsou radky mezi
ridicimi radkami #ELSE a #ENDIF ignorovany. Jestlize kontro-
lovana podminka je nepravdiva, jsou ignorovany radky mezi rad-
kou obsahujici test a radkou s ridicim prikazem #ELSE, nebo
jestlize neni radka #ELSE uvedena, radkou obsahujici ridici
prikaz #ENDIF.
Pouziti techto konstrukci muze byt vnorene.
12.4 Rizeni radkovani
----------------------
Pro pouziti s ostatnimi predprekladaci, ktere mohou genero-
vyt program v jazyku C, je zavedena ridici radka ve tvaru:
#LINE konstanta identifikator???
Tato radka zpusobi, ze prekladac bude predpokladat, ze cislo
nasledujici radky ve zdrojovem souboru je rovno konstante uve-
dene v ridicim prikazu LINE, a zapocne od teto hodnoty dalsi
radky cislovat. Toto je uzitecne napriklad z duvodu diagnostiky
chyb. Pokud je uveden i identifikator, bude predpokladat, ze
nazev zdrojoveho souboru je totozny se jmenem tohoto identifi-
katoru.
13. Implicitni deklarace
------------------------
Neni vzdy nutne specifikovat jak tridu ulozeni tak typ iden-
tifikatoru v deklaraci. Trida ulozeni je dana kontextem pri
externi definici a pri deklaracich formalnich parametru a polo-
zek struktur. Pri deklaracich uvnitr funkci plati nasledujici
pravidla:
- je dana trida ulozeni, ale neni dan typ, potom se predpoklada
typ INT
- je dan typ, ale neni dana trida ulozeni, potom se predpoklada
trida AUTO
- vyjimkou z druheho pravidla je deklarace funkci, protoze fun-
kce tridy AUTO nemohou existovat /jazyk C neni schopen kompi-
lace vykonatelneho kodu do zasobniku/
- jestlize typ identifikatoru je "function returning ..." je
implicitne deklarovan jako EXTERN
Pokud se ve vyrazech vyskytne identifikator, ktery neni jeste
deklarovan a je nasledovan levou zavorkou, je kontextove dekla-
rovan jako "function returning int".
14. Dalsi informace o typech
----------------------------
V teto casti budou shrnuty operace, ktere je mozne uskutec-
novat na objektech ruznych typu.
14.1 Struktury a uniony
-----------------------
Jsou dovoleny pouze dve operace, ktere je mozne uskutecnit
s objekty, ktere maji typ struktury:
- pristup k jedne z jeho polozek pomoci operatoru ".".
- ziskani adresy dane struktury pomoci operatoru "&".
Jine operace jako je napriklad prirazeni nebo pouziti jako
argument funkce zpusobi chybovou zpravu. Je mozne ocekavat,
ze pri budoucich rozsirenich jazyka budou takove operace nebo
nektere z nich dovoleny.
V odstavci c. 7 je definovano, ze pri primem nebo neprimem
pristupu do struktur / pomoci operatoru "." nebo "->"/ musi
byt clen na prave strane polozkou struktury a clen na strane
leve bud nazev struktury nebo ukazovatko na takovou strukturu.
Aby bylo mozne zmenit vyse uvedene pravidlo, neni prekladacem
provadena dusledna kontrola tohoto pravidla. Ve skutecnosti
muze byt na leve strane od operatoru "." libovolna lhodnota,
ktera ma tvar struktury a na prave strane nazev polozky teto
struktury. Take na leve strane od operatoru "->" muze byt uve-
den libovolny vyraz, ktery ma tvar ukazovatka nebo celeho cis-
la. Jestlize se jedna o ukazovatko, predpoklada se, ze ukazuje
na strukturu a nazev jedne polozky teto struktury je uveden na
prave strane operatoru. Jestlize je na leve strane uveden vyraz
typu int, je chapan jako absolutni adresa v pameti pocitace,
kde je dana struktura umistena.
Je nutne poznamenat, ze takove konstrukce mohou byt
neprenositelne.
14.2 Funkce
-----------
Jsou dovoleny pouze dve operace, ktere je mozne uskutecnit
s objekty charakteru funkce:
- vyvolani funkce
- ziskani adresy vstupniho bodu funkce
Jestlize se nazev funkce vyskytne ve vyrazu, ale nikoliv ve
tvaru vyvolani teto funkce, je chapan jako ukazovatko na tuto
funkci. Je tedy mozne pouzit nazev funkce jako argument pri
vyvolani jine funkce, napriklad:
INT f();
...
g(f);
Funkce g muze byt definovana nasledovne:
g(funcp)
INT(*funcp)();
{
...
(*funcp)();
...
}
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.
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-
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
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.
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;