Transcript
Page 1: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

1

PROGRAMIRANJE C JEZIKOM Nastavni materijal za studente FESB-a. Split, 2005/2006 Autor: Ivo Mateljan

Page 2: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

2

Sadržaj 1 Uvod........................................................................................................................................... 5 2 Matematički i elektronički temelji računarstva ........................................................................ 13

2.1 Izjavna i digitalna logika ................................................................................................... 13 2.2 Brojevni sustavi i računska sposobnost računala .............................................................. 16

3 Izrada prvog C programa.......................................................................................................... 20 3.1 Strojni, asemblerski i viši programski jezici ..................................................................... 20 3.2 Prvi program u C jeziku .................................................................................................... 21 3.3 Struktura i kompiliranje C programa ................................................................................ 25 3.4 Integrirana razvojna okolina (IDE) ................................................................................... 27 3.5 Usmjeravanje procesa kompiliranja programom nmake................................................... 32

4 Kodiranje i tipovi podataka ...................................................................................................... 33 4.1 Kodiranje i zapis podataka ................................................................................................ 33 4.2 Memorija ........................................................................................................................... 40 4.3 Prosti tipovi podataka........................................................................................................ 42 4.4 Direktiva #define............................................................................................................... 46 4.5 Specifikatori printf funkcije .............................................................................................. 47 4.6 Pristup podacima pomoću pokazivača .............................................................................. 49 4.7 Unos podataka u memoriju računala ................................................................................. 52 4.8 Inicijalizacija varijabli....................................................................................................... 54

5 Uvod u programiranje C jezikom............................................................................................. 55 5.1 Postupak izrade programa ................................................................................................. 55 5.2 Algoritamska struktura C programa? ............................................................................... 57 5.3 Funkcije C jezika............................................................................................................... 63 5.4 Zaključak........................................................................................................................... 70

6 Izrazi i sintaksa C jezika........................................................................................................... 71 6.1 Izrazi.................................................................................................................................. 71 6.2 Automatska i explicitna pretvorba tipova ......................................................................... 78 6.3 Definiranje sinonima tipa pomoću typedef ....................................................................... 81 6.4 Formalni zapis sintakse C-jezika....................................................................................... 81

7 Proste i strukturalne naredbe C jezika...................................................................................... 87 7.1 Proste naredbe ................................................................................................................... 87 7.2 Strukturalne naredbe ......................................................................................................... 89

8 Nizovi..................................................................................................................................... 102 8.1 Jednodimenzionalni nizovi.............................................................................................. 102 8.2 Prijenos nizova u funkciju............................................................................................... 107 8.3 Višedimenzionalni nizovi................................................................................................ 110

9 Blokovi, moduli i dekompozicija programa........................................................................... 112 9.1 Blokovska struktura programa ........................................................................................ 112 9.2 Funkcionalna dekompozicija programa "od vrha prema dolje" ...................................... 120 9.3 Zaključak......................................................................................................................... 128

10 Rad s pokazivačima.............................................................................................................. 129

Page 3: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

3

10.1 Tip pokazivača .............................................................................................................. 129 10.2 Operacije s pokazivačima.............................................................................................. 130 10.3 Pokazivači kao argumenti funkcije ............................................................................... 131 10.4 Pokazivači i nizovi ........................................................................................................ 132 10.5 Pokazivači i argumenti funkcije tipa niza ..................................................................... 134 10.6 Patrametri funkcije tipa void pokazivača ...................................................................... 136 10.7 Pokazivači na funkcije .................................................................................................. 137 10.8 Kompleksnost deklaracija ............................................................................................. 139 10.9 Polimorfne funkcije....................................................................................................... 141 10.10 Zaključak..................................................................................................................... 144

11 Nizovi znakova - string ........................................................................................................ 146 11.1 Definicija stringa ........................................................................................................... 146 11.2 Standardne funkcije za rad sa stringovima.................................................................... 148 11.3 Ulazno-izlazne operacije sa stringovima....................................................................... 151 11.4 Korisnički definirane ulazne operacije sa stringovima ................................................. 152 11.5 Pretvorba stringa u numeričku vrijednost ..................................................................... 153 11.6 Nizovi stringova ............................................................................................................ 155 11.7 Generator slučajnih brojeva .......................................................................................... 157 11.8 Argumenti komandne linije operativnog sustava.......................................................... 158

12 Dinamičko alociranje memorije ........................................................................................... 160 12.1 Funkcije za dinamičko alociranje memorije ................................................................. 160 12.2 Kako se vrši alociranje memorije.................................................................................. 163 12.3 Alociranje višedimenzionalnih nizova .......................................................................... 165 12.4 Standardne funkcije za brzi pristup memoriji ............................................................... 171

13 Korisnički definirane strukture podataka ............................................................................. 172 13.1 Struktura (struct) ...................................................................................................... 172 13.2 Union – zajednički memorijski objekt za različite tipova podataka.............................. 180 13.3 Bit-polja......................................................................................................................... 181 13.4 Pobrojanji tip (enum).................................................................................................... 182 13.5 Strukture i funkcije za očitanje vremena....................................................................... 183

14 Leksički pretprocesor ........................................................................................................... 188 14.1 Direktiva #include ......................................................................................................... 188 14.2 Direktiva #define za makro-supstitucije........................................................................ 188 14.3 String operatori # i ##.................................................................................................. 190 14.4 Direktiva #undef............................................................................................................ 191 14.5 Direktive za uvjetno kompiliranje................................................................................. 192

15 Rad s datotekama i tokovima ............................................................................................... 194 15.1 Ulazno-izlazni tokovi .................................................................................................... 194 15.2 Binarne i tekstualne datoteke ........................................................................................ 195 15.3 Pristup datotekama ........................................................................................................ 195 15.4 Formatirano pisanje podataka u datoteku...................................................................... 197 15.5 Formatirano čitanje podataka iz datoteke...................................................................... 199 15.6 Znakovni ulaz/izlaz ....................................................................................................... 200 15.7 Direktni ulaz/izlaz za memorijske objekte .................................................................... 203 15.8 Sekvencijani i proizvoljni pristup datotekama .............................................................. 206 15.9 Funkcije za održavanje datoteka ................................................................................... 208

16 Apstraktni tipovi podataka - ADT........................................................................................ 210

Page 4: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

4

16.1 Koncept apstraktnog dinamičkog tipa podataka ........................................................... 210 16.2 Stog i STACK ADT ...................................................................................................... 215 16.3 Primjena stoga za proračun izraza postfiksne notacije.................................................. 218 16.4 Red i QUEUE ADT....................................................................................................... 221 16.5 Zaključak....................................................................................................................... 224

17 Rekurzija i složenost algoritama .......................................................................................... 225 17.1 Rekurzivne funkcije ...................................................................................................... 225 17.2 Matematička indukcija .................................................................................................. 227 17.3 Kule Hanoja .................................................................................................................. 227 17.4 Metoda - podijeli pa vladaj (Divide and Conquer)........................................................ 230 17.5 Pretvorba rekurzije u iteraciju ....................................................................................... 232 17.6 Standardna bsearch() funkcija ....................................................................................... 234 17.7 Složenost algoritama - "Veliki - O" notacija................................................................. 236 17.8 Sortiranje ....................................................................................................................... 239 17.9 Zaključak....................................................................................................................... 248

18 Samoreferentne strukture i liste............................................................................................ 249 18.1 Samoreferentne strukture i lista..................................................................................... 249 18.2 Operacije s vezanom listom .......................................................................................... 250 18.3 Što može biti element liste ............................................................................................ 259 18.4 Lista sa sortiranim redoslijedom elemenata .................................................................. 260 18.5 Implementacija ADT STACK pomoću linearne liste ................................................... 265 18.6 Implementacija ADT QUEUE pomoću vezane liste..................................................... 267 18.7 Dvostruko vezana lista .................................................................................................. 269 18.8 Generički dvostrani red - ADT DEQUEUE.................................................................. 271 18.9 Zaključak....................................................................................................................... 280

19 Razgranate strukture - stabla ................................................................................................ 281 19.1 Definicija stabla ............................................................................................................ 281 19.2 Binarno stablo ............................................................................................................... 282 19.3 Interpreter prefiksnih izraza .......................................................................................... 291 19.4 Stabla s proizvoljnim brojem grana .............................................................................. 305 19.5 Prioritetni redovi i hrpe ................................................................................................. 309 19.6 Zaključak....................................................................................................................... 316

20 Strukture za brzo traženje podataka .................................................................................... 317 20.1 Tablice simbola i rječnici .............................................................................................. 317 20.2 Hash tablica ................................................................................................................... 318 20.3 BST - binarno stablo traženja........................................................................................ 333 20.4 Crveno-crna stabla......................................................................................................... 344

Literatura ................................................................................................................................... 354 Dodatak ..................................................................................................................................... 355

Dodatak A - Elementi dijagrama toka................................................................................... 355 Dodatak B - Gramatika C jezika ........................................................................................... 356 Dodatak C - Standardna biblioteka C jezika ......................................................................... 361

Index.......................................................................................................................................... 392

Page 5: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

5

1 Uvod

Naglasci:

• Što je računalo ? • Što je program ? • Kako se rješavaju problemi pomoću računala? • Računarski procesi i memorijski objekti • Apstrakcija, algoritam, program

Računalo ili kompjuter (eng. computer) je naziv za uređaje koji obavljaju radnje prema programima koje izrađuje čovjek. Sastavni dijelovi računala nazivaju se hardver, a programi i njihova dokumentacija nazivaju se softver. Prvotno su računala služila za obavljanje numeričkih proračuna, odatle i potječe naziv računalo. Danas računala služe za obradu različitih problema.

Korisnike računala zanima kako se koristi računalo, a one koji izučavaju računala zanima:

• kako se izrađuje računalo, • kako se izrađuje program i • kako se rješavaju problemi pomoću računala.

Ovdje će biti pokazano kako se izrađuju programi i kako se programiranjem rješavaju različiti problemi. Bit će opisana i unutarnja građa računala. Za pisanje programa koristit će se programski jeziku C i asemblerski jezik.

Što je program? Program je zapis operacija koje računalo treba obaviti. Taj zapis može biti u obliku

izvršnog programa ili u obliku izvornog programa. Izvršni program sadrži kôd operacija koje izvršava stroj računala, pa se naziva i strojni program. Izvorni program se zapisuje simboličkim jezikom koji se naziva programski jezik. Prevođenje izvornog programa u strojni program vrši se pomoću programa koji se nazivaju kompilatori (ili kompajleri).

Stroj računala Postoje dva tipa elektroničkih računala: analogna i digitalna. Analognim računalima se

obrađuju kontinuirani elektronički signali. Digitalnim računalom se obrađuju, prenose i pamte diskretni elektronički signali koji u jednom trenutku mogu imati samo jedno od dva moguća stanja. Ta stanja se označavaju znamenkama 0 i 1, odatle i naziv digitalna računala (eng. digit znači znamenka). Programere i korisnike ne zanimaju elektronički signali u računalu, već poruka koju oni prenose – digitalna informacija.

Brojevni sustav, u kojem postoje samo dvije znamenke, naziva se binarni brojevni sustav. U tom se sustavu može kodirati različite informacije koristeći više binarnih znamenki. Znamenka binarnog brojevnog sustava se naziva bit (kratica od eng. binary digit), a može imati samo dvije vrijednosti 0 ili 1. Niz od više bitova predstavlja kodiranu informaciju koja može

Page 6: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

6

predstavljati operaciju koju računalo treba izvršiti ili neki smisleni podatak. Uobičajeno je za nizove bitova koristiti nazive iz Tablice 1.1.

U binarnom nizu često se označava redoslijed bitova. Kratica LSB označava bit najmanjeg značaja (eng. least significant bit), a MSB označava bit najvećeg značaja (eng. most significant bit). Primjer je dan na slici 1.1.

Bit je naziv za binarnu znamenku Nibl je naziv za skupinu od četiri bita (eng. nibble) s kojom se operira kao s

cjelinom. Bajt ili oktet je naziv za skupinu od osam bita (eng. byte) s kojom se operira kao s

cjelinom. Riječ je naziv za skupinu od više bajta (eng. word) s kojom se operira kao s cjelinom.

Kod mikro računala za riječ se uzima skupina od 2 bajta. Kod većih računala za riječ se uzima skupina od 4 ili 8 bajta.

Tablica 1.1 Nazivi temeljnih binarnih nizova

MSB LSB značaj bitova 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 položaj bita 1 0 0 0 1 1 1 1 1 0 1 0 1 1 0 1 binarni niz nibl 3 nibl 2 nibl 1 nibl 0 niz nibla bajt 1 bajt 0 niz bajta Riječ riječ

Slika 1.1 Označavanje binarnog niza

Za označavanje većih nizova koriste se prefiksi: k (kilo) ⇔ × 1024 M (mega) ⇔ k × 1024 G (giga) ⇔ M × 1024 T (tera) ⇔ G × 1024

Primjerice, 2 kB (kilobajta) = 2048 bajta, 3 Mb (megabita) = 3145728 bita.

Digitalno računalo može pamtiti i izvršavati programe, te dobavljati, pamtiti i prikazivati

različite informacije. Te informacije, koje su na prikladan način pohranjene u računalu, su programski podaci.

broj bita – n broj kombinacija – 2n 2 3

4 8 16 32

4 8

16 256

65536 4294967296

Tablica 1.2 Broj kombinacija s n bita

Često se računala klasificiraju kao 8-bitna, 16-bitna, 32-bitna ili 64-bitna. Pod time se podrazumijeva da n-bitno računalo može operirati s nizom od n bita kao s cjelinom. Broj bita koji se koristi za opisivanje nekog podatka ovisi o veličini skupa kojem taj podatak pripada.

Page 7: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

7

Razmotrimo skup podataka kiji se kodira s tri bita. Taj skup može imati maksimalno 8 elemenata jer se s tri bita može kodirati maksimalno osam kombinacija: 000, 001, 010, 011, 100, 101, 110, 111. Lako je pokazati da se s n-bita može kodirati podatke iz skupa od maksimalno 2n elemenata. Tablica 1.2 pokazuje da se udvostručenjem broja bitova značajno povećava skup vrijednosti koje se mogu kodirati u računalu.

Operacije se u računala nikada ne izvršavaju samo s jednim bitom, već se istovremeno prenosi i obrađuje više bita. Kod svih računala usvojeno je da najmanja jedinica digitalne informacije, koja se kao cjelina prenosi i pamti u računalu, sadrži 8 bita, tj. jedan bajt.

Na slici 1.2 prikazani su sastavni dijelovi digitalnog računala. Centralna procesorska jedinica (CPU – central processing unit) - kontrolira izvršenje programa i aritmetičko-logičkih operacija. CPU je kod mikro i mini računala izveden kao jedinstveni integrirani elektronički sklop (čip) i naziva se mikroprocesor. Uobičajeno je koristiti naziv procesor, bilo da se radi o mikroprocesoru ili o skupini čipova koji obavljaju funkcije CPU, a programe koji se izvršavaju u računalu naziva se procesima.

Slika 1.2. Opći prikaz digitalnog računala

Radna memorija – pamti digitalne informacije za vrijeme dok je računalo u operativnom stanju. U memoriji se nalazi programski kôd i podaci s kojima operira procesor na temelju naredbi sadržanih u programskom kôdu. Memorija je napravljena od poluvodičkih elemenata u koje procesor može upisati i iz kojih može čitati digitalne informacije. Ta memorija se naziva RAM (eng. random access memory). Sa programerskog stajališta RAM predstavlja linearno uređen prostor u kojem se istovremeno može pristupiti grupi od 8 bita digitalne informacije (1 bajt). Položaj ove temeljne memorijske ćelije se označava prirodnim brojem i naziva se adresa. Jedan manji dio memorije je napravljen od poluvodičkih elemenata koji mogu trajno pamtiti digitalnu informaciju, a naziva se ROM (eng. read-only memory). U ROM-u je upisan program koji služi pokretanju osnovnih funkcija računala. U samom procesoru ugrađeno je nekoliko manjih memorijskih jedinica koje se nazivaju registri. Registri služe za privremeni smještaj programskog kôda i podataka iz radne memorije, te rezultata aritmetičko-logičkih operacije koje se izvršavaju u samom procesoru. Broj bita koji može biti pohranjen u jednom registru naziva se riječ procesora. Kod većine današnjih PC računala riječ procesora sadrži 32 bita (4 bajta), pa se kaže da su to 32-bitna računala.

Vanjska memorija - služi za trajnu pohranu podataka. U tu svrhu koriste se magnetski i optički mediji (tvrdi disk, savitljive diskete, magnetske trake, optički diskovi,..). Podaci se na njima pohranjuju u organiziranom i imenovanom skupu podataka koji se nazivaju datoteka.

Ulazne jedinice - služe za unos podataka (tipkovnica, miš, svjetlosna olovka, mikrofon,..). Standardna ulazna jedinica je tipkovnica.

Izlazne jedinice - služe za prikaz informacija korisniku računala (video-monitor, pisač, zvučnik,...). Standardna izlazna jedinica je video-monitor.

Page 8: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

8

Računalo u operativnom stanju održava poseban program koji se naziva operativni sustav. On vrši temeljne funkcija računala: inicijalizaciju računala i priključenih vanjskih jedinica pri uključenju električnog napajanja, kontrolu i redoslijed izvođenja programa, kontrolu korištenja memorije, pohranjivanje i obradu podataka, vremensku raspodjelu funkcija računala, itd. Operativni sustav nije nužno jedinstven program, već se sastoji od više programskih cjelina. On se, jednim dijelom, trajno nalazi u ROM memoriji računala. Programi s novakvim svojstvom nazivaju se rezidentni programi. Svi ostali programi moraju se prije izvršenja upisati u memoriju računala.

Može se izvršiti funkcionalna podjela softvera na sistemski i aplikativni softver. U sistemski softver spadaju programi operativnog sustava, razni jezični procesori (interpreteri, kompilatori, emulatori itd.), programi za testiranje programa (debugger), servisni i uslužni programi, te razni pomoćni programi (matematički, statistički, baze podataka i uređivači teksta). Aplikativni softver predstavljaju različiti korisnički programi.

Kako se rješavaju problemi pomoću računala? Kada se rješava neki problem, do ideje za rješenje dolazi se analizom problema. Čovjeku je

često dovoljno da već iz idejnog rješenja, koristeći svoju inteligenciju i predznanje, brzo dođe do potpunog rješenja problema. Računalo, samo po sebi, ne raspolaže s inteligencijom, već jedino može izvršavati određen broj jednostavnih operacija. Zbog toga, upute za rješenje problema pomoću računala moraju biti zapisane u obliku preciznog algoritma.

Računarski algoritam je precizni opis postupka za rješenje nekog problema u konačnom broju koraka i u konačnom vremenskom intervalu. Pravila kako se piše algoritam nisu strogo određena. Algoritam se može definirati običnim govornim jezikom, tablicama i matematičkim formulama koje opisuju problem, te usmjerenim grafovima koji opisuju tok izvršenja programa.

Primjer: Algoritam kojim se u pet koraka opisuje postupak zamjene točka na automobilu glasi:

1. ispitaj ispravnost rezervnog točka, 2. podigni auto, 3. skini točak, 4. postavi rezervni točak, 5. spusti auto.

Ovaj algoritam je jasan svakome tko je bar jednom mijenjao točak, međutim, računalo je izvršitelj kojem upute, iskazane nizom naredbi, nisu dovoljno jasne, jer ono ne zna (1) gdje se nalazi rezervni točak, (2) kako se provjerava njegova ispravnost, (3) kako i čime podignuti auto, te (4) kojim alatom se skida i postavlja točak. Zbog toga se algoritam dorađuje preciziranjem pojedinog koraka algoritma. Primjerice, u prvom koraku treba predvidjeti sljedeće naredbe:

1. ispitaj ispravnost rezervnog točka, 1.1. otvori prtljažnik 1.2. izvadi rezervni točak 1.3. uzmi mjerač tlaka iz kutije s alatom 1.4. izmjeri razinu tlaka 1.5. dok je razina tlaka manja 1,6 ponavljaj pumpaj gumu 15 sekundi

izmjeri razinu tlaka

Podrazumijeva se da je naredba označena s 1. zamijenjena s nizom naredbi koje su

označene s 1.1, 1.2,..1.5. Naredbe iskazane u koracima 1.1 do 1.4 su same po sebi jasne. Korak 1.5 treba dodatno pojasniti. Njime je opisan postupak pumpanja gume do neke razine tlaka. Pošto nitko ne može unaprijed znati koliko vremena treba pumpati gumu, da bi se postigla željena razina tlaka, predviđeno je da se dvije naredbe: "pumpaj gumu 15 sekundi" i "izmjeri razinu tlaka", višekratno ponavljaju, sve dok je razina tlaka manja od 1,6. Obje ove naredbe su

Page 9: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

9

zapisane uvlačenjem reda kako bi se točno znalo koje naredbe treba ponavljati. Ovaj se tip naredbe naziva iteracija ili petlja. Uobičajeno se kaže da petlja ima zaglavlje, u kojem se ispituje uvjet ponavljanja petlje (dok je razina tlaka manja od 1,6 ponavljaj), i tijelo petlje, koje obuhvaća jednu ili više naredbi koje treba ponavljati. Naziv petlja podsjeća na činjenicu da se uvijek nakon izvršenja posljednje naredbe tijela petlje proces vraća na izvršenje prve naredbe, ali samo u slučaju ako je zadovoljen uvjet iskazan u zaglavlju petlje. Naredbe petlje nisu posebno numerirane jer su one povezane uz zaglavlje petlje, a izvršavaju se u kao jedinstvena složena naredba. Uobičajeno se niz naredbi koji predstavljaju jedinstvenu složenu naredbu naziva i blok naredbi ili samo blok.

Uvjet ponavljanja petlje je izjava: "razina tlaka manja od 1,6". Odgovor na ovu izjavu može biti "Da" ili "Ne", ovisno o trenutno izmjerenoj razini tlaka. Ako je odgovor "Da", kažemo da je ispunjen uvjet ponavljanja petlje. Računarska se znanost koristi znanjima matematičke logike. U tom kontekstu ova izjava predstavlja tzv. predikatni izraz koji može imati samo dvije logičke vrijednosti: "istina" ili "laž", pa se kaže da je uvjet održanja petlje ispunjen ako je predikatni izraz istinit. Matematička logika je zapravo znanstveni temelj cijele računarske znanosti i o njoj će biti više govora u sljedećem poglavlju.

Pokušajte dalje sami precizirati korake 2, 3 , 4 i 5. Ali pazite, kad pomislite da je problem ispravno riješen, moguće je da se opet potkrade neka greška. To se obično događa kada se ne predvide sve moguće situacije, odnosno stanja u koja se može doći. Primjerice, gornji algoritam nije predvidio slučaj da je guma probušena. Kakav bi razvoj događaja tada bio, ako bi se dosljedno poštovao postupak iz koraka 1.5? Pošto je kod probušene gume razina tlaka uvijek manja od 1,6, ispada da bi tada izvršitelj naredbi ponavljao postupak pumpanja gume beskonačan broj puta. Algoritam se može popraviti tako da korak 1.5 glasi:

1.5. ako je tlak manji od 0.1 tada ako je guma probušena onda

odnesi točak na popravak inače dok je tlak manji od 1.6 ponavljaj

pumpaj gumu 15 sekundi izmjeri razinu tlaka

U ovom se zapisu koriste tzv. naredbe selekcije, prema sljedećoj logici izvršenja:

ako je ispunjen uvjet tada izvrši prvi niz naredbi

inače izvrši alternativni niz naredbi Ovaj se tip naredbe zove uvjetna selekcija ili grananje, jer se nakon ispitivanja logičkog uvjeta vrši selekcija jednog od dva moguća niza naredbi, odnosno program se grana u dva smjera. Specijalni oblik selekcije je uvjetna naredba tipa:

ako je ispunjen uvjet tada izvrši naredbu

Njome se određuje izvršenje neke naredbe samo ako je ispunjen neki uvjet. Koristeći naredbe selekcije, algoritam se može zapisati u obliku:

1. ispitaj ispravnost rezervnog točka, 1.1 otvori prtljažnik 1.1.1. uzmi najmanji od tri ključa

1.1.2. gurni ključ u bravu i lagano ga okreni na desno 1.1.3. podigni vrata prtljažnika

Page 10: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

10

1.2. izvadi rezervni točak 1.2.1. podigni tapetu 1.2.2.ako je točak pričvršćen vijkom onda odvij vijak 1.2 .2. izvadi točak

1.3. uzmi kutiju s alatom 1.4. ispitaj razinu tlaka

1.4.1. izvadi mjerač tlaka iz kutije alata 1.4.2. postavi ga na zračnicu točka

1.4.3. očitaj razinu tlaka 1.5. ako je tlak manji od 0,1 onda

1.5.1. provjeri da li je guma probušena 1.5.2. ako je guma probušena onda

odnesi točak na popravak inače, ako je tlak manji od 1,6 onda

1.5.3. otvori prednji poklopac motora 1.5.4. uzmi zračnu pumpu 1.5.5. dok je tlak < 1,6 ponavljaj

postavi crijevo pumpe na zračnicu dvadeset puta pritisni pumpu na zračnicu postavi mjerač tlaka ispitaj razinu tlaka Očito da je potrebno dosta raditi i dosta razmišljati da bi se napisao kvalitetan algoritam.

Nakon što je napisan precizan algoritam rješenja problema, pristupa se pisanju izvornog programa. Kako se to radi bit će objašnjeno u sljedećim poglavljima. Važno je uočiti da su u zapisu algoritma korištena četiri tipa iskaza:

1. proste ili primitivne naredbe – iskazi koji označavaju jednu operaciju 2. blok naredbi – iskazi koji opisuju niz naredbi koje se sekvencijalno izvršavaju jedna za

drugom, a tretiramo ih kao jedinstvenu složenu operaciju. 3. naredbe selekcije – iskazi kojima se logički uvjetuje izvršenje bloka naredbi. 4. iterativne naredbe ili petlje – iskazi kojima se logički kontrolira ponovljeno izvršenje

bloka naredbi.

Računarski procesi i memorijski objekti Svaki proces rezultira promjenom stanja ili atributa objekata na koje procesi djeluju. Uobičajeno se stanje nekog promjenljivog objekta označava kao varijabla koja ima neko ime. U računalu se stanje objekta pamti u memoriji računala pa se algoritamske varijable mora tretirati kao memorijske objekte. Kada se u C jeziku napiše iskaz

x = 5;

on predstavlja naredbu da se memorijskom objektu, imena x, pridijeli vrijednost 5. Ako se pak napiše iskaz:

x = 2*x +5;

on predstavlja proces u kojem se najprije iz memorije očitava vrijednost memorijskog objekta x zapisanog na desnoj strani znaka =. Zatim se ta vrijednost množi s 2 i pribraja joj se numerička vrijednost konstante 5. Time je dobivena numerička vrijednost izraza s desne strane znaka =. Ta se vrijednost zatim pridjeljuje memorijskom objektu s lijeve strane znaka =. Konačni je rezultat ovog procesa da je varijabli x pridijeljena vrijednost 15. Ako bi prethodni iskaz tretirali kao matematički iskaz, on bi predstavljao jednadžbu s jednom varijablom, koja uvjetuje da je vrijednost varijable x jednaka 5.

Page 11: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

11

Znak = u C jeziku ne predstavlja znak jednakosti, kao u matematici već operator pridjele vrijednosti. Njegova upotreba označava naredbu da se vrijednost memorijskog objekta s lijeve strane znaka = postavi na vrijednost izraza koji je zapisan s desne strane znaka =. Takove naredbe se zovu naredbe pridjele vrijednosti. Zbog ove nekonzistentnosti upotrebe znaka = u matematici u odnosu na upotrebu u nekim programskim jezicima (C, Basic, Fortan, Java) često se u općim algoritamskim zapisima operator pridjele vrijednosti zapisuje znakom ←, primjerice:

x ← 5 x ← 2*x +5

Operacija pridjele vrijednosti posljedica je načina kako procesor obrađuje podatke u računalu. Naime, procesor može vršiti operacije samo nad podacima koji se nalaze u registrima procesora, pa je prije svake operacije s memorijskim objektima prethodno potrebno njihov sadržaj (vrijednost) prenijeti u registre procesora, a nakon obavljene operacije se sadržaj iz registra, koji sadrži rezultat operacije, prebacuje u memorijski objekt označen s lijeve strane operatora pridjele vrijednosti. Kaže se da procesor funkcionira po principu: dobavi-izvrši-spremi (eng. fetch-execute-store).

Što je to apstrakcija? Netko može primijetiti da je opisani proces zamjene točka loš primjer primjene računala.

To je točno, jer ako bi se napravio robot, koji bi obavljao navedenu funkciju, onda bi to bila vrlo neefikasna i skupa upotreba računala. Međutim, malo iskusniji programer bi prema gornjem algoritmu mogao lako napraviti program kojim se animirano simulira proces zamjene točka. To je moguće jer, iako je prethodni algoritam apstraktan, on specificira procese u obliku koji se može ostvariti računarskim programom.

Apstrakcija je temeljna mentalna aktivnost programiranja. U računarskoj se terminologiji pod pojmom apstrakcije podrazumijeva prikladan način zapisa o objektima i procesima koje se obrađuje pomoću računala, a da se pri tome ne vodi računa o tome kako je izvršena stvarna računarska implementacija, niti objekta niti procesa. Važna je samo ona pojavnost koja je određena apstrakcijom. Algoritam zapisan programskim jezikom predstavlja apstrakciju strojnog koda, a algoritam zapisan prirodnim jezikom predstavlja apstrakciju programskog jezika.

Programski jezik služi da se formalnim jezikom zapiše procese i stanje memorijskih objekata u računalu, pa on predstavlja apstrakciju računarskih procesa i stanja memorije. Pomoću programskih jezika se piše program koji ponovo predstavlja neku novu apstrakciju, a u toku izvršenja programa moguće je daljnje usložnjavanje apstrakcije. Primjerice, korisnik CAD programa pokretima miša zadaje program za crtanje nekog geometrijskog oblika.

S obzirom na način kako je izvršena apstrakcija računarskog procesa, može se izvršiti sljedeća klasifikacija programskih jezika:

1. Imperativni (proceduralni) programski jezici (C, Pascal, Modula-2, Basic, Fortran,..) 2. Objektno orijentirani programski jezici (C++, Java, C#, Eiffel, Objective C, Smaltalk,

Modula-3, ..) 3. Funkcionalni programski jezici (Lisp, Sheme, ML, Haskel..) 4. Logički programski jezici (Prolog) 5. Jezici specijalne namjene: pretraživanje baza podataka (SQL), vizuelno programiranje

(Delphi, Visual Basic), uređivanje teksta (Perl, TeX, HTML), matematički proračuni (Matlab).

Imperativni programski jezici koriste iskaze koji su bliski naredbama procesora (to su

naredbe pridjele vrijednosti, aritmetičko-logičke operacije, uvjetni i bezuvjetni skokovi te poziv

Page 12: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

12

potprograma). Kod objektno orijentiranih jezika naglasak je na tome da varijable predstavljaju atribute nekog objekta, a funkcije predstavljaju metode pomoću kojih objekt komunicira s drugim objektima. Specifikacije atributa i metoda određuju klase objekata. Kod funkcionalnih se jezika ne koristi temeljna imperativna naredba pridjele vrijednosti, već se sva međudjelovanja u programu opisuju funkcijama. Teorijska podloga ovih jezika je u tzv. λ-računu. Kod logičkih programskih jezika međudjelovanja se u programu opisuju predikatnim logičkim izrazima i funkcijama. Naglasak je na zapisu onoga “što program treba izvršiti”, za razliku od imperativnih jezika pomoću kojih se zapisuje “kako nešto izvršiti”.

Apstrakcija je dakle, temeljna mentalna aktivnost programera. Ona je moguća samo ako se dobro poznaje programski jezik i programske algoritme za efikasno korištenje računarskih resursa.

O tome će biti riječi u sljedećim poglavljima.

Page 13: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

13

2 Matematički i elektronički temelji računarstva

Naglasci: • Izjavna logika • Logičke funkcije i predikati • Booleova logika • Temeljni digitalni sklopovi • Brojevni sustavi

2.1 Izjavna i digitalna logika Bit će navedeni osnovni pojmovi potrebni za razumijevanje izjavne logike (ili propozicijske

logike), koji se intenzivno koristi u programiranju, i digitalne logike koja je temelj izgradnje digitalnog računala.

Osnovni objekt kojeg proučava izjavna logika je elementarna izjava. Ona može imati samo jedno svojstvo - njome se izriče

"laž" ili "istina". Primjerice, izjava "osam je veće od sedam" je istinita, a izjava "broj sto je djeljiv sa

sedam" je laž. Pri označavanju izjava koristit će se slovo T (true) za istinitu izjavu i F (false) za lažnu izjavu.

Rečenica "broj x je veći od broja y" ne predstavlja izjavu jer njena istinitost ovisi o veličini brojeva x i y. Ako se umjesto x i y uvrste brojevi dobije se izjava. Ovakve rečenice se nazivaju izjavne funkcije, a za x i y se kaže da su (predmetne) varijable. Odnos među varijablama, kojeg izjavna funkcija izriče, naziva se predikat. Označi li se u prethodnom primjeru predikat " ... je veći od.... " sa P, navedena izjavna funkcija se može zapisati u obliku P(x,y).

Izjavne funkcije se prevode u izjave kada se uvrsti vrijednost predmetnih varijabli ili ako se uz izjavne funkcije primijene neodređene zamjenice svaki (oznaka ∀ koja se naziva univerzalni kvantifikator) ili neki (oznaka ∃ koja se naziva egzistencijalni kvantifikator). ∃x se čita i "postoji x". Primjerice, prethodna izjavna funkcija primjenom kvantifikatora u predikatnom izrazu (∀y)(∃x)P(x,y) postaje izjava koja znači: "za svaki broj y postoji broj x takav da je x veći od y".

Rezultat izjavne funkcije je logička vrijednost T ili F. Varijable koje sadrže logičku vrijednost nazivaju se logičke varijable.

U programiranju se često koriste izjavne funkcije iskazane tzv. relacijskim izrazima primjerice

a ← (x<z) označava da se logičkoj varijabli a pridijeli logička vrijednost određena izjavnom funkcijom (x<z). Kada je x manje od z logička varijabla poprima logičku vrijednost T inače je F. Standardno se koriste relacijski operatori: < (veće), > (manje), ≠ (različito ili nije jednako), ≥ (veće ili jednako), ≤ (manje ili jednako).

Page 14: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

14

Složene logičke izjave nastaju korištenjem sljedećih logičkih operacija: Konjunkcija, a & b, (ili a ∧ b) dviju izjava a i b je je složena izjava, nastala povezivanjem

izjava a i b veznikom i za kojeg se upotrebljava simbol ∧ ili &. Složena izjava je istinita samo ako su obje izjave istinite. Izjava a & b čita se "a i b".

Disjunkcija, a ∨ b, je složena izjava, koja je lažna onda i samo onda kada su obje izjave lažne; a ∨ b čita se "a ili b".

Implikacija, a ⇒ b, je složena izjava koja je lažna onda i samo onda ako je a istinito i b lažno; čita se " a povlači b" ili " a implicira b". Za izjavu b ⇒ a kaže se da je obrat izjave a ⇒ b. Vrijedi i sljedeće tumačenje implikacije: ako je izjava a ⇒ b istinita onda je a dovoljan uvjet za b, ili b je nuždan uvjet za a.

Ekvivalencija, a ⇔ b, je složena izjava koja je istinita onda i samo onda kada su obje izjave istinite, ili kada su obje lažne: čita se " a je ekvivalentno sa b".

Negacija, ¬a, je izjava koja je istinita onda i samo onda kada je izjava a lažna.

Simboli: ¬, &, ∨, ⇔ i ⇒ su logički operatori. Njihovo djelovanje na logičke varijable a i b

je prikazano tzv. tablicom istinitosti (tablica 2.1).

A b ¬a a & b a ∨ b a ⇒ b a ⇔ b T T F T T T T T F F F T F F F T T F T T F F F T F F T T

Tablica 2.1. Tablica istinitosti logičkih operacija

Upotrebom logičkih operatora i uvođenjem zagrada mogu se, kao i u algebri, graditi razni

logički izrazi, primjerice

¬a ∨ (b & d) ⇒ c.

Redoslijed izvršavanja operacija je sljedeći: (1) izraz u zagradi, (2) negacija, (3) disjunkcija, (4) konjunkcija, (5) implikacija i ekvivalencija. Logički izrazi koji sadrže samo operacije negacije, konjunkcije i disjunkcije, te zagrade, određuju Booleovu algebru. Svi se logički izrazi mogu iskazati Booleovom algebrom jer se djelovanje operatora implikacije i ekvivalencije može izraziti pomoću Booleovih izraza. Vrijedi:

x ⇒ y = ¬x ∨ y x ⇔ y = ¬((¬x & y) ∨ (¬y & x))

Zadatak: Provjerite prethodne izraze tablicom istinitosti. U Booleovoj algebri vrijede slijedeće zakonitosti: 1. Zakon komutacije

x ∨ y ≡ y ∨ x x & y ≡ y & x

2. Zakon asocijacije

Page 15: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

15

x ∨ (y ∨ z) ≡ (x ∨ y) ∨ z x & (y & z) ≡ (x & y) & z

3. Zakon idempotentnosti

x ∨ x ≡ x x & x ≡ x

4. Zakon distribucije

x ∨ (y & z) ≡ (x ∨ y) & (x ∨ z) x & (y ∨ z) ≡ (x & y) ∨ (x & z)

5. De Morganov teorem

¬(x ∨ y) ≡ ¬x & ¬y ¬(x & y) ≡ ¬x ∨ ¬z

6. Zakon dvostruke negacije

¬¬x ≡ x

Booleova logika ima veliku primjenu u programiranju i posebno pri projektiranju sklopova digitalnog računala, jer se gotovo svi potrebni sklopovi digitalnog računala mogu realizirati pomoću tri temeljna elektronička sklopa: invertor, sklop-I (eng. AND gate) i sklop-ILI (eng. OR gate).

Slika 2.1. Temeljni digitalni sklopovi

Ovi se sklopovi upravljaju naponom (ili strujom) tako da reagiraju na stanje pod naponom i stanje bez napona, dakle oni raspoznaju samo dvije naponske razine: nisku i visoku. Uobičajeno se ta dva stanja označavaju s "1" i "0" umjesto s true i false. To su sklopovi kojima izlaz odgovara operacijama negacije, disjunkcije i konjunkcije ulaznih logičkih stanja "0" i "1". Funkcija ovih sklopova se može prikazati pomoću preklopki. Primjerice, rad sklopa I se može opisati strujnim krugom u kojem su serijski spojene žarulja, sklopka A i sklopka B. Žarulja će zasvijetliti kada proteče struja, a to je moguće samo ako ako su obje sklopke uključene, odnosno izlaz je 1 samo ako su varijable A i B jednake 1. Kod sklopa ILI dovoljno je uključiti jednu sklopku da bi zasvijetlila žarulja. Očito sklop I obavlja logičku funkciju konjunkcije, a sklop ILI obavlja logičku funkciju disjunkcije. U digitalnom se računalu pomoću navedenih sklopova obrađuje i prenosi mnoštvo digitalnih signala.

Pošto je uvedeno označavanje stanja digitalnog signala znamenkama 0 i 1, može se reći da se digitalnim signalom prenosi poruka o vrijednosti binarne znamenke koja u jednom trenutku

Page 16: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

16

može imati iznos nula ili jedinica. Iz tog se razloga umjesto pojma Booleova algebra ili matematička logika često koristi pojam digitalna logika.

U digitalnoj je tehnici uobičajena primjena logičkih operatora na nizove bitova. Tada se podrazumijeva da se logičke operacije provode nad bitovima jednake značajnosti. Takve logičke operacije se nazivaju bit-značajne operacije.

Primjer: bit značajnom konjunkcijom dva binarna niza A i B dobije se niz C:

7 6 5 4 3 2 1 0 bit ----------------------- 1 1 0 0 1 1 1 1 = A 0 0 0 0 0 1 0 0 = B ---------------------- A & B = 0 0 0 0 0 1 0 0 = C

U nizu C jedino bit 2 može biti jednak 1 i to samo ako je i u nizu A taj bit jednak 1. Ovo je često korišten postupak da se ispita da li je neki bit u nizu jednak 1 ili 0. Obično se niz B naziva "maska" za ispitivanje bitova u nizu A.

Pored prije navedenih Booleovih logičkih operacija u digitalnoj se tehnici često koristi bit-značajna operacija koja se naziva ekskluzivna disjunkcija ili ekskluzivno ILI. Označava se znakom ⊕ ili XOR. Ima značaj zbrajanja po modulu 2, a njeno korištenje u programiranju bit će pojašnjeno kasnije.

A XOR B = A ⊕ B = (¬A & B) ∨ (A & ¬B)

A

B

A ⊕ B 0 0 1 1

0 1 0 1

0 1 1 0

A ⊕ B = (¬A & B) ∨ (A & ¬B)

Slika 2.2 Definicijska tablica ekskluzivne disjunkcije i simbol digitalnog XOR-sklopa

2.2 Brojevni sustavi i računska sposobnost računala U programskim jezicima operacije s brojevima se najčešće zapisuju u decimalnom

brojevnom sustavu, jer je čovjek naviknut na rad s decimalnim brojevima. U računalu se pak računske operacije vrše u binarnom brojevnom sustavu.

2.2.1 Binarni brojevni sustav Sasvim općenito, numerička vrijednost broja Z, koji je u pozicionoj notaciji zapisan znamenkama: zn-1....z1z0, u brojevnom sustavu baze x, računa se prema izrazu:

( )Z z z z z xn x ii

i

n

= = ⋅−=

∑1 1 00

1

....

Decimalni brojevni sustav je definiran bazom x=10 i znamenkama zi ε{0,1,2,3,4,5,6,7,8,9}, primjerice iznos broja 765 je jednak 7⋅102 + 6⋅101 + 5⋅100 .

Page 17: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

17

Binarni brojevni sustav je definiran bazom x=2 i binarnim znamenkama zi ∈{0,1}.Primjerice, iznos binarnog broja 1011 odgovara iznosu broja 11 u decimalnom sustavu, jer je

(1011)2 = 1⋅23 + 0⋅22 + 1⋅21 +1⋅20 = 8 + 0 + 2 + 1 = (11)10.

Općenito vrijedi da se s binarnim nizom od n bita može kodirati pozitivni cijeli broj maksimalnog iznosa 2n -1, što odgovara broju različitih kombinacija binarnog niza duljine n umanjenom za jedan (i nula je broj!). Za pozitivne cijele brojeve koristi se i nazivi kardinalni brojevi i nepredznačeni cijeli brojevi.

U binarnom brojevnom sustavu se mogu izvoditi osnovne računske operacije kao i u decimalnom brojevnom sustavu. Binarno zbrajanje se obavlja kao i decimalno zbrajanje, osim što se prijenos na slijedeće značajnije mjesto ne obavlja nakon zbroja 10, već nakon 2 (1+1).

Primjer:

1 1 ← prijenos 1 0 1 = 510 1 1 1 = 710 + 0 1 0 = 210 + 1 0 1 = 510 ----------------- ---------------------- 1 1 1 = 710 1 1 0 0 = 1210

Ukoliko se zbrajanje izvodi bez prijenosa ta operacija se naziva zbrajanje po modulu 2. U logičkom smislu ta operacija je ekvivalentna ekskluzivnoj disjunkciji (XOR). Operaciju zbrajanja LSB bitova može se prikazati tablicom istinitosti 2.2:

A B zbroj = A ⊕ B prijenos = A & B 0 0 0 0 0 1 1 0 1 0 1 0 1 1 0 1

Tablica 2.2. Tablica istinitosti za polu-zbrajalo

A B Donos Zbroj prijenos 0 0 0 0 0 0 0 1 1 0 0 1 0 1 0 0 1 1 0 1 1 0 0 1 0 1 0 1 0 1 1 1 0 0 1 1 1 1 1 1

Tablica 2.3. Tablica istinitosti za potpuno zbrajalo

Digitalni sklop koji realizira ovu funciju naziva se poluzbrajalo (half-adder) i prikazan je na slici 2.3(a). Pri zbrajanju ostalih bitove treba pribrojiti i bit donosa kao u tablici 2.3. Digitalni sklop koji realizira ovu funkciju naziva se potpuno zbrajalo (full-adder). Prikazan je na slici 2.3(b).

Očito je da se upotrebom više ovakvih sklopova može "izračunati" zbroj dva binarna niza, na način da se "prijenos" s zbrajala bitova manjeg značaja prenosi kao "donos" u zbrajalo bitova većeg značaja.

Page 18: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

18

Slika 2.3 Sklopovska izvedba 1-bitnog zbrajala

Operacija ekskluzivne disjunkcije (XOR) se često koristi u bit-značajnim operacijama pri

šifriranju i u programima s bit-mapiranim grafičkim algoritmima. Interesantno svojstvo ove operacije je da ako se na neki binarni niz A dva puta uzastopno primjeni bit-značajna ekskluzivna disjunkcija s nizom B rezultatni niz je jednak nizu A. Primjerice, neka je niz A= 1010, a niz B=0110. Tada je:

A ⊕ B = 1100 (A ⊕ B) ⊕ B = 1010 = A

Dakle, prvo djelovanje je šifriranje, a drugo djelovanje je dešifriranje originalnog niza.

Oduzimanje broja se može izvesti kao zbrajanje negativne vrijednosti broja. Kako se kodiraju negativni brojevi bit će pokazano kasnije.

Binarno množenja se vrši tako da se djelomičan umnožak pomiče za jedno mjesto ulijevo pri svakom uzimanju idućeg množitelja. Ako je množitelj 0, djelomični umnožak je 0, a ako je množitelj 1, djelomični umnožak jednak je množeniku. Primjer:

5 x 5 = 25 5 x 10 = 50 101 (5) 101 (5) 101 (5) 1010 (10) ------------ --------------- 101 000 000 101 101 000 ------------ 101 11001 (25) ---------------- 110010 (50)

Binarno dijeljenje se u računalu izvodi primjenom binarnog množenja i oduzimanja, na isti način kao i kod decimalnih brojeva. Navedene operacije su ugrađene u skup naredbi većine današnjih procesora.

Page 19: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

19

Još dvije operacije su specifične za rad s nizovima bitova. To su operacije logičkog posmaka bitova u lijevo ili u desno (podrazumijeva se LSB na desnoj strani niza), a označavaju se sa SHL (eng. shift left - posmak u lijevo) i SHR (shift right - posmak u desno).

Posmak od jednog mjesta u lijevo odgovara množnju kardinalnih brojeva s 2, a posmak bitova jedno mjesto udesno odgovara dijeljenju kardinalnih brojeva s 2. Na prazna mjesta se postavljaju nule.

Primjer:

0011 SHL 1 ≡ 0110 odgovara 3 * 2 = 6 0011 SHL 2 ≡ 1100 odgovara 3 * 4 = 12 1110 SHR 1 ≡ 0111 odgovara 14 / 2 = 7

2.2.2 Oktalni i heksadecimalni brojevni sustavi U višim programskim se jezicima rijetko koristi zapis broja u binarnom obliku jer čovjek

teško pamti veće nizove "nula i jedinica". Radije se koristi oktalni ili heksadecimalni brojevni sustav.

U oktalnom brojevnom sustavu koristi se 8 znamenki: 01234567, a baza brojevnog sustava je x=23=8. Oktalnim brojem jednostavno se označava niz od 3 bita, jer je s binarnim nizom od 3 bita moguće kodirati 8 znamenki oktalnog brojevnog sustava:

bit 0 0 1 0 1 0 1 0 1 bit 1 0 0 1 1 0 0 1 1 bit 2 0 0 0 0 1 1 1 1 ----------------------- 0 1 2 3 4 5 6 7 znamenke oktalnog brojevnog sustava

To omogućuje pregledniji zapis većih binarnih nizova, primjerice

1001000101112 = 44278,

a koristi se pravilo grupiranja po 3 bita: 100=4, 100=4, 010=2, 111=7.

U heksadecimalnom brojevnom sustavu koristi se 16 znamenki: 0123456789ABCDEF, a baza brojevnog sustava iznosi x=16. Za kombinacije od 10 do 15 upotrebljena su prva slova abecede, kojima numerička vrijednost u decimalnom brojevom sustavu iznosi:

A=10, B=11, C=12, D=13, E=14 i F=15.

Heksadecimalnim se brojem jednostavno označava niz od 4 bita, jer se binarnim nizom od 4 bita može kodirati 16 znamenki heksadecimalnog brojevnog sustava:

bit 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 bit 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 bit 2 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 bit 3 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 --------------------------------------- 0 1 2 3 4 5 6 7 8 9 A B C D E F heksadecimalne znamenke

To omogućava pregledniji zapis većih binarnih nizova, primjerice:

10010001011111102 = 917E16,

a koristi se pravilo grupiranja po 4 bita: 1001=9, 0001=1, 0111=7, 1110=E.

U programskim jezicima se uvode posebna leksička pravila za zapis konstanti u pojedinom brojevnom sustavu. Ta pravila će biti opisana u četvrtom poglavlju.

Page 20: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

20

3 Izrada prvog C programa

Naglasci: • Izvorni program i izvršni program • Prvi program u C jeziku – hello.c • Kompiliranje programa koji je napisan u više datoteka • Integrirana programska okolina • Makefile

U ovom poglavlju opisani su programi koji se koriste u razvoju programa. Njihova upotreba se demonstrira s nekoliko jednostavnih programa u C jeziku.

3.1 Strojni, asemblerski i viši programski jezici Procesor izvršava radnje u računalu na temelju binarno kodiranih strojnih naredbi, koje

dobavlja iz memorije računala. Skup svih strojnih naredbi procesora naziva se strojni jezik, a skup naredbi kojima se neposredno izvršava neka zadana operacija u računalu naziva se strojni program.

Strojne naredbe je veoma teško pamtiti. Zbog toga se koristi simboličko zapisivanje naredbi asemblerskim jezikom, u kojem se naredbe zapisuju s kraticama riječi iz engleskog jezika. Primjerice, naredba da se broj, koji se nalazi u registru ax procesora Intel 8086, uveća za jedan, glasi:

strojni jezik 01000000 asemblerski jezik inc ax (inc je kratica od increment)

Dakle, binarni strojni kôd je zamijenjen simboličkim zapisom koje procesor ne razumije. Za prevođenje asemblerskog zapisa u strojni program koristi se program koji se naziva asembler.

Viši programski jezici omogućuju jednostavnije programiranje uvođenjem simboličkih naredbi koje zamjenjuju više naredbi strojnog jezika. Primjerice, iskaz C jezika

x = sin(3.14) + 7;

znači naredbu da se varijabli x pridijeli vrijednost koja se dobije zbrojem vrijednosti funkcije sin(3.14) i konstante 7.

Zapis programa asemblerskim ili višim programskim jezikom naziva se izvorni program (eng. source program). Za pisanje izvornog programa koriste se programi za uređivanje teksta koji se nazivaju editori. Izvorni program u C jeziku se obično pohranjuje kao datoteka s imenom koje završava znakovima “.c”

Programi za prevođenje izvornog programa u izvršni program mogu biti realizirani kao interpreteri ili kao kompilatori (eng. compiler), a razlika među njima je u načinu kako se izvorni program prevodi u izvršni program.

Kompilator analizira izvorni program i stvara strojni kôd, koji pohranjuje u tzv. objektnu datoteku (eng. object file). Kod MS-DOS računala ime objektne datoteke završava s “.obj”, a kod Unix računala s “.o”. Iako objektna datoteka sadrži strojni kôd, on još nije pogodan za izvođenje u računala, naime za izvođenje programa potrebno je odrediti veze s operativnim

Page 21: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

21

sustavom i memorijske lokacije programskih objekata. To se vrši programom koji se zove povezivač ili linker. Nakon obrade s linkerom dobije izvršni ili izvedivi program (eng. executive program), a ako se pohrani kao datoteka onda se ta datoteka naziva izvršna datoteka (eng. executive file). Kod MS-DOS računala ime izvršne datoteke završava slovima “.exe”. Postoje programi u kojima je integrirana funkcija kompilatora i linkera. Primjerice, Microsoft Visual C sadrži program "cl.exe", a na Unix-u se koriste "cc" ili "gcc" programi.

Učitavanje izvršne datoteke u memoriju računala vrši program koji se naziva punjač (eng. loader). On je sastavni dio operativnog sustava računala. Korisnik pokreće izvršenje programa tako da otkuca ime programa u komandnoj liniji ili se služi programima s grafičkim sučeljem (primjerice, program Explorer kod Windows-a). Ostale radnje punjača obavlja operativni sustav.

Programi se mogu izvršavati i pomoću specijalnih programa koji služe za testiranje izvršenja procesa. Tu spadaju programi koji se nazivaju dibageri (eng. debugger) i monitori. Pomoću njih je moguće izvršavati program u konačno zadanom broju koraka, naredbu po naredbu, i nakon svakog koraka moguće je pratiti stanje varijabli (memorije) i registara procesora. Kod naprednijih dibagera moguće je pratiti izvršenje programa na razini izvornog koda, dok se kod jednostavnijih dibagera i monitora izvršenje programa može pratiti na razini strojnog koda.

Interpreter prevodi izvorni kod u niz izvršnih naredbi koje se obično izvršavaju unutar samog programa interpretera. Skup izvršnih naredbi interpretera obično se naziva "virtuelni stroj" (primjerice Java VM ili Microsoft CLR). Program za "interpretiranje", pomoću virtuelnog stroja, može biti i odvojeni program. U tom slučaju interpreter ima funkciju kompilatora koji generira datoteke s nizom naredbi virtuelnog stroja.

U razvoju programa koristi se veliki broj programa, tzv. softverskih alata, koji olakšavaju razvoj programa i pisanja dokumentacije. Primjerice, na Unix-u se koriste: make - program za kontrolu procesa kompiliranja, grep - program za pretraživanje datoteka, profiler - program za analizu izvršenja programa, diff – program za utvrđivanje razlika među izvornim datotekama, patch – program za automatsko unošenje izmjena u izvorne datoteke. Kod Windows operativnog sustava više su u upotrebi programi koji se nazivaju integrirana razvojna okolina (eng. IDE – integrated developement environment) kod kojih se pod jedinstvenim grafičkim sučeljem koordinira rad svih faza razvoja programa – editiranje izvornog koda, kompiliranje, dibagiranje i profiliranje koda. U okviru IDE-a integriran je i pristup kompletnoj dokumentaciji kompilatora i programskih biblioteka. Bez sumnje, najpopularniji program ovog tipa je Microsoft Visual Studio.

3.2 Prvi program u C jeziku Gotovo sve knjige o C jeziku kao prvi primjer C-programa uzimaju program:

/* Datoteka: hello.c */ /* Prvi C program. */ #include <stdio.h> int main() { printf("Hello world!\n"); return 0; }

Ovaj program vrši samo jednu radnju; na standardnom izlaznom uređaju ispisuje poruku:

Hello World!.

Page 22: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

22

Na slici 3.1 prikazan je Windows komandni prozor. On se još naziva MS-DOS prozor ili još jednostavnije komandna konzola. Karakteristika ispisa u konzoli je da se može jedino vršiti ispis teksta, i to u po principu da se ispisuje redak po redak, od vrha konzole na dolje. U konzoli se mogu zadavati komande operativnom sustavu u retku koji uobičajeno započinje s oznakom tekućeg direktorija. Taj redak se naziva komandna linija operativnog sustava.

Najprije će biti pokazano kako se može editirati, kompilirati i izvršiti ovaj program, koristeći komandnu liniju operativnog sustava u MSDOS-prozoru (sl. 3.1). To se vrši u tri koraka:

1. Pomoću editora stvara se tekstualna datoteka, imena "hello1.c", koja sadrži prikazani izvorni kôd programa.

2. Pomoću kompilatora se izvorni kôd programa prevodi u objektni, a zatim i u izvršni kôd. Ako kompilator dojavi da u izvornom kôdu postoje leksičke ili sintaktičke pogreške, ponavlja se korak 1 i ispravljaju pogreške.

3. Izvršenje programa se zadaje u komandnoj liniji. Primjer za OS Windows:

c:> edit hello.c ↵ - poziv editora edit (↵ je tipka enter) i unos izvornog programa u datoteku hello.c c:> cl hello.c ↵ - poziv kompilatora (Microsoft-program cl.exe) koji stvara izvršnu datoteku hello.exe c:> hello ↵ - komanda za izvršenje programa hello.exe Hello world! - rezultat izvršenja programa

Slika 3.1 Izgled komandnog prozora u Windows operativnom sustavu

Primjer za OS Linux:

$ vi hello.c ↵ - poziv editora vi i unos datoteke hello.c $ gcc hello.c –o hello ↵ - poziv kompilatora gcc, koji stvara izvršnu datoteku hello $ hello ↵ - komanda za izvršenje programa hello Hello world! - rezultat izvršenja programa

Page 23: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

23

Analiza programa "hello1.c":

C programi se sastoje od niza potprograma koji se zovu funkcije C-jezika. U programu "hello1.c" definirana je samo jedna funkcija, nazvana main(). Ona mora biti definirana u svakom C programu, jer predstavlja mjesto početka izvršenja programa. Programer može definirati nove funkcije, svaku s jedinstvenim imenom, a mogu se koristiti i prethodno definirane funkcije iz standardne biblioteke funkcija C jezika.

Radnje koje obavlja neka funkcija zapisuju se unutar tijela funkcije. Tijelo funkcije je omeđeno vitičastim zagradama. U ovom je slučaju u tijelu funkcije je iskaz koji predstavlja naredbu da se pomoću standardne C funkcije printf(), na standardnoj izlaznoj jedinici, ispiše poruka "Hello World!".

Pojedini dijelovi programa "hello1.c" imaju sljedeći značaj: /* Prvi C program. */ Tekst omeđen znakovima /* i */ predstavlja

komentar. Kompilator ne analizira komentare, već ih tretira kao umetnuto "prazno" mjesto.

#include <stdio.h> #include predstavlja pretprocesorsku direktivu. Ona označava da u proces kompiliranja treba uključiti sadržaj datoteke imena "stdio.h". Ta datoteka sadrži deklaracije funkcija iz standardne biblioteke C-jezika.

int main() Ovo je zaglavlje funkcije imena main.

int označava tip vrijednosti (cijeli broj) koji vraća funkcija na kraju svog izvršenja (u ovom programu to nema nikakvi značaj).

{ { označava početak tijela funkcije main. printf("Hello world!\n"); Ovo je naredba za poziv standardne funkcije

printf(), kojom se ispisuje niz znakova (string) koji je argument ove funkcije.

\n predstavlja oznaku za prijelaz u novi red ispisa.

Znak točka-zarez označava kraj naredbe. Return 0; main() "vraća" vrijednost 0, što se uobičajeno

koristi kao oznaka uspješnog završetka programa.

} } označava kraja tijela funkcije main.

U objašnjenju programskih iskaza korišteni su neki novi pojmovi (deklaracija, standardna biblioteka, pretprocesorska direktiva). Oni će biti objašnjeni u sljedećim poglavljima.

Ako program nije napisan u skladu s pravilima jezika, tada kažemo da je program

sintaktički pogrešan. Primjerice, ukoliko u prethodnom programu nije otkucana točka-zarez iza naredbe printf("Hello world!\n"), kao u programu "hello2.c",

Page 24: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

24

/* Datoteka: hello2.c */ /* Hello s greškom */ #include <stdio. h> int main() { printf("Hello world!\n") /* greška: nema ; */ return 0; }

tada kompilator, u ovom slučaju program cl.exe, ispisuje poruku da postoji sintaktička pogreška u sljedećem obliku:

C:\>cl hello.c

Microsoft (R) 32-bit C/C Optimizing Compiler Ver.12.00.8168 for 80x86 Copyright (C) Microsoft Corp 1984-1998. All rights reserved. hello.c hello.c(5) : error C2143: syntax error : missing ';' before 'return' Poruka o greški ima sljedeće elemente:

hello.c(5) – obavijest da je greška u retku 5 datoteke "hello.c", error C2143: syntax error - kôd i tip greške, missing ';' before 'return' – kratki opis mogućeg uzroka greške.

Na temelju dojave greške često je lako izvršiti potrebne ispravke u programu.

Važno je uočiti da je kompilator pronašao grešku u petom retku, iako je pogrešno napisana naredba u četvrtom retku. Razlog tome je pravilo C jezika po kojem se naredba može pisati u više redaka, a stvarni kraj naredbe predstavlja znak točka-zarez. Pošto kompilator nije pronašao točku-zarez u četvrtom retku, kompiliranje je nastavljeno s petim retkom i tek tada je utvrđeno da postoji pogreška.

Zadatak: Provjerite da li je sintaktički ispravno napisan sljedeći program:

/* Datoteka: hello3.c * Zapis naredbe u više redaka */ #include <stdio. h> int main() { printf ( "Hello world!\n" ); return 0; }

Page 25: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

25

3.3 Struktura i kompiliranje C programa Na sličan način, kao u funkciji main(), može se neka druga grupa naredbi definirati kao

posebna funkcija s prikladnim imenom. Primjerice, prethodni program se može napisati pomoću dvije funkcije Hello() i main() na sljedeći način:

/* Datoteka: hello4.c * Program s korisnički definiranom funkcijom Hello() */ #include <stdio.h> void Hello() { printf("Hello world\n"); } int main() { Hello(); return 0; }

Za razumijevanje ovog programa potrebno je upoznati pravila za definiranje i pozivanje funkcija.

Funkcija se definira zaglavljem i tijelom funkcije. Zaglavlje funkcije se zapisuje na sljedeći način: ime funkcije se zapisuje nizom znakova

koji sadrži slova, znamenke i znak '_', ali uz uvjet da je prvi znak niza slovo ili '_'. Ispred imena funkcije se navodi vrijednost koju funkcija vraća. Ako funkcija ne vraća nikakovu vrijednost tada se ispred imena piše riječ void, koja znači “ništa ili nevažno je”. Ovakve funkcije se nazivaju procedure. U njima se ne mora koristiti naredba return, već one završavaju kada se izvrši posljednje definirana naredba. Iza imena funkcije se, u zagradama, navode formalni argumenti funkcije, ako ih ima. Kasnije će biti objašnjeno kako se definiraju i koriste argumenti funkcije.

Tijelo funkcije se zapisuje unutar vitičastih zagrada, a sadrži niz naredbi i deklaracija. Poziv funkcije je naredba za izvršenje funkcije, (tj. za izvršenje naredbi koje su definirane

unutar funkcije). Zapisuje se na način da se prvo napiše ime funkcije, a zatim obvezno zagrade i argumenti funkcije, ako su prethodno definirani. Primjerice, u funkciji main() iskaz Hello(); predstavlja poziv funkcije Hello(). Poziv funkcije pokreće izvršenje naredbi koje su definirane u tijelu funkcije Hello(), tj. poziva se funkcija printf() s argumentom "Hello World\n". Nakon izvršenja te naredbe program se vraća, u funkciju main() na izvršenje prve naredbe koja je napisana iza poziva funkcije Hello(). Funkcija iz koje se pokreće izvršenje pozvane funkcije naziva se pozivna funkcija.

U prethodnom primjeru funkcija Hello() je definirana prije funkcije main(). Taj redoslijed je određen pravilom da se funkcija može pozivati samo ako je prethodno definirana. Iznimka od ovog pravila je ako se koriste tzv. prototipovi funkcija (ili unaprijedne deklaracije funkcija).

Page 26: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

26

Prototip ili deklaracija funkcije je zapis u koji sadrži zaglavlje funkcije i znak točka-zarez. On služi kao najava da je funkcija definirana negdje drugdje; u standardnoj biblioteci ili u programu iza mjesta njenog poziva ili u drugoj datoteci. U skladu s ovim pravilom dozvoljeno je prethodni program pisati u obliku:

/* Datoteka: hello5.c: * C program s korisnički definiranom funkcijom Hello() * i prototipom funkcije Hello() */ #include <stdio.h> void Hello(); /* prototip ili deklaracija funkcije Hello()*/ int main() /* definicija funkcije main() */ { Hello(); return 0; } void Hello() /* definicija funkcije Hello() */ { printf("Hello world\n"); }

U C jeziku se programi mogu zapisati u više odvojenih datoteka. Primjerice, prethodni program se može zapisati u dvije datoteke "hellomain.c" i "hellosub.c". U datoteci "hellomain.c" definirana je funkcija main() i deklarirana je funkcija Hello(). Definicija funkcije Hello() zapisana je u datoteci "hellosub.c".

/* Datoteka: hellomain.c */ void Hello(); int main() { Hello(); return 0; }

/* Datoteka: hellosub.c */ #include <stdio.h> void Hello() { printf("Hello world\n"); }

Izvršni program se može dobiti komandom:

c:> cl hellomain.c hellosub.c /Fe"hello.exe"

U komandnoj liniji su zapisana imena datoteka koje treba kompilirati. Zatim je komandnom preklopkom /Fe zapisano da izvršnu datoteku treba formirati pod imenom "hello.exe".

Page 27: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

27

Slika 3.2 Proces formiranja izvršnog programa

Proces formiranja izvršnog programa je prikazan na slici 3.2. Najprije leksički pretprocesor

kompilatora unosi sve deklaracije iz datoteke "stdio.h" u datoteku "hellosub.c". Zatim kompilator prevodi izvorne datoteke "hellomain.c" i "hellosub.c" u objektne datoteke "hellomain.obj" i "hellosub.obj". U ovim datotekama se uz prijevod izvornog koda u strojni jezik nalaze i podaci o tome kako izvršiti poziv funkcija koje su definirane u drugoj datoteci. Povezivanje strojnog kôda, iz obje datoteke, u zajednički izvršni program obavlja program link.exe, kojeg skriveno poziva program cl.exe.

Dobra strana odvajanja programa u više datoteka je da se ne mora uvijek kompilirati sve datoteke, već samo one koje su mijenjane. To se može ostvariti na sljedeći način:

Prvo se pomoću preklopke /c kompilatorskom pogonskom programu cl.exe zadaje da izvrši prijevod u objektne datoteke, tj.

c:> cl /c hellomain.c c:> cl /c hellosub.c

Time se dobiju dvije objektne datoteke: "hellomain.obj" i "hellosub.obj". Povezivanje ovih datoteka u izvršnu datoteku vrši se komandom:

c:> cl hellomain.obj hellosub.obj /Fe"hello.exe"

Ako se kasnije promijeni izvorni kôd u datoteci "hellomain.c", proces kompiliranja se može ubrzati komandnom:

c:> cl hellomain.c hellosub.obj /Fe"hello.exe"

jer se na ovaj način prevodi u strojni kôd samo datoteka "hellomain.c", a u procesu formiranja izvršne datoteke koristi se prethodno formirana objektna datoteka "hellosub.obj".

3.4 Integrirana razvojna okolina (IDE) Integrirana razvojna okolina Visual Studio omogućuje editiranje izvornog koda,

kompiliranje, linkanje, izvršenje i dibagiranje programa. Sadrži sustav "on-line" dokumentacije o programskom jeziku, standardnim bibliotekama i programskom sučelju prema operativnom sustavu (Win32 API). Pomoću njega se mogu izrađivati programi s grafičkim korisničkim sučeljem i programi za konzolni rad.

Nakon poziva programa dobije se IDE Visual Studio prikazan na slici 3.3.

Page 28: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

28

Slika 3.3 Izgled Visual Studio pri pokretanju programa

Najprije će biti opisano kako se formira projekt za konzolni tip programa. Prije pokretanja programa, neka se u direktoriju c:\My Documents\C2002\pog2\ nalaze izvorne datoteke hellomain.c i hellosub.c.

Pokretanjem komande menija: File-New-Project, dobije dijalog za postavljanje novog projekta. U dijalogu prikazanom na slici 3.4 označeno je

1. da će projekt biti klase: Win32 Console Application, 2. upisano je ime direktorija d:\src\ 3. gdje će biti zapisana dokumentacija projekta imena hello.

U ovom slučaju Visual Studio zapisuje dokumentaciju projekta u datotekama hello.vsproj i hello.sln (datoteka ekstenzije .sln sadrži opis radne okoline, a datoteka ekstenzije .vcproj sadrži opis projekta). Visual Studio također automatski formira dva poddirektorija: .\Release i .\Debug, u kojima će se nalaziti objektne i izvršne datoteke. (Debug direktorij je predviđen za rad kompilatora u obliku koji je prikladan za pronalaženje grešaka u programu)

Pokretanjem komande: Project - Add to project – File, u dijalogu prikazanom na slici 3.9 odabiremo datoteke "hellomain.c" i "hellosub.c", iz direktorija c:\My Documents\cpp-2001\pog3\. Dobije se izgled radne okoline kao na slici 3.10.

Pokretanjem komande: Build – Build hello.exe vrši se proces kompiliranja i linkanja. Ako

se izvrši bez greške, nastaje izvršni program imena hello.exe. Program hello.exe se može pokrenuti pozivom komande Build – Execute hello.exe (ili pritiskom tipki Ctrl+F5).

Page 29: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

29

Slika 3.4 Dijalog za postavljanje novog projekta

Slika 3.5 Dijalog za postavljanje tipa Win32-Console projekta

Page 30: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

30

Slika 3.6 Dijalog koji izvještava o postavkama novog projekta

Slika 3.7 Izgled radne okoline nakon formiranja novog projekta “hello”

Page 31: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

31

Slika 3.9 Dijalog za umetanja datoteka u projekt

Slika 3.10 Izgled IDE s aktivnim editorom

Page 32: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

32

3.5 Usmjeravanje procesa kompiliranja programom nmake Kada se program sastoji od velikog broja datoteka, procesom kompiliranja se može

upravljati pomoću program imena nmake (make na UNIX-u) i specifikacije koja se zapisuje u datoteci koji se obično naziva makefile. Za prethodni primjer datoteka makefile može biti napisana na sljedeći način:

# datoteka: makefile #Simbolička definicija za spisak objektnih datoteka OBJS = hellomain.obj hellosub.obj #progam se formira povezivanjem objektnih datoteka: hello.exe : $(OBJS) cl $(OBJS) /Fe"hello.exe" # $(...) znači umetanje prethodnih makro definicija # ovisnost objektnih datoteka o izvornim datotekama # i komanda za stvaranje objektne datoteke hellomain.obj : hellomain.c cl -c hellomain.c hellosub.obj : hellosub.c cl -c hellosub.c

Ako se ovu datoteku spremi pod imenom makefile, dovoljno je u komandnoj liniji otkucati:

c:> nmake

i biti će izvršen cijeli postupak kompiliranja i linkanja izvršnog programa. Ako se pak ova datoteka zapiše pod nekim drugim imenom, primjerice "hello.mak", u komandnoj liniji, iza preklopke –f treba zadati i ime datoteke, tj.

c:>nmake –fhello.mak

Kako se formira makefile.

Temeljna pravila formiranja makefile datoteke su: • Komentari se u makefile zapisuju tako da redak započne znakom #. • Mogu se navesti različite simboličke definicije oblika: IME = text, što omogućuje da

na mjestu gdje se piše $(IME) bude supstituiran text. • U makefile se zatim navodi niz definicija koje se sastoje od dva dijela: prvi dio opisuje

ovisnost datoteka, a drugi opisuje kao se ta ovisnost realizira. Primjerice, u zapisu

hellosub.obj : hellosub.c cl –c hellosub.c

• U prvom retku je označena ovisnost sadržaja "hellosub.obj" o sadržaju "hellosub.c". • U drugom retku je specificirana komanda koja se primjenjuje na datoteku "hellosub.c" • Redak u kojem se specificira komanda mora započeti znakom tabulatora

Program nmake uspoređuje vrijeme kada su nastale međuovisne datoteke. Čim se promijeni sadržaj "hellosub.c", dolazi do razlike u vremenu nastanka međuovisnih datoteka, pa program nmake pokreće program za kompiliranje koji je specificiran u drugom retku.

Korištenje makefile je vrlo popularno, posebno na Unix sustavima i kod profesionalnih programera, jer se pomoću simboličkih definicija lako može definirati proces kompiliranja na različitim operativnim sustavima.

Page 33: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

33

4 Kodiranje i tipovi podataka

Naglasci: • kodiranje brojeva • kodiranje znakova • kodiranje logičkih vrijednosti • pojam tipa podataka • tipovi konstanti i varijabli u C jeziku • adrese i pokazivači • ispis i unos podataka

U ovom se poglavlju se opisuje kako se u računalu kodiraju brojevi i znakovi, objašnjava se koncept tipa podataka i pokazuje karakteristike tipova u C jeziku.

4.1 Kodiranje i zapis podataka Kodiranje je postupak kojim se znakovima, numeričkim vrijednostima i drugim tipovima

podataka pridjeljuje dogovorom utvrđena kombinacija binarnih znamenki. Ovdje će biti opisano kodiranje koje se koristi u C jeziku.

S programerskog stajališta, važnije od samog načina kodiranja je veličina zauzeća memorije i interval vrijednosti koji se postiže kodiranjem. Također, važno je upoznati leksička pravila po kojima se zapisuju znakovne i numeričke konstante u "literalnom" obliku.

4.1.1 Kodiranje pozitivnih cijelih brojeva (eng. unsigned integers) Pozitivni cijeli brojevi (eng. unsigned integers), ili kardinalni brojevi, su brojevi iz skupa

kojeg čine prirodni brojevi i nula. Način njihovog kodiranja je opisan u poglavlju 2. U C jeziku se literalne konstante, koje predstavljaju pozitivne cijele brojeve, mogu zapisati u decimalnom, heksadecimalnom i oktalnom brojevnom sustavu, prema slijedećem leksičkom pravilu:

• niz decimalnih znamenki označava decimalnu konstantu ukoliko prva znamenka nije nula.

• niz oktalnih znamenki označava oktalnu konstantu ako je prva znamenka jednaka nuli. • niz heksadecimalnih znamenki, kojem prethodi prefix 0x ili 0X, označava

heksadecimalnu konstantu. Primjer: tri ekvivalentna literalna zapisa vrijednosti binarnog niza 011011 u C jeziku su:

decimalna konstanta 27 oktalna konstanta 033 heksadecimalna konstanta 0x1B

4.1.2 Kodiranje cijelih brojeva (eng. integers) Cijeli brojevi (eng. integers) su brojevi iz skupa kojeg čine prirodni brojevi, negativni

prirodni brojevi i nula, ili drukčije kazano, to su brojevi s predznakom (eng. signed integers). Većina današnjih procesora za kodiranje cijelih brojeva s predznakom koristi tzv. komplementni

Page 34: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

34

brojevni sustav. Puni komplement n-znamenkastog broja Nx, u brojevnom sustavu baze x matematički se definira izrazom:

′ = −N x Nxn

x

primjerice, u decimalnom sustavu komplement troznamenkastog broja 733 je 103-733= 277. Ako se n-znamenkasti broj i njegov komplement zbroje vrijedi da će n znamenki biti jednako nuli. U prijašnjem primjeru 733+277=1000, dakle tri znamenke su jednake nuli.

U binarnom se sustavu puni komplement naziva komplement dvojke i vrijedi:

′ = −N Nn2 22

Komplement dvojke se koristi za označavanje negativnih brojeva. Primjerice, komplement dvojke broja +1 za n=4 iznosi:

11110001100001242 =−=−=′N .

Ako se broj i njegov komplement zbroje, rezultat treba biti nula. To vrijedi, u prethodnom primjeru, jer su prva četiri bita zbroja jednaka nuli. Peti bit je jednak jedinici, ali on se u 4-bitnom sustavu odbacuje.

U sustavu komplementa dvojke pozitivni brojevi uvijek imaju MSB=0, a negativni brojevi imaju MSB=1.

Što se događa ako se zbroje dva pozitivna broja koji imaju bitove ispod MSB jednake jedinici. Primjerice, ako zbrojimo 4+5 ( u 4-bitnom sustavu)

0100 +0101 ----- 1001

dobije se rezultat koji predstavlja negativan broj u sustavu komplementa dvojke. Do prijelaza u područje komplementa ne bi došlo da je rezultat zbrajanja bio manji od 7, odnosno 24-1-1.

Poopći li se prethodno zapažanje na brojeve od n-bita, može se zaključiti da operacija zbrajanja ima smisla samo ako je zbroj operanada manji od 2n-1-1. Zbog toga, najveći pozitivni broj koji se može predstaviti u sustavu komplementa dvojke iznosi:

max_int = (0111...111) = 2n-1-1,

a najveći iznos negativnog broja iznosi:

min_int = (1000...000) = -2n-1.

Uočite da postoji za jedan više negativnih brojeva od pozitivnih brojeva.

Obični komplement binarnog broja (naziva se i komplement jedinice) dobije se zamjenom svih jedinica s nulom i obratno. Iznos broja. koji se dobije na ovaj način, računa se prema izrazu:

N Nn2 22 1= − −

Obični komplement nije pogodan za izražavanje prirodnih brojeva jer nije jednoznačno određena vrijednost nule, naime obični komplement od 0000 iznosi 1111. On služi za jednostavno izračunavanje punog komplementa, jer vrijedi:

′ = +N N2 2 1

Page 35: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

35

Puni komplement se izračunava tako da se običnom komplementu pribroji jedinica, primjerice, komplement dvojke broja 6 u 8-bitnoj notaciji iznosi:

00000110 (+6) -------- 11111001 (obični komplement od +6) 1 (dodaj 1) -------- 11111010 (-6 u komplementu dvojke)

Izračunajmo puni komplement od 0:

00000000 (0) -------- 11111111 (komplement od 0) 1 (dodaj 1) -------- 100000000 (-0 u komplementu dvojke)

Jedinica predstavlja deveti bit. Ona se u 8-bitnom sustavu odbacuje pa rezultat opet predstavlja nulu. Komplement dvojke omogućuje jednoznačno određivanje nule, pa je podesan za kodiranje cijelih brojeva.

4.1.3. Kodiranje realnih brojeva Realni brojevi se u matematici zapisuju na način da se cijeli i decimalni dio odvoje

decimalnim zarezom (pr. 67,098), a koristi se i ekponentni format (eng. scientific format). Primjerice, prethodni se broj može zapisati u obliku 0,67089⋅102. U programskom jeziku C koristi se sličan zapis kao u matematici, s razlikom da se umjesto decimalnog zareza koristi "decimalna točka", a potencija broja 10 se označava velikim ili malim slovom E.

matematički zapis ekvivalentni zapis u C-jeziku 1,789 1.789

0,789 0.789 ili .789

-178,9⋅10-2 -178.9e-2 ili -178.9E-2

-0,01789⋅102 -0.01789e2 ili -0.01789E2 ili -0.01789e+2

Tablica 4.1. Matematički i programski zapis realnih brojeva

Eksponentni format se sastoji od dva dijela: mantise i eksponenta

eksponentni decimalni format = mantisa 10eksponent

Mantisa se zapisuje kao obični decimalni broj s predznakom, a eksponent se zapisuje kao cijeli broj. Prednost korištenja eksponentnog formata je u lakšem zapisu vrlo velikih i vrlo malih brojeva. Uočite da se promjenom vrijednosti eksponenta pomiče položaj decimalnog zareza.

Kodiranje s fiksnim položajem binarne točke (eng. fixed point numbers)

Umjesto pojma decimalnog zareza uvodi se pojam binarne točke. Opći oblik zapisivanja realnog broja s fiksnim položajem binarne točke, u slučaju da se N znamenki koristi za

Page 36: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

36

označavanje cijelih vrijednosti, a n znamenki za označavanje razlomljenih vrijednosti (po bazi 2: 1/2, 1/4, 1/8 itd.) glasi:

nNN bbbbbb −−−−− • ...... 21021 ,

a iznos mu se računa prema izrazu:

( )1,0,2...222...2. 22

11

00

112 ∈++++++= −

−−

−−

−−

− in

nN

N bbbbbbnN

Ako se ovaj broj pomnoži s 2n može ga se u operacijama smatrati cijelim brojem, a nakon

izvršenih aritmetičkih operacija rezultat se skalira za iznos 2-n. Ovaj oblik kodiranja ima brojne nedostatke, i koristi se samo u izuzetnim slučajevima.

Kodiranje s pomičnim položajem binarne točke (eng. floating point numbers)

Ideja eksponentnog formata uzeta je kao temelj za kodiranje realnih brojeva i u binarnom brojevnom sustavu. Kodiranje se vrši prema izrazu

( )F ms e= −1 2

gdje m predstavlja mantisu, e je eksponent dvojke, a s∈(0,1) određuje predznak broja. Eksponent i mantisa se kodiraju u binarnom brojevnom sustavu. Eksponent se kodira kao cijeli broj, a mantisa kao binarni broj s fiksnim položajem binarne točke. Ideja je jednostavna: promjenom eksponenta pomiče se i položaj binarne točke iako se mantisa zapisuje s fiksnim položajem binarne točke. Primjerice, neka je broj kodiran u obliku:

0.00001xxxxxxxxx 2e

gdje x može biti 0 ili 1. Ovaj oblik zapisa realnog broja naziva se nenormalizirani zapis. Pomakne li se položaj binarne točke za 5 mjesta ulijevo dobije se ekvivalentni zapis

1.xxxxxxxxx00000 2e-5,

Posmak bitova ulijevo ekvivalentan je dijeljenju s dva, stoga se vrijednost eksponenta smanjuje za 5. Ovakav kodni zapis, u kojem je uvijek jedinica na prvom mjestu, naziva se normalizirani zapis. Značaj normaliziranog zapisa je u činjenici što se njime iskorištavaju svi bitovi mantise za kodiranje vrijednosti, dakle osigurava se veća točnost zapisa. Normaliziranim se oblikom ipak ne može kodirati veoma male vrijednosti, pa je tada pogodniji nenormalizirani zapis broja. Treba pojasniti i kako je kodirana vrijednost nula. Pogodno bi bilo da sva bitna polja pri kodiranju vrijednosti nula budu jednaka nuli (zbog logičkih operacija), ali pošto se za kodiranje eksponenta također koristi binarni zapis, vrijednost eksponenta nula se matematički koristi za označavanje brojeva većih od jedinice. Da bi se zadovoljilo zahtjevu kodiranja nule s nultim zapisom eksponenta uobičajeno je da se umjesto stvarne vrijednosti eksponenta kodira vrijednost:

E = e + pomak,

gdje je pomak neka konstantna vrijednost, a odabire se na način da je jednak najnižoj vrijednosti eksponenta e, koji je negativna vrijednost Ovako zapisani eksponent naziva se pomaknuti eksponent. Značaj pomaka pokazuje sljedeći primjer. Neka je eksponent opisan s 8 bita. Tada se E kreće u rasponu od 0 do 255. Ako se uzme da je pomak=127, i da je E=0 rezervirano za kodiranje nule, onda se vrijednost binarnog eksponenta kreće u rasponu od -126 do +127.

Page 37: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

37

Postoji više različitih formata zapisa mantise i eksponenta u binarnom kodu. Danas se gotovo isključivo koristi format koji je određen ANSI/IEEE standardom br.745 iz 1985. godine. Prema tom standardu koriste se dva tipa kodiranja: jednostruki format (32 bita) i dvostruki format (64 bita).

Slika 4.1 Format kodiranja realnih brojeva prema IEEE/ANSI standardu

STANDARDNI IEEE/ANSI FORMAT REALNIH BROJEVA parametar Jednostruki (SINGLE) dvostruki (DOUBLE) ukupan broj bita 32 (+1) 64 (+1) broj bita eksponenta 8 11 broj bita za predznak 1 1 broj bita mantise 23 (+1) 52 (+1) pomak +127 +1023 Emax 255 2047 Emin 0 0 minreal (za nenorm.) (-1)s⋅1.4⋅10-45 (-1)s⋅2.225⋅10-324 minreal (za norm.) (-1)s⋅1.175⋅10-38 (-1)s⋅2.225⋅10-308 maxreal (-1)s⋅3.4028⋅10+38 (-1)s⋅1.797⋅10+308

Tablica 4.2. Standardni IEEE/ANSI format realnih brojeva

Bitna karakteristika ovog standarda je da je u format za kodiranje realnog broja moguće

upisati i šifru o ispravno obavljenoj matematičkoj operaciji (pr. dijeljenje s nulom dalo bi beskonačnu vrijednost, koju je nemoguće kodirati, pa se ta operacija izvještava kao greška). Binarno kodirani signal greške koristi format binarno kodiranih realnih brojeva, ali kako to nije broj, u standardu se opisuje pod nazivom NaN (Not a Number). Kodiranje s normaliziranim zapisom mantise je izvršeno na način da se ne upisuje prva jedinica, čime se podrazumjeva da je mantisa kodirana s jednim bitom više nego je to predviđeno u binarnom zapisu. Vrijednost pomaka i raspona eksponenta dana je tablicom 3.2. Vrijednost eksponenta Emin je iskorištena za kodiranje nule, a vrijednost Emax za kodiranje NaN-a i beskonačnosti.

Zapis formata se interpretira na sljedeći način: 1. Ako je E=Emax i m≠0 kodna riječ predstavlja NaN, bez obzira na vrijedost predznaka s. 2. Ako je E=Emax i m=0 kodna riječ predstavlja (-1)s (∝). 3. Ako je Emin<E<Emax kodna riječ predstavlja broj (-1)s2e-127(1.m), tj. predstavlja

normalizirani realni broj. 4. Ako je E=0 i m=0 kodna riječ predstavlja broj (-1)s(0). 5. Ako je E=0 i m≠0 kodna riječ predstavlja broj (-1)s2ee-127(0.m) tj. predstavlja nenormalizirani

realni broj (vodeća nula se ne zapisuje). Opis nenormaliziranim brojevima ne osigurava točnost za sve brojeve pa se ovo kodiranje u nekim implementacija ne koristi. Vrijednosti za minimalnu i maksimalnu vrijednost realnog broja u tablici 3.2. dani su za normalizirani i nenormalizirani format realnog broja.

Page 38: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

38

Programer, koji programira u višem programskom jeziku, ne mora znati kako se neki broj kodira u procesoru ili memoriji računala. Njega zanimaju pravila za zapis literalnih konstanti, veličina zauzeća memorije, maksimalna i minimalna vrijednost broja, te broj točnih decimala. Normaliziranim zapisom postiže se točnost na 7 decimala za jednostruki format, odnosno 15 decimala za prošireni format.

4.1.4. Kodiranje znakova Znak (eng. character) je element dogovorno usvojenog skupa različitih simbola koji su

namijenjeni obradi podataka (slova abecede, numeričke znamenke, znakovi interpunkcije i sl.). Niz znakova se često tretira kao cjelina i naziva se string. Primjerice, u C jeziku se string literalno zapisuje kao niz znakova omeđen znakom navodnika ("ovo je literalni zapis C stringa").

Za kodiranje slova, znamenki i ostalih tzv. kontrolnih znakova koristi se američki standard za izmjenu informacija ASCII (American Standard Code for Information Interchange). Njemu je ekvivalentan međunarodni standard ISO 7. ASCII kôd se najviše koristi za komuniciranje između računala i priključenih vanjskih jedinica: pisača, crtača, modema, terminala itd.

To je sedam bitni kôd (ukupno 128 različitih znakova), od čega se prva 32 znaka koriste kao kontrolni znakovi za različite namjene, a ostali znakovi predstavljaju slova abecede, pravopisne i matematičke simbole.

000: (nul) 016: (dle) 032: (sp) 048: 0 064: @ 080: P 096: ž 112: p 001: (soh) 017: (dc1) 033: ! 049: 1 065: A 081: Q 097: a 113: q 002: (stx) 018: (dc2) 034: " 050: 2 066: B 082: R 098: b 114: r 003: (etx) 019: (dc3) 035: # 051: 3 067: C 083: S 099: c 115: s 004: (eot) 020: (dc4) 036: $ 052: 4 068: D 084: T 100: d 116: t 005: (enq) 021: (nak) 037: % 053: 5 069: E 085: U 101: e 117: u 006: (ack) 022: (syn) 038: & 054: 6 070: F 086: V 102: f 118: v 007: (bel) 023: (etb) 039: ' 055: 7 071: G 087: W 103: g 119: w 008: (bs) 024: (can) 040: ( 056: 8 072: H 088: X 104: h 120: x 009: (tab) 025: (em) 041: ) 057: 9 073: I 089: Y 105: i 121: y 010: (lf) 026: (eof) 042: * 058: : 074; J 090: Z 106: j 122: z 011: (vt) 027: (esc) 043: + 059: ; 075: K 091: [ 107: k 123: { 012: (np) 028: (fs) 044: , 060: < 076: L 092: \ 108: l 124: | 013: (cr) 029: (gs) 045: - 061: = 077: M 093: ] 109: m 125: } 014: (so) 030: (rs) 046: . 062: > 078: N 094: ^ 110: n 126: ~ 015: (si) 031: (us) 047: / 063: ? 079: O 095: _ 111: o 127: del

Tablica 4.3. ACSII standard za kodiranje znakova

U Hrvatskoj se za latinična slova Č,Š,Ž,Ć i Đ koristi modifikacija ASCII standarda. (tablica 4.4).

HR-ASCII IBM EBCDIC-852 standard dec hex ASCII HR-ASCII 64 40 @ Ž 91 5B [ Š 92 5C \ Đ 93 5D ] Ć 94 5E ^ Č 96 60 ` ž 123 7B { š 124 7C | đ 125 7D } ć 126 7E ~ č

Dec hex EBCDIC-852 166 A6 Ž 230 E6 Š 209 D1 Đ 172 AC Ć 143 8F Č 167 A7 ž 231 E7 š 208 D0 đ 134 86 ć 159 9F č

Tablica 4.4. HR_ASCII i EBCDIC-852 standard

Page 39: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

39

Na IBM PC računalima se koristi 8-bitno kodiranje znakova, čime je omogućeno kodiranje 256 znakova. Ovaj kod se naziva EBCDIC (Extended Binary Coded Decimal Interchange Code). Prvih 128 znakova ovog koda jednaki su ASCII standardu, a ostali znakovi predstavljaju različite grafičke i matematičke simbole te slova koja se koriste u alfabetu većine zapadno-evropskih zemalja, a nisu obuhvaćena ASCII standardom. U sklopu tog standarda predviđeno je da se znakovi Č,Š,Ž,Ć,Đ,č,ć,ž,š,đ kodiraju vrijednostima većim od 127. Taj standard ima oznaku EBCDIC-852 i prikazan je u tablici 4.4. U Windows operativnom sustavu, koji koristi grafičko sučelje, koristi se poseban tip kodiranja, gdje se uz kôd znakova zapisuje i oblik znaka (font). Sve se više koristi prošireni skup znakova koji je određen 16-bitnim Unicode standardom. Njime su obuhvaćeni znakovi iz svih svjetskih jezika.

U C jeziku se imena varijabli i funkcija označavaju nizom znakova, stoga se za

označavanje znakovne konstante koriste jednostruki navodnici, primjerice u C iskazu:

a = 'a';

slovo a se koristi za označavanje varijable s lijeve strane naredbe pridjele vrijednosti, a s desne strane je napisana znakovna konstanta 'a'. Ovom se naredbom se varijabli a pridjeljuje ASCII vrijednost znakovne konstante 'a', odnosno numerička vrijednost 97.

Potrebno je razlikovati sljedeće iskaze:

1) a = 8; 2) a = '8';

U prvom iskazu varijabla a ima numeričku vrijednost 8, a u drugom iskazu varijabla a ima numeričku vrijednost 56, jer se tada varijabli pridjeljuje numerička ASCII vrijednost znaka '8'. U samom procesu programiranja uglavnom nije nužno znati numeričku vrijednost znakovne konstante, već se tada apstraktno uzima da je varijabli a pridijeljen znak '8'.

Neki znakovi iz ASCII skupa se ne mogu zapisati u izvornom kodu, jer za njih ne postoji oznaka na standardnoj tipkovnici. Za slučaj da se pak želi raditi i s takvim znakovima , za njihov unos u C jeziku se koriste specijalne kontrolne sekvence (tzv. escape sequence), koje se zapisuju na način da se napiše obrnuta kosa crta \ i jedan od sljedećih znakova.

\b oznaka za povrat unatrag - backspace BS \f oznaka za stranicu unaprijed - form feed FF \n oznaka nove linije - new line NL \r oznaka za povrat na početak reda CR \t oznaka za horizontalni tab HT \v oznaka za vertikalni tab \” oznaka za dvostruki navodnik \' oznaka za jednostruki navodnik \\ oznaka za znak \ - backslash \ooo ASCII kôd znaka je zapisan oktalnim znamenkama ooo. \0xhh ASCII kôd znak je zapisan heksadecimalnim znamenkama hh.

Primjerice, ekvivalentna su tri zapisa znaka A:

'A', '\0x41', '\101'

Znakovne konstante su u C jeziku zapisuju unutar jednostrukih zagrada znakovima iz ASCII skupa (ili kontrolnom sekvencom). Može ih se tretirati i kao numeričke

Page 40: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

40

vrijednosti cjelobrojnog tipa na način da je vrijednost znakovne konstante jednaka ASCII kodu znaka.

Kada se u C jeziku koriste literalne konstante, koje predstavljaju string (niz znakova), one se zapisuju unutar dvostrukih navodnika, primjerice

"Hello World!\n".

Unutar stringa se kontrolni znakovi zapisuju bez navodnika. U ovom primjeru, na kraju stringa je zapisan znak za prijelaz u novu liniju.

4.1.5. Kodiranje logičkih vrijednosti Moguće su samo su dvije logičke vrijednosti: istina i laž. U većini programskih jezika

(C++, Pascal) ove vrijednosti se zapisuju s “true” i “false”. U C jeziku se ne koristi posebno označavanje logičkih vrijednosti, već vrijedi pravilo da se svaka numerička vrijednost može tretirati kao logička “istina” ako je različita od nule, odnosno “laž” ako je jednaka nuli.

Logičke vrijednosti nastaju i kao rezultat relacijskih izraza u kojima se koriste operatori: > (veće), < (manje), >= (veće ili jednako) ,<= (manje ili jednako), == (jednako) i != (nije jednako). U C jeziku, ako je vrijednost relacijskog izraza logička vrijednost “istina” ona se kodira kao numerička vrijednost 1, a logička vrijednost “laž” se kodira kao numerička vrijednost 0. Primjerice u izrazu

a = x > y;

varijabli a se pridjeljuje vrijednost 1 ako je izraz na desnoj strani istinit (tj. ako je x veće od y), u suprotnome, pridjeljuje se vrijednost 0. Relacijski se odnos dvije vrijednosti pri izvršavanju programa zapravo određuje pomoću operacije oduzimanja, a definiran je u tablici 4.5.

Ako je x == y x != y x > y x < y x <= y x >= y x-y > 0 0 1 1 0 0 1 x-y = 0 1 0 0 0 1 1 x-y < 0 0 1 0 1 1 0

Tablica 4.5 Rezultat izvršenja relacijskih operacija u C jeziku

4.2 Memorija Prije nego se pokaže kako se na apstraktnoj razini manipulira s podacima, koji se nalaze u

radnoj memoriji računala, potrebno je upoznati neke hardverske karakteristike radne memorije računala.

Elektronička memorijska jedinica može pamtiti 1 bit informacije. Više memorijskih jedinica se pakira u integrirani elektronički sklop koji se popularno naziva čip. U računalu se memorijski čipovi spajaju na način da se istovremeno može pristupiti grupi od 8 ili više temeljnih memorijskih jedinica, pa se sa softverskog stajališta može uzeti da jedna memorijska ćelija sadrži 1 bajt digitalne informacije.

Informacije se između procesora i memorije ne prenose serijski, bit po bit, već se vrši istovremeni prijenos više digitalnih znamenki (8, 16, 32 ili 64 bita) pomoću višestrukih električnih vodova, koje se obično naziva sabirnica (eng. bus). Postoje tri tipa sabirnice: adresna sabirnica služi za aktiviranje memorijske ćelije, podatkovna sabirnica služi za prijenos podatka koji će biti spremljen (zapisan) ili dobavljen (pročitan) iz te memorijske ćelije i kontrolna sabirnica kojom se određuje da li i kako se vrši čitanje ili pisanje.

Na slici 3.2. prikazana je pojednostavljena shema spajanja memorijskih čipova. Stanje upisivanja ili čitanja sadržaja memorijske grupe (8,16,32 ili 64 bita) kontrolira se signalom mem

Page 41: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

41

r/w (memory read/write). Usklađenost rada s ostalim sklopovima računala osigurava se na način da se vrijeme upisivanja ili čitanja iz memorije kontrolira posebnim signalima generatora takta. Aktiviranje jedne memorijske grupe određeno je binarnom kombinacijom naponskih stanje adresne sabirnice. Memorija je, stoga, linearno uređen prostor, jer je položaj u memoriji, tj. adresa, proporcionalan numeričkoj vrijednosti binarne kombinacije adresne sabirnice.

Slika 4.2. Pojednostavljeni prikaz sheme spajanja memorijskih čipova

Potrebno je uočiti da svakom podatku u memoriji su pridjeljene dvije vrijednosti: adresa i sadržaj memorije na toj adresi. Veličina se adresne sabirnice i sabirnice podataka iskazuje brojem bita koje oni u jednom trenutku prenose i određena je mogućnostima procesora i hardverskom strukturom računala. Maksimalni memorijski kapacitet je određen izrazom:

Maksimalni memorijski kapacitet = 2veličina adresne sabirnice (bajta)

Tzv. 8-bitni mikroprocesori (Z-80, Motorola 6800, Intel 8080) imaju 16-bitnu adresnu sabirnicu i 8-bitnu sabirnicu podataka, pa im maksimalni memorijski kapacitet iznosi 64 kilobajta. Prva personalna računala klase IBM PC XT imala su adresnu sabirnica od 20 linija i 8-bitnu sabirnica podataka, pa je maksimalni kapacitet memorije tih računala iznosio 1M. Kod današnjih personalnih računala koriste se 32-bitne sabirnice (kod većih računala koriste se 64-bitne sabirnice podataka) pa maksimalni kapacitet memorije može biti 4GB. Realno se u računala ugrađuje znatno manje memorije (64MB-1GB).

Na temelju onoga što je do sada kazano o kodiranju sadržaja i hardverskom ustrojstvu memorije, sa programerskog stajališta, važne su sljedeće činjenice i definicije:

1. Podaci, koji se nalaze na nekoj adresi u memoriji, nazivaju se memorijski objekti. 2. Adresa se opisuje kardinalnim brojem koji opisuje položaj temeljne memorijske ćelije,

veličine 8 bita, u linearno uređenom prostoru memorije. 3. Podaci su najčešće kodirani tako da pripadaju skupu numeričkih vrijednosti ili skupu

znakova koji su kodirani prema ASCII standardu, ili im se pridodaje logička vrijednost {true, false}.

4. Memorijskim objektima procesor može mijenjati sadržaj i s njima obavljati aritmetičko-logičke operacije.

5. Memorijski objekti, s kojima procesor operira kao s cjelinom, nazivaju se varijable.

Page 42: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

42

4.3 Prosti tipovi podataka U uvodu je naglašeno da ime varijable simbolički označava položaj u memoriji (adresu) na

kojem je upisana vrijednost varijable. Sada ćemo uz pojam varijable uvesti i pojam tipa varijable.

U matematici je uobičajeno uz matematičke izraze navesti i skup vrijednosti varijabli za koje ti izrazi vrijede. Primjerice, neka varijabla x pripada skupu realnih brojeva (x ∈ R), a varijabla z skupu kompleksnih brojeva (z ∈ Z). Tada vrijedi:

( ) ( )z

xz

xj

zx

= +Re Im

Vrijednost ovog izraza također pripada skupu kompleksnih brojeva. Bitno je uočiti da oznaka pripadnosti skupu vrijednosti određuje način kako se računa matematički izraz i kojem skupu vrijednosti pripada rezultat izraza. U programskim se jezicima pripadnost skupu koji ima zajedničke karakteristike naziva tipom. Tip varijable određuje način kodiranja, veličinu zauzimanja memorije i operacije koje su sa tom varijablom dozvoljene. Tipovi varijabli (ili konstanti) koji se koriste u izrazima određuju tip kojim rezultira taj izraz. U većini programskih jezika se koriste sljedeći tipovi podataka: • numerički (cjelobrojni ili realni), • logički, • znakovni, a nazivaju se i primitivni ili prosti tipovi jer predstavljaju nedjeljive objekte koji imaju izravan prikaz u memoriji računala.

Tip Oznaka u C jeziku

Interval vrijednosti

zauzeće Memorije

znakovni tip

[signed] char Unsigned char

-127 .. 128 0 .. 255

1 bajt 1 bajt

cjelobrojni tip

[signed] int [signed] short [signed] long

-2147483648.. 2147483647 -32768 .. 32767 -2147483648.. 2147483647

4 bajta 2 bajta 4 bajta

kardinalni tip

unsigned [int] unsigned short unsigned long

0 .. 4294967295 0 .. 65535 0 .. 4294967295

4 bajta 2 bajta 4 bajta

realni tip (jednostruki format)

float

min ± 1.175494351e-38 maks ± 3.402823466e+38

4 bajta

realni tip (dvostruki format)

double min ± 2.2250738585072014e-308 maks ± 1.7976931348623158e+308

8 bajta

logički tip - 0 .. različito od nule -

Tablica 4.6. Označavanje standardnih tipova podataka u C jeziku (uglate zagrade označavaju opcioni element)

Tablica 4.6 prikazuje karakteristike standardnih tipove podataka koji se koriste u C jeziku;

ime kojim se označavaju, interval vrijednosti i veličina zauzeća memorije u bajtima. Uglate zagrade označavaju opciona imena tipova, primjerice može se pisati

int ili signed int unsigned ili unsigned int

Page 43: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

43

Oznake tipova su uzete iz leksike engleskog jezika: int je kratica leksema integer (cijeli broj), char je kratica leksema character (znak), signed je pridjev koji označava skup cijelih brojeva u kojem se koristi predznak (+ ili -), a unsigned je pridjev koji označava da se iz skupa cijelih brojeva koriste samo brojevi bez predznaka (kardinalni brojevi), float je kratica od floating point (realni broj kodiran prema IEEE standardu jednostrukog formata), a double znači dvostruko u smislu dvostruko preciznog IEEE formata zapisa realnih brojeva.

Uz pojam varijable uvijek su vezani pojmovi: tip i ime. Ime predstavlja oznaku adrese varijable u memoriji, pa se zove i referenca varijable, ili referenca memorijskog objekta. Tip označava skup vrijednosti varijable, veličinu zauzeća memorije, način kodiranja i operacije koje se mogu primijeniti.

Pridjeljivanje oznake tipa nekoj varijabli naziva se deklaracija varijable, a ako se pod tom deklaracijom podrazumijeva i rezerviranje memorijskog prostora u kojem će biti smještena vrijednost varijable, onda se kaže da je deklaracijom izvršena i definicija varijable. U C jeziku uvijek mora biti deklariran tip varijable prije nego sa ona upotrijebi u programskim iskazima.

Primjer: Program, imena povrsina.c, služi za proračun površine kruga. U njemu se koriste se dvije varijable tipa double: r označava radijus, a P površinu kruga. Pretpostavlja se da je radijus poznat i neka iznosi 2.1 m. Rezultat proračuna će biti ispisan pomoću printf() funkcije. Evo kako je napisan taj program.

/* Datoteka: povrsina.c */ /* Program koji računa površinu kruga radijusa 2.1m */ #include <stdio.h> int main() { double r, P; /* deklaracija varijabli r i P */ r = 2.1; /* zadana vrijednost radijusa 2.1m */ P = r*r*3.14; /* proračun površine kruga */ printf( "\n Povrsina kruga = %f m", P); return 0; }

Kada se program kompilira i izvrši dobije se ispis:

Povrsina kruga = 13.847400 m

Komentarima je opisan značaj pojedinog programskog iskaza. Najprije je izvršena deklaracija varijabli r i P, prema pravilu da se oznaka tipa napiše ispred imena varijabli (ako se istovremeno deklarira više varijabli istoga tipa, njihova se imena odvajaju zarezom). Zapis deklaracije završava znakom točka-zarez. Nadalje, naredbom pridjele vrijednosti postavljena je vrijednost varijable r na zadanu vrijednost 2.1. Proračun površine je izvršen prema izrazu 3.14*r*r, gdje zvjezdica označava operator množenja. U matematici formula za površinu glasi r2π. Ovu formulu se ne može izravno primijeniti jer u C jeziku ne postoji operator potenciranja.

Page 44: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

44

Rezultat proračuna je pridijeljen varijabli P. Za ispis vrijednosti te varijable koristi se standardna funkcija printf(). Potrebno je uočiti razliku od zapisa printf() funkcije koji je korišten u programu Hello.c. U ovom slučaju funkcija printf() koristi dva argumenta. Prvi argument je literalni string, a drugi argument je varijabla P čiju vrijednost treba ispisati. Položaj na kojem će se ispisati ta vrijednost označen je unutar literalnog stringa oznakom %d. Ova oznaka se naziva specifikator ispisa. Argumenti su odvojeni zarezom.

Opći oblik korištenja printf funkcije je:

printf(format_ispisa, lista_argumenata)

gdje lista_argumenata sadrži nula ili više argumenata funkcije odvojenih zarezom, a položaj gdje će biti izvršen ispis vrijednosti tih argumenta označava se u stringu format_ispisa specifikatorom ispisa. Broj specifikatora ispisa mora biti jednak broju argumenata funkcije koji su navedeni u lista_argumenata. Specifikatori ispisa moraju odgovarati tipu argumenta, primjerice za cjelobrojne argumente ( tip: int, short, long) oznaka je %d, za znakovne argumente (tip char) oznaka je %c, za realne argumente (tip: float, double) oznaka je %f ili %g, a za ispis stringa oznaka je %s. Argumenti funkcije mogu biti imena varijabli, konstante i izrazi koji rezultiraju nekom vrijednošću.

Sada će sa nekoliko programa biti pokazane karakteristike standardnih tipova C jezika. Prvi program sizeof.c ispisuje koliko pojedini tip zauzima memorije.

/* Datoteka: sizeof.c */ /* Program ispisuje zauzeće memorije za sve proste tipove C jezika */ #include <stdio.h> int main() { printf( "\nSizeof(char) = %d", sizeof( char )); printf( "\nSizeof(int) = %d", sizeof( int )); printf( "\nSizeof(short) = %d", sizeof( short )); printf( "\nSizeof(long) = %d", sizeof( long )); printf( "\nSizeof(float) = %d", sizeof( float )); printf( "\nSizeof(double) = %d", sizeof( double )); printf( "\nSizeof(unsigned char) = %d", sizeof( unsigned char )); printf( "\nSizeof(unsigned int) = %d", sizeof( unsigned int )); printf( "\nSizeof(unsigned short) = %d", sizeof( unsigned short )); printf( "\nSizeof(unsigned long) = %d\n", sizeof( unsigned long )); return 0; }

Kada se kompilira i izvrši, program daje ispis:

Sizeof(char) = 1 Sizeof(int) = 4 Sizeof(short) = 2 Sizeof(long) = 4 Sizeof(float) = 4 Sizeof(double) = 8 Sizeof(unsigned char) = 1 Sizeof(unsigned int) = 4 Sizeof(unsigned short) = 2 Sizeof(unsigned long) = 4

Page 45: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

45

U ovom se programu koristi standardni C operator sizeof koji daje vrijednost u bajtima, koju neki tip ili prethodno deklarirana varijabla zauzima u memoriji. Primjerice, sizeof(short int) daje vrijednost 2.

Konstante C jezika indirektno u svom zapisu sadrže oznaku tipa. Primjeri su dani u tablici 4.7. Uočite da sufiks U ili u označava konstante kardinalnog tipa, sufiks f ili F označava realne brojeve jednostrukog formata, sufiks L ili l označava brojeve dvostrukog formata.

char ili int

'A' 'a' '\035' '\x29' '\n'

znakovna konstanata A znakovna konstanata a znakovna konstanata 35 u oktalnoj notaciji znakovna konstanata 29 u heksadecimalnoj notaciji znak za novu liniju

Int 156 0234 0x9c

decimalna notacija oktalna notacija cjelobrojne konstante heksadecimalna notacija

unsigned 156U 0234U 0x9cU

Decimalno oktalno (prefiks U određuje kardinalni broj) heksadecimalno

float

15.6F 1.56e1F

realni broj – jednostruki format određen primjenom sufiksa F ili f

double 15.6 1.56E1L

konstante su tipa "double" ukoliko se ne koristi prefiks F. Nije nužno pisati sufiks L.

Tablica 4.7 Literalni zapis konstanti

U tablici 7.7 važno je uočiti da su znakovne konstante kompatibilne i sa znakovnim i s cjelobrojnim tipom. To demonstrira program charsize.c.

/* Datoteka: charsize.c */ /* Program kojim se ispituje tip znakovne konstante */ #include <stdio.h> int main() { char c; /* deklaracija varijable c tipa char*/ int x; /* deklaracija varijable x tipa int*/ c = 'A'; /* objema varijablama može se pridijeliti */ x = 'A'; /* vrijednost znakovne konstante 'A' */ printf( "\n c = %c", c); printf( "\n Sizeof c = %d", sizeof (c) ); printf( "\n x = %d", x); printf( "\n Sizeof x = %d", sizeof(x)); printf( "\n Sizeof 'A' = %d", sizeof('A')); return 0; }

koji daje ispis:

c = A Sizeof c = 1 x = 65 Sizeof x = 4 Sizeof 'A' = 4

Page 46: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

46

U programu su prvo deklarirane dvije varijable; c je tipa char, a x je tipa int. Objema varijablama je zatim pridijeljena vrijednost znakovne konstante 'A'. Prilikom ispisa varijable c koristi se specifikator ispisa za znakove - %c, pa se ispisuje slovo A. Ispis vrijednosti varijable x daje cjelobrojnu vrijednost, koja je jednaka vrijednosti ASCII koda znaka A. Očito je da kompilator tretira znakovnu konstantu ovisno o kontekstu u kojem se koristi, a za nju rezervira u memoriji mjesto od 4 bajta, što pokazuje posljednji ispis. Ova dvostrukost primjene znakovnih konstanti zapravo je dobra strana C jezika. Time je omogućeno da se znakovne konstante mogu koristiti i kao simboli i kao numeričke vrijednosti. Simbolički značaj konstante važan je pri unosu i ispisu znakova, numerički značaj omogućuje da se znakovne konstante mogu koristiti u aritmetičkim i relacijskim izrazima na isti način kao i cjelobrojne konstante.

4.4 Direktiva #define Leksička se vrijednost numeričkih, znakovnih i literalnih konstanti, pa i samog

programskog teksta, može pridijeliti nekom simboličkom imenu pomoću pretprocesorske direktive #define. Primjerice, ako se zapiše:

#define PI 3.141592653589793

to ima efekt da kada se u tekstu programa, koji slijedi iza ove direktive, zapiše PI, vrijedi kao da je zapisano 3.141592653589793.

Do sada smo upoznali direktivu #include. Sve pretprocesorske direktive počinju znakom #. Njih se ne smatra programskim naredbama pa se iza njih ne zapisuje znak točka-zarez. Pomoću njih se vrši leksička supstitucija teksta iz drugih datoteka ( pomoću #include) ili prema supstitucijskom pravilu koji je opisan direktivu #define .

Općenito direktivu #define ima oblik:

#define IME supstitucijski_tekst

gdje IME predstavlja proizvoljan naziv zapisan neprekinutim nizom znakova (obično se zapisuje velikim slovima i podvlakom), a supstitucijski_tekst može sadržavati i isprekidani niz znakova. Primjerice,

#include <stdio. h> #define MESSAGE "Vrijednost broja pi = " #define PI 3.141592653589793 #define PR_NL printf("\n") int main( void) { printf(MESSAGE); printf("%f" ,PI); PR_NL; return 0; }

Nakon izvršenja dobije se ispis

Vrijednost broja pi = 3.141592653589793

Kasnije će biti detaljnije pojašnjen rad s pretprocesorskim direktivama.

Page 47: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

47

Na kraju ovog poglavlja pogledajmo program Limits.c. Njime se ispisuje minimalna i maksimalna vrijednost za sve proste tipove podataka C jezika. U programu se koriste simboličke konstante koje su pomoću direktive #define zapisane u standardnim datotekama limits.h i float.h.

/* Datoteka: Limits.c */ /* Ispisuje interval vrijednost numeričkih tipova */ #include <stdio.h> #include <limits.h> #include <float.h> int main( void ) { printf("%12s%12s%15s%15s\n", "Tip", "Sizeof", "Minimum", "Maksimum"); printf("%12s%15d%15d\n","char", CHAR_MIN, CHAR_MAX) ; printf("%12s%15d%15d\n","short int", SHRT_MIN, SHRT_MAX) ; printf("%12s%15d%15d\n","int", INT_MIN, INT_MAX) ; printf("%12s%15ld%15ld\n","long int", LONG_MIN, LONG_MAX) ; printf("%12s%15g%15g\n", "float", FLT_MIN, FLT_MAX) ; printf("%12s%15g%15g\n", "double", DBL_MIN, DBL_MAX) ; printf("%12s%15Lg%15Lg\n","long double",LDBL_MIN, LDBL_MAX) ; return 0 ; }

Nakon izvršenja dobije se ispis:

Tip Sizeof Minimum Maksimum char 1 -128 127 short int 2 -32768 32767 int 4 -2147483648 2147483647 long int 4 -2147483648 2147483647 float 4 1.17549e-038 3.40282e+038 double 8 2.22507e-308 1.79769e+308 long double 8 2.22507e-308 1.79769e+308

Ovaj ispis izgleda uredno. To je postignuto korištenjem specifikatora ispisa printf() funkcije u proširenom obliku, tako da je između znaka % i oznake tipa ispisa upisan broj kojim se određuje točan broj mjesta za ispis neke vrijednosti.

4.5 Specifikatori printf funkcije Kada se ispis vrši po unaprijed određenom obliku i rasporedu kažemo da se vrši formatirani

ispis. Sada će biti pokazano kako se zadaje format ispisa printf() funkcije. Općenito format se zadaje pomoću šest polja:

%[prefiks][širina_ispisa][. preciznost][veličina_tipa]tip_argumenta Format mora započeti znakom % i završiti s oznakom tipa argumenta. Sva ostala polja su opciona (zbog toga su napisana unutar uglatih zagrada).

U polje širina_ispisa zadaje se minimalni broj kolona predviđenih za ispis vrijednosti. Ako ispis sadrži manji broj znakova od zadane širine ispisa, na prazna mjesta se ispisuje razmak. Ako ispis sadrži veći broj znakova od zadane širine, ispis se proširuje. Ako se u ovo polje

Page 48: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

48

upiše znak * to znači da će se broj kolona indirektno očitati iz slijedećeg argumenta funkcije, koji mora biti tipa int.

Polje prefiks može sadržavati jedan znak koji ima sljedeće značenje:

- Ispis se poravnava prema lijevoj granici ispisa određenog poljem širina_ispisa. (inače se poravnava s desne strane) U prazna mjesta se upisuje razmak

+ Pozitivnim se vrijednostima ispisuje i '+' predznak. razmak Ako je vrijednost pozitivna, dodaje se razmak prije ispisa (tako se može poravnati kolone s

pozitivnim i negativnim brojevima). 0 Mjesta razmaka ispunjaju se znakom 0. # Alternativni stil formatiranja

Polje .preciznost određuje broj decimalnih znamenki iza decimalne točke kod ispisa realnog broja ili minimalni broj znamenki ispisa cijelog broja ili maksimalni broj znakova koji se ispisuje iz stringa. Ovo polje mora započeti znakom točke, a iza nje se navodi broj ili znak *, koji znači da će se preciznost očitati iz slijedećeg argumenta tipa int. Ukoliko se ovo polje ne koristi, tada se podrazumijeva da će realni brojevi biti ispisano s maksimalno šest decimalnih znamenki iza decimalne točke.

Polje tip_argumenta može sadržavati samo jedan od sljedećih znakova:

c Argument se tretira kao int koji se ispisuje kao znak iz ASCII skupa. d, i Argument se tretira kao int, a ispisuje se decimalnim znamenkama. e, E Argument je float ili double, a ispis je u eksponentom formatu. F Argument je float ili double, a ispis je prostom decimalnom formatu. Ako je prefiks # i

preciznost .0, tada se ne ispisuje decimalna točka. g, G Argument je float ili double, a ispis je prostom decimalnom formatu ili u

eksponencijalnom formatu, ovisno o tome koji daje precizniji ispis u istoj širini ispisa. o Argument je unsigned int, a ispisuje se oktalnim znamenkama. p Argument se tretira kao pokazivač tipa void *, pa se na ovaj način može ispisati adresa bilo

koje varijable. Adresa se obično ispisuje kao heksadecimalni broj. s Argument mora biti literalni string odnosno pokazivač tipa char *. u Argument je unsigned int, a ispisuje se decimalnim znamenkama. x, X Argument je unsigned int, a ispisuje se heksadecimalnim znamenkama. Ako se zada

prefiks # , ispred heksadecimalnih znamenki se ispisuje 0x ili 0X.

Polje veličina_tipa može sadržavati samo jedan znak koji se upisuje neposredno ispred oznake tipa.

h Pripadni argument tipa int tretira se kao short int ili unsigned short int. l Pripadni argument je long int ili unsigned long int. L Pripadni argument realnog tipa je long double.

Primjeri korištenja printf() funkcije dani su u programu printf.c.

/* Datoteka: printf.c */ /* Primjer korištenja printf funkcije */ #include<stdio.h> int main() {

Page 49: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

49

printf("%d, %5d, %-5d, %05d, %5.5d\n", 1, 2, 3, 4, 5); printf("%o %x %X %#o %#x\n", 171, 171, 171, 171, 171); printf("%f %e %g\n", 3.14, 3.14, 3.14); printf("%s, %.5s!\n", "Hello", "worldly"); printf("%0*d, %.*f, %*.*s\n", 2, 3, 4, 5.6, 7, 3, "abcdef"); return 0; }

Ovaj pogram daje ispis:

1, 2, 3 , 00004, 00005 253 ab AB 0253 0xab 3.140000 3.140000e+000 3.14 Hello, world! 03, 5.6000, abc

U prethodnim tablicama za oznake tipa argumenata 'p' i 's' naglašeno je da su argumenti funkcije pokazivači. Što je to pokazivač, bit će objašnjeno u slijedećem odjeljku.

4.6 Pristup podacima pomoću pokazivača Jedan od glavnih razloga zašto se C jezik smatra jezikom niske razine je taj što omogućuje

korištenje numeričke vrijednosti adresa varijabli i funkcija za indirektno manipuliranje s podacima i izvršenjem programa. U tu svrhu koriste se posebni operatori - adresni operator & i operator indirekcije *, te specijalni tip varijabli koje se nazivaju pokazivačke varijable ili pokazivači (eng. pointer).

Adresni operator &

Prije je naglašeno da ime varijable ujedno označava i adresu varijable. Koja je to adresa? Brigu o tome vodi kompilator. Adresa varijable se može odrediti pomoću posebnog operatora & koji se naziva adresni operator. Koristi se kao unarni operator koji se zapisuje ispred imena varijable. Pogledajmo primjer programa adresa.c.

/* Datoteka: adresa.c */ /* Primjer ispisa adrese varijable */ #include<stdio.h> int main() { int y; y = 777; printf("\n Vrijednost varijable je %d", y); printf("\n Adresa varijable je %#p", &y); return 0; }

Ispis programa može izgledati ovako:

Vrijednost varijable y je 777 Adresa varijable y je 0x0063FDF4

Page 50: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

50

Za ispis adrese varijable korištena je naredba

printf("\n Adresa varijable je %#p", &y);

Uočimo da je ispred imena varijable zapisan adresni operator &. Ispis adrese je izvršen u heksadecimalnim obliku s osam znamenki, jer je korišteno 32-bitno računalo na kojem je adresa određena 32-bitnim kardinalnim brojem.

Napomena: pri ponovljenom izvršenju programa ispis adrese ne mora biti isti jer operativni sustav ne učitava program uvijek na isto mjesto u memoriji (time se mijenja i adresa na kojoj se nalazi varijabla x).

Operator indirekcije *

Komplementarno adresnom operatoru koristi se unarni operator indirekcije koji se označava znakom *. Zapisuje se ispred izraza čija vrijednost predstavlja neku adresu. Značaj operatora indirekcije je u tome da se pomoću njega dobije vrijednost koja je upisana na toj adresi. To znači da

ako je y jednako 777, onda je *(&y) također jednako 777

jer operator indirekcije daje vrijednost koja se nalazi na adresi varijable y. Ovo pravilo se može provjeriti tako da se u prethodnom programu napiše naredba:

printf("\n Vrijednost dobivena indirekcijom je %d", *(&y) );

Tada se dobije ispis:

Vrijednost varijable je 777 Adresa varijable je 0X0063FDF4 Vrijednost dobivena indirekcijom je 777

Napomena: izraz *(&y) se može pisati i bez zagrada *&y, međutim , obično se pišu zagrade zbog lakšeg uočavanja redoslijeda djelovanja operatora.

Pokazivači

Varijable kojima je vrijednost adresa neke druge varijable ili funkcije nazivaju se pokazivači ili pointeri. Pokazivači moraju biti deklarirani, jer C kompilator mora znati kakav će tip podatka biti na adresi koju oni sadrže, ili kako se češće kaže, mora biti poznat tip objekta na kojeg oni pokazuju.

Deklaracija pokazivača vrši se slično deklaraciji varijable, s razlikom što se između oznake tipa i imena pokazivača obvezno zapisuje znak indirekcije '*'. Primjerice,

int *p; /* p je pokazivač na objekt tipa int */ unsigned *q; /* q je pokazivač na objekt tipa unsigned */

Ovim deklaracijama definirane su dvije pokazivačke varijable. Njihova vrijednost je neodređena, jer im nije pridijeljena adrese nekog realnog memorijskog objekta. Važno je znati da pokazivače prije upotrebe treba inicijalizirati, odnosno mora im se pridijeliti vrijednost adrese postojećeg memorijskog objekta. Pri tome, tip pokazivača mora biti jednak tipu memorijskog objekta. To se ostvaruje adresnim operatorom '&'. Primjerice, u programu

int suma; /* deklaracija varijable suma */ int *p; /* deklaracija pokazivača na objekt tipa int */ sum = 777; /* inicijalizacija varijable suma */ p = &suma; /* p inicijaliziran na adresu varijable suma */

Page 51: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

51

najprije je izvršena deklaracija varijable suma i pokazivača p. Zatim je varijabli suma pridijeljena vrijednost 777, a pokazivač p je inicijaliziran da pokazuje na tu varijablu. Ako bi dalje koristili naredbe

printf ("%d", suma); printf ("%d", *p);

dobili bi isti ispis, jer se indirekcijom pokazivača dobiva vrijednost na koju on pokazuje, a to je vrijednost varijable suma (*p ⇔*&suma).

memorijska adresa

sadržaj memorije

ime varijable

0x09A8 0x09AC 0x09B0 ...... ...... ...... 0x1000 0x1004

...... 777

......

......

......

...... 0x09AC ......

...... suma ...... ...... ...... ......

p ......

Slika 4.3. Prikaz memorijskog sadržaja i adresa varijable suma i pokazivača p

Stanje u memoriji prikazano je na slici 4.3. Strelicom je označeno da je vrijednost pokazivača p jednaka adresi varijable suma. Vrijednost adresa je napisana proizvoljno, jer se ne može unaprijed znati na kojoj će se adresi nalaziti podaci. Zbog toga se češće za opis operacija s pokazivačima koristi sljedeća simbolika. Varijabla se označava kao pravokutni objekt. Unutar pravokutnika upisuje se vrijednost varijable (ako je vrijednost neodređena upisuje se znak ?), a ime varijable se upisuje uz pravokutnik. Sadržaj pokazivačke varijable se označava strelicom koja pokazuje na neki drugi objekt. U skladu s ovakvom simbolikom prije opisane operacije se mogu predstaviti slikom 4.4.

Slika 4.4. Prikaz operacija s pokazivačem na varijablu

Važna osobina indirekcije pokazivača je da se može koristiti u svim izrazima u kojima se koriste i obične varijable. Primjerice, naredbom

*p = 678;

Page 52: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

52

je iskazano da se vrijednost 678 pridijeli memorijskom objektu na kojeg pokazuje pokazivač p. Pošto je prije određeno da je taj objekt varijabla suma, onda ovaj izraz ima isti efekt kao da je korištena naredba:

suma = 678;

Ovime je demonstriran glavni razlog korištenja pokazivača, a to je da se pomoću pokazivača može manipulirati sa svim memorijskim objektima.

U slučaju kada se pokazivač koristi samo za bilježenje adresa i kad nije predviđeno da se koristi u izrazima on se može deklarirati posebnom oznakom tipa

void *p;

što znači da je deklariran pokazivač koji pokazuje na “bilo što”, ili da je deklariran “netipski” pokazivač. Riječ void se u C jeziku koristi s značenjem kojeg je teško prevesti na hrvatski, jer sama riječ void ima višestruko značenje – prazan , slobodan i nevažeći.

Često se koristi naziv nul pokazivač za pokazivače kojima je vrijednost jednak nuli. Njihova upotreba je vrlo opasna jer pokazuju na početni dio memorije, odnosno na područje memorije gdje nije smješten korisnički program, već programi ili podaci operativnog sustava. Stoga nije čudo da programi u kojima se greškom koristi nul pokazivači mogu potpuno “uništiti” operativno stanje računala.

Iz prethodnog izlaganja očito je da su pokazivači varijable, jer im se sadržaj može mijenjati.

To je razlog da se u C jeziku češće koristi pojam memorijski objekt nego varijabla za objekte kojima se može mijenjati sadržaj. Memorijskom se objektu može pristupiti pomoću imena ili posredno pomoću pokazivača.

Imena označavaju fiksni položaj u memorija i često se kaže da predstavljaju referencu memorijskog objekta ili referencu. U skladu s ovim nazivljem, kada se ispred pokazivača primijeni operator indirekcije, kaže se da je to dereferencirani pokazivač.

Rad s pokazivačima predstavlja važan element programiranja C jezikom. Slučajevi kada se koriste, i zašto se koriste, biti će objašnjeni tek kada se upoznaju temeljni elementi programiranja C jezikom.

4.7 Unos podataka u memoriju računala Do sada smo razmatrali kako su podaci smješteni u računalu i kako se njihova vrijednost

može predočiti korisniku programa pomoću printf() funkcije. Sada će biti pokazano kako se podaci mogu unijeti u memoriju računala, u slučaju kada se unos podataka vrši pomoću tipkovnice. U tu svrhu se može koristiti funkcija scanf().

Funkciju scanf() ćemo koristiti u obliku:

scanf(format_unosa, lista_adresnih_izraza);

format_unosa je literalni string u kojem se zadaju specifikatori tipa objekta čija se vrijednost unosi. Oni su gotovo identični specifikatorima ispisa kod printf() funkcije.

lista_adresnih_izraza je niz izraza odvojenih zarezom, čija vrijednost predstavlja adresu postojećeg memorijskog objekta. Tip objekta mora odgovarati specifikaciji tipa prethodno zapisanog u formatu_unosa.

Page 53: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

53

Jednostavni programi u kojima ćemo koristiti funkciju scanf() imat će sljedeći oblik:

/* Datoteka: scanf1.c */ /* Obrazac unosa podataka pomoću scanf() funkcije*/ #include <stdio.h> int main(void) { /* 1. definiraj varijablu čiju vrijednost će unositi korisnik */ int unos; /* 2. ispiši poruku korisniku da se program očekuje unos : */ printf("Molim otipkajte jedan cijeli broj >"); /* 3. Pozovi funkciju scan s agumentom koji je adresa varijabe*/ scanf("%d", &unos); /* 4. obavi radnje s tom varijablom ......*/ /* 5. ispiši rezultat obrada*/ printf("\nOtkucali ste broj %d.\n", unos); return 0; }

Kada se pokrene, ovaj program ispisuje poruku: c:> Molim otipkajte jedan cijeli broj >_

i čeka da korisnik otkuca jedan broj. Unos završava kada korisnik pritisne tipku <Enter>. Primjerice, ako korisnik otipka 12345<Enter>, program će završiti s porukom:

Otkucali ste broj 12345.

Važno je zapamtiti da argumenti funkcije scanf() moraju biti izrazi čija je vrijednost adresa. U prethodnom primjeru to je adresa varijable unos. Adresa se dobije primjenom adresnog operatora & na varijablu unos.

Obično se u format_unosa ne upisuje nikakvi dodatni tekst, kao što je bio slučaj kod printf() funkcije, iako je to dozvoljeno. Razlog tome je činjenica da se tada od korisnika očekuje da otipka i taj dodatni tekst. Primjerice, ako bi se koristila naredba

scanf("Broj=%d", &unos);

i ako se želi unijeti vrijednost 25, onda korisnik mora otipkati "Broj=25<Enter>". Ako bi otkucao samo broj 25, funkcija scanf() ne bi dala ispravan rezultat.

Pomoću scanf() funkcije može se odjednom unijeti više vrijednosti, primjerice unos jednog cijelog broja, jednog realnog broja i jednog znaka, može se ostvariti samo s jednim pozivom funkcije scanf();

int i; double x; char c; .... scanf("%d%f%c", &i, &x, &c);

Pri unosu cijelih i realnih brojeva, funkcijom scanf(), podrazumijeva se da unos broja završava tzv. “bijelim” znakovima (razmak, tab, nova linija). Svi bijeli znakovi uneseni ispred

Page 54: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

54

broja se odbacuju. To nije slučaj kada se unosi znak, jer i bijeli znakovi predstavljaju znak. Stoga, pri unosu znaka potrebno je, u formatu zapisa, eksplicitno zadati razmak od prethodnog unosa.

Razmotrimo prijašnji primjer i pretpostavimo da korisnik želi unijet cijeli broj 67, realni broj 3.14 i znak 'Z'. Prema zadanom formatu on bi morao otkucati:

86 3.14Z

dakle, znak bi trebalo otipkati bez razmaka od prethodnog broja. Problem se može riješiti tako da se u format upisa unese razmak:

scanf("%d%f% %c", &i, &x, &c);

iako i ovo može stvarati probleme u komuniciranju s korisnikom, jer se smije koristiti samo jedan razmak. Primjerice ako bi korisnik otipkao:

86 3.14 Z

ne bi bio unesen znak 'Z' već znak razmaka, jer su ispred znaka 'Z' dva mjesta razmaka.

Navedeni problemi su razlog da programeri rijetko koriste scanf() funkciju za komuniciranje s korisnikom. Kasnije će biti pokazano da je za unos znakova pogodnije koristiti neke druge funkcije. Također, bit će pokazano kako dijagnosticirati da li je izvršen unos koji odgovara zadanom tipu varijable.

4.8 Inicijalizacija varijabli Temeljni uvjet korištenja neke varijable je da ona prethodno mora biti deklarirana. Samom

deklaracijom nije određeno i početna (inicijalna) vrijednost varijable, pa prije korištenja varijable u izrazima, treba joj pridijeliti neku početnu vrijednost. Primjerice, u dijelu programa

int main() { int y, x; /* deklaracija varijabli x i y */ x = 77; /* početna vrijednost varijable x */ y = x + 7; /* početna vrijednost varijable y */ ...

koriste se dvije varijable: x i y. Početno je varijabli x pridijeljena vrijednost 77, i pomoću nje je određena početna vrijednost varijable y. Kada ne bi bila određena vrijednost od x, program bi se kompilirao bez dojave pogreške, ali tada bi pri izvršenju programa bila neodređena vrijednost varijable y.

Određivanje početnih vrijednosti varijabli važan je element programiranja. Prilikom izrade većih programa, ukoliko se koriste neinicijalizirane varijable, mogu nastati greške koje je teško otkriti. U C jeziku se početna vrijednost varijable može odrediti i u samoj deklaraciji. Primjerice, prethodni program se može napisati u obliku:

int main() { int y, x = 77; /* deklaracija varijabli x i y */ /* i inicijalizacija x na vrijednost 77 */ y = x + 7; /* početna vrijednost varijable y */

Inicijalizacija varijable je deklaracija u kojoj se određuje početna vrijednost varijable.

Page 55: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

55

5 Uvod u programiranje C jezikom

Naglasci:

• postupak izrade programa • algoritamska struktura programa u C jeziku • složena naredba, if-else selekcija i while-petlja • standardne i korisničke funkcije • definiranje prototipa funkcije • zaglavlje i tijelo funkcije • formalni i stvarni argumenti funkcije • razvoj jednostavnih algoritama

U prethodnom je poglavlju opisano nekoliko jednostavnih C programa. Cilj je bio

upoznati standardne tipove podataka i jednostavne postupke komuniciranja s korisnikom u dobavi i ispisu podataka. Sada će biti opisana algoritamska i funkcionalna struktura C programa te postupci izrade jednostavnih programa.

5.1 Postupak izrade programa Izrada se programa može opisati kao aktivnost koja se odvija u četiri temeljna koraka:

1. Definiranje zadatka i analiza problema. 2. Izrada detaljne specifikacije i uputa za rješenje problema. 3. Pisanje programa, dokumentacije i formiranje izvršnog programa. 4. Testiranje programa.

Programer treba znati: • mogućnosti programskog jezika, • kako obraditi problem:

o definiranje objekata obrade (podaci), o definiranje postupaka obrade (apstraktni i programski algoritmi), o definiranje korisničkog sučelja za unos podataka i prezentiranje rezultata

obrade. • kako metodološki pristupiti razradi programa (strukturalno programiranje, modularno

programiranje, objektno orijentirano programiranje), • kako optimalno iskoristiti računarske resurse i mogućnosti operativnog sustava

računala, • koje softverske alate koristiti za razvoj programa.

Većina od ovih pitanja bit će obrađena u narednim poglavljima.

Postupak izrade manjih programa se može prikazati i dijagramom toka na slici 5.1.

(korišteni su standardnim elementi za opis dijagrama toka, a opisani su u Dodatku 1).

Page 56: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

56

Slika 5.1. Postupak izrade manjih programa

Bitno je uočiti: o Izradi programa prethodi analiza problema i izrada algoritama za rješenja problema o Tijekom pisanja programa često je potrebno ispravljati sintaktičke pogreške. o Ukoliko se program ne izvršava u potpunosti, moguće je postojanje pogreške u

korištenju računarskih resursa (pr. u korištenju memorije). Postojanje takovih pogrešaka se ispituje posebnim programima – dibagerima (eng. debugger).

o Postupak programiranja ne može biti završen ako program pri izvršavanju iskazuje nelogične rezultate. Tada ne preostaje ništa drugo nego da se krene od početka i da se ponovo kritički sagleda zadatak programiranja.

Postupci izrade velikih programa, koji obrađuju kompleksne sustave, ovdje neći biti razmatrani.

Page 57: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

57

5.2 Algoritamska struktura C programa? U uvodnom su poglavlju opisani temeljni oblici zapisa programskih algoritma. Oni se

sastoje od naredbi koje se izvršavaju jedna za drugom (sekvence), od naredbi selekcije i iterativnih naredbi (petlji). Kroz niz primjera bit će pokazano kako se ove naredbe zapisuju u C jeziku.

Posebnu pažnju posvetit će se problemu koji se obrađuje. Prvo će se definirati zadatak, a zatim će se vršiti analiza problema. Moguće rješenje iskazat će se podesnim algoritmom. Zatim će biti pokazano kako se izvršenje tog algoritam može ostvariti programom napisanim u C jeziku. Na kraju će se analizirati napisani program i rezultati koje on iskazuje tijekom svog izvršenja.

Zadatak: Napisati program kojim se računa vrijednost od 5! (čitaj: pet faktorijela). Analiza problema: Vrijednost n! u matematici naziva n-faktorijela, a definirana je formulom:

⎪⎩

⎪⎨

>

==∏=

n

k

nzak

nzan

1

0

01!

Ovu se formulu može opisati i sljedećim zapisom:

n! je jednako 1 ako je n=0, a za vrijednosti n>0, n! je jednako 1*2*3*..*n Rješenje: Trivijalno rješenje problema dano je u programu fact0.c. Najprije je deklarirana cjelobrojna varijabla nfac. Zatim je toj varijabli pridijeljena vrijednost umnoška konstanti 1*2*3*4*5, što odgovara vrijednosti 5!. Za ispis te vrijednosti korištena je standardna funkcija printf().

/* Datoteka fact0.c - Proračun 5! */ #include <stdio.h> int main() { int nfact; nfact = 1 * 2 * 3 * 4 * 5; printf("Vrijednost 5! iznosi: %d\n", nfact); return 0; }

Nakon izvršenja programa dobije se ispis:

Vrijednost 5! iznosi: 120

Pošto argument funkcije printf() može biti bilo koji izraz koji rezultira nekom vrijednošću, prethodni se program može napisati i u obliku:

/* Datoteka fact01.c */ /* Proračun 5! unutar argumenta funkcije printf() */ #include <stdio.h> int main() { printf("Vrijednost 5! iznosi: %d\n", 2 * 3 * 4 * 5); return 0;

Page 58: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

58

}

Oba, prethodno napisana programa nisu od neke koristi, jer se njima računa nešto što

čovjek može napamet puno brže riješiti.

5.2.1 Naredba iteracije – while petlja Cilj pisanja programa je poopćenje procesa obrade nekog problema na način da se dobije

rezultat za različite vrijednosti ulaznih podatka. U tu svrhu definiran je sljedeći zadatak:

Zadatak: Napisati program kojim se računa vrijednost od n!. Vrijednost n zadaje korisnik. Program mora obaviti sljedeće operacije:

1. Dobaviti vrijednost od n. 2. Izračunati vrijednost n!. 3. Ispisati vrijednost od n i n!.

Postavlja se pitanje kako realizirati korak 2 ovog algoritma. Problem je u tome što se unaprijed ne zna vrijednost od n, jer tu vrijednost unosi korisnik programa.

Analiza problema: Polazi se od definicije n-faktorijela n! = 1 za n = 0 n! = 1*2 * ..(n-2)*(n-1)*n za n > 0

Lako je uočiti da vrijedi i sljedeće pravilo:

n! = 1 za n=0 n! = n * (n-1)! za n>0

koje kazuje da se vrijednost od n! može izračunati iz prethodno poznate vrijednosti od (n-1)!. Koristeći ovu formulu, prijašnji problem proračuna 5! bi se mogao programski riješiti uvođenjem pomoćne cjelobrojne varijable k i sljedećim nizom naredbi:

/* stanje nakon izvršenja naredbi */ k = 0; nfact = 1; /* k jednak nuli, nfact jednak 1 */ k = k+1; nfact = k * nfact; /* k jednak 2, nfact jednak 2 */ k = k+1; nfact = k * nfact; /* k jednak 3, nfact jednak 6*/ k = k+1; nfact = k * nfact; /* k jednak 4, nfact jednak 24*/ k = k+1; nfact = k * nfact; /* k jednak 5, nfact jednak 120*/

Ovaj primjer pokazuje vrlo neefikasan način proračuna 5!, međutim, značajan je jer ukazuje da se do rezultata dolazi ponavljanjem istih naredbi. U ovom se slučaju naredba

k=k+1; nfact=k*nfact;

ponavlja sve dok je vrijednost varijable k manja od 5, pa se može napisati algoritamsko rješenje u obliku iterativne petlje:

1. k = 0; nfact = 1; 2. dok je k<5 ponavljaj k = k+1;

nfact = k * nfact;

U C jeziku se ovaj tip petlje zapisuje iskazom koji se zove while-petlja:

Page 59: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

59

k = 0; nfact = 1; while (k < 5) /* zaglavlje petlje */ { k = k+1; /* tijelo petlje */ nfact = k * nfact; }

Općenito while-petlja ima oblik: while (izraz) {

niz_naredbi ili while (izraz) naredba

}

a ima značenje: dok je (eng. while) izraz u zaglavlju petlje različit od nule izvršava se niz_naredbi tijela petlje koje su napisane unutar vitičastih zagrada. Ako je izraz jednak nuli izvršenje programa se nastavlja operacijom koja je definirana naredbom koja slijedi iza tijela petlje. U slučaju kada se u tijelu petlje navodi samo jedna naredba, tada nije nužno pisati vitičaste zagrade.

Izraz može biti bilo koji numerički ili relacijski izraz, a tretira se kao logički uvjet za izvršenje naredbi koje su obuhvaćene tijelom petlje. U prethodnom primjeru izraz ima oblik relacijskog izraza k < 5. Taj izraz u C jeziku može imati samo dvije vrijednosti: 1 ili 0, što je ekvivalentno logičkim vrijednostima istina ili laž. Naredbe tijela petlje će se ponavljati za vrijednosti k=1,2,3,4, jer je za te vrijednosti relacijski izraz k<5 istinit, odnosno njegova numerička vrijednost je različita od nule.

Pravu korist korištenja while-petlje spoznaje se tek kada ona primijeni za računanje

vrijednosti n!, gdje je n vrijednost koju zadaje korisnik programa. Rješenje je jednostavno: u zaglavlju while-petlje, umjeto izraza k<5, dovoljno je uvrstiti izraz k<n, pa za realizaciju koraka 2 vrijedi algoritam:

2. Izračunati vrijednost n!.

2.1. Postavi nfact = 1; 2.2. Postavi k=0; 2.3. Dok je k < n ponavljaj

Uvećaj vrijednost varijable k za jedan nfact = k * nfact;

Dorada koraka 2: Analiziram prethodnog algoritama može se uočiti da je vrijednost nfact jednaka jedinici ne samo kada je n=0, već i u slučaju kada je n=1. Zbog toga se kao početna vrijednost varijable k može uzeti jedinica. Vrijedi algoritam:

2. Izračunati vrijednost n!.

2.1 Postavi nfact = 1; 2.3 Postavi k=1; 2.3 Dok je k < n ponavljaj

Uvećaj vrijednost varijable k za jedan nfact = k * nfact;

Sada se može napisati program "fact1.c", kojim se implementira prethodno opisani algoritam.

Page 60: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

60

/* Datoteka: fact1.c */ /* Proračun n!. Vrijednost od n unosi korisnik. */ #include <stdio.h> int main() { int n, k, nfact; /* deklaracija potrebnih varijabli*/ /* korak 1*/ scanf("%d", &n); /* korak 2 */ nfact = 1; /* korak 2.1 */ k = 1; /* korak 2.2 */ while ( k < n) { /* korak 2.3 */ k = k + 1; nfact = k * nfact; } /* korak 3 */ printf("Vrijednost %d! iznosi: %d\n", n, nfact); return 0; }

Unutar programa komentarima je označen pojedini korak algoritma.

Testiranje programa fact1:

Nakon izvršenja ovog programa, na ekranu se dobije prikaz

c:>_

Program čeka da korisnik unese neku vrijednost za n. Ako unese vrijednost 5, dobije se ispis

Vrijednost 5! iznosi: 120

Ako korisnik unese vrijednost 13 dobije se rezultat:

Vrijednost 13! iznosi: 1932053504

Ako korisnik unese vrijednost 18 dobije se rezultat:

Vrijednost 18! iznosi: -898433024

Ovaj posljednji rezultat je pogrešan, jer vrijednost od 18! nadmašuje maksimalnu vrijednost koja se može kodirati kao cijeli broj u memoriji veličine 4 bajta (tj. 2147483647). Može se zaključiti da je maksimalna vrijednost koja se može izračunati jednaka 13!. Ako korisnik otkuca negativni broj, primjerice broj -3, program će ispisati:

Vrijednost -3! iznosi: 1

Ovaj rezultat nema nikakvog smisla jer funkcija n-faktorijela nije definirana za negativne brojeve.

Page 61: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

61

5.2.2 Uvjetna naredba – if naredba Nakon provedenog testiranja, pokazala se potreba za doradom prvog koraka algoritma

sljedećim operacijama:

Dorada koraka 1: 1. Dobaviti vrijednost od n. 1.1. Upozoriti korisnika da se očekuje unos broja unutar intervala [0,13] 1.2. Dobaviti otipkanu vrijednost u varijablu n 1.3 Ako je n < 0 ili n > 13 tada izvršiti sljedeće:

izvijestiti korisnika da je otkucao nedozvoljeni broj prekinuti izvršenje programa

Kako implementirati ove korake u C jeziku? Korake 1.1 i 1.2 može se zapisati naredbama

printf("Unesite broj unutar intervala [0,13]\n"); scanf("%d", &n);

Za implementaciju koraka 1.3 potrebno je upoznati kako se u C jeziku zapisuje uvjetna naredba tzv. if-naredba. Njen opći oblik glasi:

if (izraz) { niz_naredbi ili if (izraz) naredba; }

a značenje ove naredbe je: ako je (eng. if) izraz različit od nule izvršava se niz_naredbi koji je omeđen vitičastim zagradama, u protivnom izvršit će se naredba koja slijedi iza if-naredbe. Predikatni izraz, na temelju kojeg se u algoritamskom zapisu vrši selekcija, glasi:

n < 0 ili n > 13.

U C jeziku se logički operator “ili” zapisuje s dvije okomite crte ||, pa prethodni izraz u C jeziku ima oblik

n < 0 || n > 13

(Napomena: logički operator “i” se zapisuje s &&, a logička negacija znakom ! ispred logičkog izraza). Sada se korak 1.3 može napisati u obliku:

if((n < 0) || (n > 13)) { printf("Otipkali ste nedozvoljenu vrijednost"); return 1; /* forsirani izlaz iz funkcije main */ }

pa kompletni program izgleda ovako:

/* Datoteka fact2.c */ /* Proračun n!. Vrijednost od n unosi korisnik. */ /* Vrijednost od n mora biti unutar intervala [0,13]*/ #include <stdio.h> int main()

Page 62: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

62

{ int n, k, nfact; printf("Unesite broj unutar intervala [0,13]\n"); scanf("%d", &n); if((n < 0) || (n > 13)) { printf("Otipkali ste nedozvoljenu vrijednost"); return 1; /* forsirani izlaz iz funkcije main */ } nfact = 1; k = 1; while ( k < n) { k = k + 1; nfact = k * nfact; } printf("Vrijednost %d! iznosi: %d\n", n, nfact); return 0; }

Konačno je ostvaren kvalitetan i robustan program. On za bilo koju ulaznu vrijednost daje

rezultat nakon konačnog broja operacija. Ovo svojstvo se smatra temeljnim uvjetom koji mora zadovoljiti svaki programski algoritam.

5.2.3 Naredba selekcije: if-else naredba Radi vježbe i upoznavanja još jednog programskog iskaza – if-else naredbe, prethodni

algoritam se može zapisati u ekvivalentnom obliku:

Dobavi vrijednost od n. Ako je n >= 0 i n<=13 tada

Izračunaj vrijednost n!. Ispiši vrijednost od n i n!.

inače Izvijesti o pogrešnom unosu Kraj!

Tijek programa se sada kontrolira naredbom selekcije, koja ima značenje:

ako je logički uvjet istinit tada izvrši prvi niz naredbi

inače izvrši alternativni niz naredbi U C jeziku se ovaj tip naredbe zove if-else naredba ili if-else iskaz, a zapisuje se prema obrascu:

if(izraz) { niz_naredbi1 ili if(izraz) } naredba1; else else { naredba2; niz_naredbi2 }

Page 63: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

63

Značenje ove naredbe u je: ako je (eng. if) izraz različit od nule izvršava se niz_naredbi1, inače (eng. else) izvršava se niz_naredbi2. Ako niz_naredbi sadrži samo jednu naredbu ne moraju se pisati vitičaste zagrade. Izraz se tretira kao logička vrijednost.

U ovom primjeru proračun n! će se izvršiti samo ako su istovremeno zadovoljena dva uvjeta: n>=0 i n<=13. Ovaj se uvjet u C jeziku zapisuje s dva relacijska izraza povezana logičkim operatorom “i”, koji se označava s &&.

Program sada izgleda ovako:

/* Datoteka fact3.c */ /* Proračun n!. Vrijednost od n unosi korisnik. */ /* Vrijednost od n mora biti unutar intervala [0,13]*/ #include <stdio.h> int main() { int n, k, nfact; printf("Unesite broj unutar intervala [0,13]\n"); scanf("%d", &n); if((n >= 0) && (n <= 13)) { nfact = 1; k = 1; while ( k < n) { k = k + 1; nfact = k * nfact; } printf("Vrijednost %d! iznosi: %d\n", n, nfact); } else printf("Otipkali ste nedozvoljenu vrijednost"); return 0; }

5.3 Funkcije C jezika U prethodnoj su sekciji opisani temeljni iskazi kontrole izvršenja C programa, te kako se

oni koriste u implementaciji programskih algoritama. Čitav se program izvršavao unutar jedne funkcije – main(). Unutar te funkcije korištene su standardne funkcije print() i scanf(), iako nije poznato kako su te funkcije implementirane. Korištene su zbog toga jer su poznata pravila njihove upotrebe i efekti koje one uzrokuju.

5.3.1 Korištenje funkcija iz standardne biblioteke C jezika Funkcije se u programiranju koriste slično načinu kako se koriste funkcije u matematici.

Kada se u matematici napiše y=sin(x), x predstavlja argument funkcije, a ime funkcije sin označava pravilo po kojem se skup vrijednosti, kojem pripada argument x, pretvara u skup vrijednosti koje može poprimiti y.

Funkcija sin() se može koristiti i u izrazima C-jezika jer je implementirana u standardnoj biblioteci funkcija. Primjerice, dio programa, u kojem se ona koristi, može biti sljedećeg oblika:

#include <math.h> int main()

Page 64: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

64

{ double x,y; ......... x = sin(5.6); y = sin(x)+4.6; ..... }

Prvo je napisana leksička direktiva da se u proces kompiliranja uključi datoteka "math.h" u kojoj je specificiran prototip (ili deklaracija) funkcije sin(). Pregledom te datoteke može se pronaći specifikacija prototipa funkcije sin() oblika:

double sin(double);

Prototip iskazuje da argument funkcije mora biti vrijednost tipa double i da funkcija u izraze vraća vrijednost tipa double.

Općenito, funkcija može imati više argumenata. Oni se navode u zagradama iza imena funkcije i odvajaju zarezom. Tip vrijednosti kojim rezultira izvršenje funkcije uvijek se navodi ispred imena funkcije. Deklaracija prototipa završava znakom točka-zarez.

Ime argumenta funkcije nije navedeno već samo tip argumenta. Ime argumenta može biti i napisano (primjerice, double sin(double x) ), međutim, u prototipu ono nema nikakvi značaj jer deklaracija prototipa služi kompilatoru jedino kao pokazatelj s kojim tipovima vrijednosti će se koristiti funkcija.

Važno je zapamtiti da C funkcije “uzimaju” vrijednost svojih argumenata za proračun novih vrijednosti ili za ostvarenje nekog drugog procesa. Argument funkcije može biti bilo koji izraz koji rezultira tipom vrijednosti koji je deklariran u prototipu funkcije. Primjerice, vrijednost argumenta u naredbi x=sin(5.6) je vrijednost konstante 5.6, a u naredbi y=sin(x)+4.6 stvarni argument funkcije je vrijednost varijable x.

Slika 5.2. Redoslijed poziva funkcije

Uobičajeno se kaže da je u prethodnim naredbama izvršen poziv funkcije sin(), čime se

želi naglasiti da se za izvršenje te funkcije aktivira dio izvršnog koda u kojem se nalaze naredbe koje realiziraju tu funkciju. Funkcija iz koje se poziva funkcija, naziva se pozivna funkcija, (u ovom slučaju to je funkcija main()), a sama funkcija sin() se naziva pozvana funkcija.

Simbolički, pozvanu funkciju možemo shvatiti kao “crnu kutiju” koja prima i vraća vrijednost u pozivnu funkciju. Ta je simbolika ilustrirana na slici 5.2.

Prethodni segment programa se može napisati u ekvivalentnom obliku:

#include <math.h> ....... y = sin(sin(5.6))+ 4.6; .......

Page 65: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

65

prema pravilu da argumenti funkcije mogu biti i izrazi. Postavlja se pitanje: kojim redoslijedom se izvršavaju operacije u navedenoj naredbi pridjele vrijednosti. U C jeziku vrijedi pravilo da se pri proračunu izraza najprije računa vrijednost izraza koji se nalaze unutar zagrada. Stoga, najprije će biti izračunata vrijednost funkcije sin(5.6), zatim će ta vrijednost biti upotrebljena kao argument za ponovni poziv funkcije sin(). Konačno, dobivenoj će vrijednosti biti pribrojena vrijednost konstante 4.6.

Korisnik ne mora znati kako je napisan dio programa koji računa vrijednost funkcije sin() jer se taj dio programa uključuje u izvršni kod direktno iz biblioteke kompiliranih funkcija. Kako se to može napraviti i s funkcijama koje kreira korisnik bit će pokazano u sljedećoj sekciji.

5.3.2 Korisnički definirane funkcije Sada će biti pokazano kako korisnik može definiranja neku funkciju i kako se ona uključuje

u korisnički program. Pravilo je:

Definicija funkcije se sastoji od "zaglavlja" i "tijela" funkcije. Zaglavlje funkcije je deklaracija u kojoj se redom navodi

1. oznaka tipa koji funkcija vraća u izraze, 2. ime funkcije, 3. deklaracija liste parametara (formalnih argumenata) funkcije napisanih unutar

zagrada. Tijelo funkcije je složeni iskaz naredbi i deklaracija varijabli, koji definiraju implementaciju. Piše se unutar vitičastih zagrada. Unutar tijela funkcije se pomoću ključne riječi return označava izraz, čiju vrijednost funkcija vraća u pozivnu funkciju. Primjerice, definicija funkcije kojom se računa kvadrat cjelobrojnog argumenta glasi

int kvadrat(int y) { return y * y; }

Ključna riječ return označava mjesto na kojem se prekida izvršenje funkcije, na način da se prethodno izračuna vrijednost izraza koji je napisan iza riječi return. Vrijednost tog izraza je vrijednost koju funkcija vraća u izraz iz kojeg je pozvana.

U definiciji funkcije mora se navesti ime argumenta s kojim će se izvršiti operacije unutar funkcije. To ime se naziva formalni argument ili parametar funkcije jer on ima značaj samo pri definiranju funkcije (pri pozivu funkcije kao stvarni argument koristi se vrijednost nekog izraza).

U sljedećem programu ilustrirana je definicija i upotreba funkcije kvadrat().

Page 66: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

66

Slika 5.3 Definiranje funkcije

Složeni iskaz C jezika, koji je napisan unutar vitičastih zagrada, naziva se blok. Tijelo funkcije je blok C jezika. Unutar bloka se mogu koristiti svi tipovi iskaza C jezika uključujući deklaraciju varijabli i prototipova funkcija, jedino se ne smije vršiti definiranje neke druge funkcije. Deklaracije se moraju pisati neposredno na početku bloka (iza vitičastih zagrada).

Alternativno se funkcija kvadrat() može napisati u obliku:

int kvadrat(int y) { int tmp; tmp = y*y; return tmp; }

U ovom je slučaju najprije deklarirana varijabla tmp koja služi za privremeni smještaju rezultata izraza y*y. Funkcija vraća vrijednost te varijable. Iako ova verzija funkcije kvadrat() izgleda bitno drugačije od prve verzije, ne mora biti nikakve razlike u načinu kako se stvarno izvršavaju ove funkcije Razlog tome je činjenica da kompilator sam generira tzv. privremene varijable za smještaj rezultata aritmetičkih operacija.

Optimizirajući kompilatori često za smještaj privremenih varijabli koriste registre procesora, jer se njima najbrže pristupa. Iz ovog razloga u C jeziku je omogućeno da se pomoću ključne riječi register, napisane ispred deklaracije cjelobrojne varijable, sugerira kompilatoru da za smještaj varijable koristi registre procesora. Primjerice, sljedeći oblik funkcije

int kvadrat(int y) { register int tmp; /* sugeriraj korištenje registra*/ tmp = y*y; return tmp; }

Page 67: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

67

je najbliži načinu kako optimizirajući kompilatori prevode prvi oblik funkcije kvadrat(). Danas mnogi programeri smatraju da uopće ne treba koristiti ključnu riječ register, jer moderni optimizirajući kompilatori mnogo efikasnije koriste procesorske registre, nego što to može učiniti programer tijekom procesa programiranja.

5.3.2 “void” funkcije U programskim se jezicima često koristi dva tipa potprograma: funkcije i procedure.

Procedura je potprogram koji vrši neki proces, ali ne vraća nikakvu vrijednost. Pošto se u C-jeziku svi potprogrami nazivaju funkcije, onda se kaže da je procedura funkcija koja vraća ništa (eng. void). Primjerice, u trećem poglavlju korištena je funkcija void hello() za ispis poruke "Hello World!".

Pomoću ključne riječi void označava se da je tip vrijednosti koji funkcija vraća "ništa", odnosno da je nevažan. Poziv procedure se vrši njezinim imenom. Pošto procedure ne vraćaju nikakvu vrijednost, ne mogu se koristiti u izrazima. U proceduri se ne navodi ključna riječ return, iako se može koristiti (bez argumenta) ako se želi prekinuti izvršenje procedure prije izvršenja svih naredbi koje se pozivaju u proceduri.

5.3.3 Primjer: funkcija za proračun n! Za proračuna n-faktorijela zgodno je definirati funkciju koja obavlja taj proračun. Prototip

te funkcije može biti oblika:

int factorial(int n);

Funkcija factorial() će kao argument koristiti vrijednost tipa int. Primjena ove funkcije u izrazima rezultirat će vrijednošću tipa int koji predstavlja vrijednost n-faktorijela. Definicija i primjena funkcije zapisani su u programu fact4.c

/* Datoteka fact4.c */ /* Proračun n! pomoću funkcije factorial(n) */ /* Vrijednost od n mora biti unutar intervala [0,13]*/ #include <stdio.h> /* definicija funkcije za proračun n faktorijela */ int factorial(int n) { int k = 1, nfact = 1; while (k < n) { k = k + 1; nfact = k * nfact; } return nfact; } int main() { int n; printf("Unesite broj unutar intervala [0,13]\n");

Page 68: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

68

scanf("%d", &n); if((n < 0) || (n > 13)) printf("Otipkali ste nedozvoljenu vrijednost"); else printf("Vrijednost %d! iznosi: %d\n", n, factorial(n)); return 0; }

Bitno je uočiti da se u glavnom programu više ne koriste varijable k i nfact. Te varijable su deklarirane unutar funkcije factorial(), jer su one potrebne samo za vrijeme dok se izvršava ta funkcija.

U C jeziku vrijedi opće pravilo da sve varijable, koje se definiraju unutar bloka ili tijela funkcije, zauzimaju memoriju samo dok se izvršava taj blok ili funkcija. Kada započne izvršenje funkcije, skriveno od korisnika rezervira se dio memorije za te varijable, i to u dijelu memorije koja se uobičajeno naziva stog (eng. stack). Nakon izvršenja funkcije, a prije nego se nastavi izvršenje programa iz pozivne funkcije, ta se memorija ponovo smatra slobodnom za korištenje. Ovo ujedno znači da se varijable, koje se deklariraju u nekoj funkciji, mogu koristiti samo u toj funkciji. One se stoga po dosegu imena ili vidljivosti (eng. scope) nazivaju lokalne varijable, a pošto im je vrijeme postojanja ograničeno na vrijeme u kojem se izvršavaju naredbe funkcije, nazivaju se i automatske varijable.

5.3.4 Funkcija za proračun ex Zadatak je napisati funkciju kojom se približno određuje vrijednost funkcije ex (e = 2. 718282) i rezultat usporediti s vrijednošću koja se dobije pomoću standardne funkcije exp(), kojoj je prototip - double exp(double x) - deklariran u datoteci "math.h". Metod: Koristeći razvoj u red: ex = 1 + x/1! + x2 /2! + x3 /3! + ..

zbrajati članovi reda, za dati x, sve dok razlika od prethodnog rezultata ne bude manja od zadane preciznosti eps. Primjerice, za x = 1.0, i eps = 0.0001 trebat će zbrojiti 10 članova reda.

Specifikacija funkcije: double my_exp(double x, double eps);

Parametri: x - vrijednost za koju se računa ex , tipa double eps - zadana preciznost proračuna, tipa double Rezultat: vrijednost tipa double, jednaka vrijednosti ex

Algoritam: Razvoj u red funkcije ex ima karakteristiku da se i-ti pribrojnik reda dobije tako da se prethodni pribrojnik pomnoži s x/i. Koristeći tu činjenicu, može se primijeniti sljedeći iterativni algoritam:

unesi x i eps i =1, pribrojnik = 1; ex = pribrojnik, preth_ex = 0; dok je apsolutna vrijednost od (ex – preth_ex) manja od eps ponavljaj preth_ex = ex; pribrojnik = pribrojnik * x / i; ex = ex + pribrojnik; uvećaj i;

Page 69: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

69

Napomena: apsolutna se vrijednost realnog broja x u C jeziku dobije primjenom funkcije double fabs(double x) koja je deklarirana u <math.h>. Realizacija programa:

/*Datoteka: ex.c*/ #include <stdio.h> #include <math.h> double my_exp(double x, double epsilon) { int i = 1; double pribroj = 1.0; double ex = 1.0, preth_ex = 0.0; while (fabs( ex - preth_ex) > epsilon) { preth_ex = ex; pribroj = pribroj * x / i; ex = ex + pribroj; i = i + 1; } return ex; } int main( void) { double eps, x, ex; printf(" Unesi x i preciznost eps:\n"); scanf("%lf%lf", &x, &eps); ex = my_exp(x, eps); printf(" e^%f = %f; (tocno: %f)\n", x, ex, exp(x)); return 0; }

Izvršenjem programa dobiju su rezultati:

c:>ex Unesi x i preciznost eps: 1 .00001 e^1.000000 = 2.718282; (tocno: 2.718282) c:>ex.exe Enter x and the eps: 2 .0001 e^2.000000 = 7.389047; (tocno: 7.389056)

U prethodnom programu istim imenom (ex) su deklarirane varijable u funkciji main() i u funkciji my_exp(). Postavlja se pitanje: da li je to ista varijabla ili se radi o dvije različite varijable? Na to pitanje daju odgovor pravila dosega ili postojanosti identifikatora. Pravilo je da se u različitim blokovima mogu deklarirati varijable s istim imenom. To su onda različite varijable koje postoje samo u bloku u kojem su definirane. O tome će biti više govora u poglavlju 8.

Na sličan način su definirane mnoge matematičke funkcije iz standardne biblioteke (vidi

Dodatak C).

Page 70: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

70

5.4 Zaključak Do sada su korišteni sljedeći elementi C jezika: 1. Varijable i funkcije su zapisivane simboličkim imenima. U iskazima deklaracije svim

varijablama je uvijek označen tip. To omogućuje kompilatoru da rezervira memoriju potrebnu za smještaj vrijednosti varijable.

2. Numeričke konstante i stringovi su zapisivani u literalnom obliku – upravo onako kako se zapisuju i u govornom jeziku..

3. Korišteni su različiti operatori pomoću kojih se formiraju aritmetički, relacijski i logički izrazi.

4. Korištene su naredbe kojima se određuje izvršenje procesa u računalu. Najprije su korištene tzv. proste naredbe: pridjela vrijednosti i poziv izvršenja standardnih funkcija printf() i scanf(). Zatim su korištene tzv. strukturalne naredbe: sekvenca naredbi koja se omeđuje vitičastim zagradama, while-petlja kojom se kontrolira tijek iterativnih procesa, if-naredba, pomoću koje se uvjetno određuje izvršenje neke naredbe, te if-else naredba, pomoću koje se vrši selekcija naredbi. Kasnije će biti opisane još neke naredbe za kontrolu toka programa.

5. Opisan je jednostavni način interakcije s korisnikom programa. 6. Pokazano je kako se koriste funkcije iz standardne biblioteke i kako korisnik može

definirati nove funkcije. 7. Pokazano je da se funkcija može pozivati višestruko. 8. Pokazano je da se proračuni u računalu mogu izvršiti s ograničenom točnošću. Na primjeru

eksponencijalne funkcije pokazano je kako je implementirana većina trigonometrijskih funkcija.

9. Razvijen je algoritam za proračun n-faktorijela i izvršena implementaciju tog algoritma u C jeziku. Sam tijek razvoja algoritma može programerima - početnicima biti zbunjujući, jer su stalno vršene dodatne analize i dorada algoritma. Iskusniji programeri znaju da je to jedini ispravni način razvoja programa, jer se samo postupnom analizom i doradom programa može napraviti kvalitetan program.

Razvoj programa postupnom analizom i doradom (ili razvoj u koracima preciziranja) je metoda koju su popularizirali E. Dijkstra, u knjizi "Structured Programming", Academic Press, 1972, i N. Wirth u članku "Program Development by Stepwise Refinement",CACM, April 1971.

Sam postupak se može opisati na sljedeći način: 1. Formuliraj problem na način da bude potpuno jasno što program treba obaviti. 2. Formuliraj temeljni tijek algoritamskog rješenja običnim govornim jezikom. 3. Izdvoji pogodnu manju cjelinu i razloži je detaljnijim algoritmom. 4. Ponavljaj korak (3) dok se ne dobiju algoritmi koji se mogu zapisati programskim

jezikom (ili pseudo-jezikom). 5. Odaberi dio algoritamskog zapisa i zapiši ga programskim jezikom. Pri tome odredi

potrebne struktura podataka. 6. Sustavno ponavljaj korak (5) i pri tome povećaj razinu dorade programskih rješenja.

Na kraju, mora se nažalost reći, da ni danas u programiranju nema gotovih recepata, pa i dalje vrijedi izneseni metodološki pristup razvoju programskih algoritama.

Page 71: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

71

6 Izrazi i sintaksa C jezika

Naglasci: • aritmetički, logički i relacijski izrazi • pravila prioriteta i asocijativnosti • bitznačajni operatori • složeni operatori • ternarni izrazi • automatska i eksplicitna pretvorba tipova • typedef • sintaksa i leksika programskih jezika • BNF notacija za zapis sintakse

6.1 Izrazi Izrazi su zapisi koji sadrže operande i operatore. Svaki izraz daje neku vrijednost. Operandi

mogu biti varijable, funkcije i konstante. U izrazima može biti više operatora i više različitih tipova operanada.

S obzirom na složenost izraza razlikuju se:

• Unarni izrazi – imaju samo jedan operator i jedan operand, • Binarni izrazi – imaju dva operanda i jedan operator, • Ternarni izrazi – imaju tri operanda i dva operatora, • Složeni izrazi – sastoje se od više operanada, operatora i zagrada koje služe za

grupiranje izraza. Pravilo je da se najprije računa vrijednost izraza koji je napisan u zagradama, a zatim se ta vrijednost tretira kao prosti operand. Ukoliko nema zagrada, tada za redoslijed izvršenja složenog izraza vrijede posebna pravila prioriteta i asocijativnosti djelovanja operatora.

S obzirom na upotrebu različitih operatora, izrazi mogu biti aritmetički, relacijski i logički. Bit će pokazno:

• Kako se izvršavaju izrazi? • Koji su pravila prioriteta i asocijativnosti djelovanja operatora? • Kako se vrši pretvorba tipova ako u nekom izrazu postoji više različitih tipova?

6.1.1 Aritmetički izrazi Binarni aritmetički izrazi koriste dva operanda i jedan operator: + za zbrajanje, - za

oduzimanje, * za mnnoženje, / za djeljenje i % za ostatak dijeljenja cjelobrojnih tipova (modulo operacija). Operandi mogu biti varijable, konstante i funkcije koja vraćaju numeričku vrijednost.

Operator % se može primijeniti samo na cjelobrojne operande jer se njime dobija ostatak cjelobrojnog dijeljenja, primjerice izraz

Page 72: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

72

x % 2

daje vrijednost ostatka dijeljenja s 2. Taj ostatak može biti 0 ili 1 (ako je 0, broj x je paran, a ako je 1, broj x je neparan).

Unarni aritmetički izrazi imaju jedan operand i jedan operator: - za negaciju (daje negativnu vrijednost) i + za 'afirmaciju' (ne mijenja vrijednost operanda). Operatori se zapisuju ispred imena varijable, konstante ili funkcije koja vraća vrijednost.

Prefiks i postfiks unarni operatori

Prefiks i postfiks operatori: ++ i --, uvećavaju, odnosno umanjuju, vrijednost numeričkih varijabli za 1. Mogu se primijeniti ispred ili iza imena varijable,

++n; /* uvećava n za 1 */ --n; /* umanjuje n za 1 */

Prefiks operator djeluje na operand prije nego se koristi njegova nova vrijednost.

n = 5; x = ++n; /* x je jednak 6, n je jednak 6 */

Postfiks operator djeluje na operand nakon korištenja njegove trenutne vrijednosti.

n = 5; x = n++; /* x je jednak 5, n je jednak 6 */

Operandi na koje djeluju operatori ++ i -- moraju biti varijable.

Asocijativnost i prioritet djelovanja operatora

Kada u izrazima ima više operanada i operatora, redoslijed kojim se računa izraz određen je pravilima prioriteta i asocijativnosti. Prioritet djelovanja operatora određuje koji se podizraz prvi izvodi.

Aritmetički operatori imaju sljedeći prioritet izvršenja:

viši prioritet unarni operatori - + prefiks op (++ --) ..... binarni operatori * / % niži prioritet binarni operatori + -

Primjerice, -2* a + b se izvodi kao da je napisano (((- 2)* a) + b).

Asocijativnost određuje redoslijed izvođenja izraza koji imaju više operanada istog prioriteta. Svi aritmetički operatori imaju asocijativnost s lijeva na desno.

a + b + c <=> (( a + b) + c)

Redoslijed izvođenja se uvijek može predodrediti upotrebom zagrada. Tada se najprije izvršava izraz u zagradama.

Kako se vrši potenciranje?

U C jeziku ne postoji operator potenciranja. Kada je potrebno potencirati neki broj ili numeričku varijablu, može se koristiti dva postupka:

1. ako se potencira s cijelim brojem tada se potenciranje može realizirati pomoću višestrukog množenja, primjerice

a3 se realizira izrazom a*a*a a-3 se realizira izrazom 1/(a*a*a)

Page 73: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

73

2. ako se potencira s realnim brojem tada se može koristiti standardna funkcija

double pow(double x, double y);

koja vraća realnu vrijednost koja je jednka xy. Ova funkcija je deklarirana u <math.h>.

6.1.2 Relacijski i logički izrazi Relacijski ili uvjetni izrazi se sastoje se od dva operanda numeričkog tipa i sljedećih

operatora:

< manje <= manje ili jednako == jednako != nije jednako > veće >= veće ili jednako

Rezultat relacijskog izraza je vrijednost 0 ili 1. Primjerice,

x = (a == b); /* x je 1, ako je a jednako b, inače x je 0 */ x = (a != b); /* x je 0, ako je a jednako b, inače x je 1 */ x = (a > b); /* x je 1, ako je a veće od b, inače x je 0 */

Pošto u C-u ne postoji logički tip varijabli, nula predstavlja logičku vrijednost false, a nenulta vrijednost predstavlja logičku vrijednost true.

Logički operatori su:

&& logička konjunkcija (i) || logička disjunkcija (ili) ! negacija

Djelovanje logičkih operatora se određuje prema pravilu:

izraz1 && izraz2 -> 1 ako su oba izraza različita od nule, inače 0 izraz1 || izraz2 -> 0 ako su oba izraza jednaka nuli, inače 1 !izraz -> 0 ako je izraz različit od nule, inače 1

Asocijativnost relacijskih i logičkih operatora je s lijeva na desno, a prioritet je manji od aritmetičkih operatora viši prioritet niži prioritet

Aritmetički operatori <, <=, >, >= ==, != && ||

a + b < max || max == 0 && a == b se izvršava kao: (( a + b) < max) || (max == 0 && (a == b))

Primjer: Godina je prestupna ako je djeljiva sa 4, a ne i s 100, ali godine koje su djeljive s 400 su uvijek prestupne godine. Ta se činjenicu može programski iskazati ne sljedeći način: if ((godina % 4 == 0 && godina % 100 != 0) || godina % 400 == 0)

Page 74: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

74

printf("%d je prestupna godina\n", godina); else printf("%d nije prestupna godina \n", godina); Primjer: Definirana je funkcija isupper() kojom se određuje da li neka cjelobrojna vrijednost predstavlja ASCII kod kojim su kodirana velika slova

int isupper(int c) /* ukoliko je argument c iz intervala ASCII vrijednosti u kojem su */ /* velika slova, funkcija vraća vrijednost 1, inače vraća 0 */ { return (c >= 'A' && c <= 'Z'); }

Primjer: Definirana je funkcija tolower() koja vraća veliko slovo, ako je argument malo slovo.

int tolower(int c) /* argument c je vrijednoost iz ASCII skupa * Ako c predstavlja ASCII kod nekog velikog slova, * funkcija vraća vrijednost koja predstavlja * ekvivalentno malo slovo */ { if (isupper(c)) return c + 'a' - 'A'; else return c; }

U C jeziku se znakovne konstante tretiraju kao cijeli brojevi

Zadatak: Napišite funcije

Funkcija vraća vrijednost različitu od nule (true), ako je znak c int isupper(int c); veliko slovo int islower(int c); malo slovo int isalpha(int c); veliko ili malo slovo int iscntrl(int c); kontrolni znak int isalnum(int c); slovo ili znamenka int isdigit(int c); decimalna znamenka int isxdigit(int c); heksadecimalna znamanka int isgraph(int c); tiskani znak osim razmaka int isprint(int c); tiskani znak uključujući razmak int ispunct(int c); tiskani znak osim razmaka, slova ili znamanke int isspace(int c); razmak, tab, vert. tab, nova linija, povrat, nova stranica

Ove funkcije su implementirane u standardnoj biblioteci, a njihova deklaracija je dana u datoteci "ctype.h".

6.1.3 Bitznačajni operatori U C jeziku se koristi 6 bitznačajnih operatora, koji se mogu primijeniti na integralne tipove

(char, short, int i long) .

Page 75: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

75

& bitznačajni "i" ( AND) | bitznačajni "ili" (OR) ^ bitznačajno "ekskluzivno ili" (XOR) << posmak bitova u lijevo >> posmak bitova u desno ~ bitznačajna negacija (unarni op.)

(komplement jedinice) Bitznačajne operacije se provode na bitovima istog značaja. Bitznačajni "i" operator & se najčešće koristi za maskiranje bitova, primjerice nakon naredbe

n = n & 0x000F;

u varijabli n će svi bitovi biti postavljeni na nula osim 4 bita najmanjeg značaja, bez obzira na vrijednost od n;

1010111000011011 n & 0000000000001111 0x000F ---------------- 0000000000001011 rezultat

Bitznačajni "ili" operator | se najčešće koristi za postavljanje bitova, primjerice

n = n | 0x000F;

ima učinak da se u varijabli n četiri bita najmanjeg značaja postavljaju na vrijednost 1, a ostali bitovi su nepromijenjeni;

1010111000011011 n | 0000000000001111 0x000F ---------------- 1010111000011111 rezultat

Bitznačajni "ekskluzivno ili" operator ^ postavlja bitove na vrijednost 1 na mjestima gdje su bitovi oba operanda različiti, odnosno na nulu na mjestima gdje su bitovi oba operanda isti.

Posmačni operatori djeluju tako da pomiču bitove udesno (>>) ili ulijevo (<<), primjerice x

<< 2 daje vrijednost od x s bitovima pomaknutim za dva mjesta udesno ( u 2 prazna mjesta se upisuje 0).

Dokažite: Kada posmačni operatori djeluju na varijable unsigned tipa onda pomak bitova za jedno mjesto u lijevo je ekvivalentno množenju s 2, a pomak bitova za jedno mjesto u desno je ekvivalentno dijeljenju s cijelim brojem 2. Primjer: Definirana je funkcija getbit(x,n) kojom se ispituje da li u cijelom broju x n-ti bit ima vrijednost 1.

int getbit (unsigned x, int n) { if (n>=0 && n<32) /* unsigned ima 32 bita */ {

Page 76: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

76

return (x & 01 << n) != 0; } return 0; }

Objasnite primjenu << operatora u ovom primjeru. Primjer: U programu binary.c korisnik unosi cijeli broj, a program ispisuje njegov binarni oblik.

/* datoteka: binary.c */ /* program ispisuje binarni kod cijelog broja*/ #include <stdio.h> int main() { int x, i, n; printf("Otkucaj cijeli broj:\n"); scanf("%d", &x); n = 8*sizeof(x); printf("Binarni kod je: "); i =n-1; while(i >=0) printf("%d",getbit(x,i--)); printf("\n"); return 0; }

Ispis je sljedeći:

Otkucaj cijeli broj: -2 Binarni kod je: 11111111111111111111111111111110 ili Otkucaj cijeli broj: 67 Binarni kod je: 00000000000000000000000001000011

6.1.4 Složeni operatori pridjele vrijednosti Izraz oblika

i = i + 2

u kojem se ista varijabla pojavljuje s obje strane znaka pridjele vrijednosti, može se zapisati u obliku:

i += 2

Operator += se naziva složeni operator pridjele vrijednosti.

Ovaj oblik se može primijeniti na većinu binarnih operatora: +=, -=, *=, /=, %=, <<=, >>=, &=, ^= i |=, koristeći opće pravilo:

Ako su izraz1 i izraz2 neki izrazi, tada

izraz1 op= izraz2

Page 77: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

77

je ekvivalentno

izraz1 = (izraz1) op (izraz2)

pri tome izraz1 mora biti izraz koji označava položaj u memoriji (ime varijable ili dereferencirani pokazivač).

Ovi operatori, kao i operator pridjele vrijednosti, imaju niži prioritet od aritmetičkih, relacijskih i logičkih operatora, stoga iskaz

x *= y + 1;

znači

x = x * (y + 1);

a ne

x = x * y + 1;

Primjer: Definirana je funkcija brojbita(x) koja vraća broj bita koji u argumentu x imaju vrijednost 1.

int brojbita(unsigned x) /* daje broj bita koji u argumentu x imaju vrijednost 1*/ { int broj=0; while( x != 0) { if (x & 01) broj++; x >>= 1; } return b; }

Zadatak: napišite program u kojem korisnik unosi cijeli broj, a program ispisuje broj bita koji su u tom broju različiti od nule.

6.1.5 Ternarni uvjetni izraz Ternarni izraz se sastoji od tri izraza međusobno odvojena upitnikom i dvotočkom:

izraz1 ? izraz2 : izraz3

a značenje mu je slijedeće: ako je izraz1 različit od nule, vrijednost ternarnog izraza je jednaka izrazu2, a ako je izraz1 jednak nuli vrijednost ternarnog izraza je jednaka izrazu3. Primjerice u naredbi:

max = (x>y) ? x : y;

vrijednost varijable max će biti jednaka x ako je x>y, u suprotnom vrijednost od max će biti jednaka vrijednosti varijable y. Ternarni izraz je zapravo skraćeni oblik naredbe selekcije:

Page 78: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

78

if(x>y) max = x; else max = y;

međutim, često je prikladnija njegova upotreba od naredbe selekcije jer ga se može koristiti u izrazima.

6.2 Automatska i explicitna pretvorba tipova Automatska pretvorba tipova

Svaki izraz daje neku vrijednost čiji tip ovisi o tipu članova izraza. Kada su u nekom izrazu svi članovi i faktori istog tipa tada je i vrijednost izraza tog tipa. Primjerice, za

float y = 5, x=2;

izraz y/x daje realnu vrijednost 2.5. Ako su x i y cjelobrojne varijable,

int y = 5, x=2;

tada izraz y/x daje cjelobrojnu vrijednost 2 (ostatak dijeljenja se odbacuje).

U C jeziku se svi standardni tipovi tretiraju kao numerički tipovi i može ih se koristiti u svim izrazima. Kada u nekom izrazu ima više različitih tipova tada kompilator u izvršnom kodu vrši automatsku pretvorbu tipova. Princip je da se uvijek izvršava jedna operacija s maksimalno dva operanda. Ako su ta dva operanda različitog tipa onda se prije označene operacije vrši pretvorba tipa niže opsežnosti u tip više opsežnosti.

Opsežnost tipa, u redoslijedu od manje prema većoj opsežnosti je:

char → int → unsigned → long → float → double.

Primjerice, ako se koriste varijable

int j=5, k=7; float x=2.1;

u izrazu:

j+7.1*(x+k)

on se izvršava sljedećim redoslijedom:

1. najprije se izvršava proračun izraza u zagradama. U tom izrazu se najprije vrijednost varijable k pretvara (kodira) u tip float, jer je drugi operand tipa float. Zatim se toj vrijednosti dodaje vrijednost varijable x.

2. Vrijednost dobivenog izraza se zatim množi s realnom konstantom 7.1, jer množenje ima viši prioritet od zbrajanja.

3. Konačno preostaje da se zbroji vrijednost varijable j s vrijednošću prethodno izračunatog izraza (7.1*(x +k)), koji je realnog tipa. Pošto je to izraz s dva različita tipa, najprije se vrši pretvorba vrijednosti varijable i u tip float, i tek tada se izvršava operacija zbrajanja.

Page 79: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

79

Pretvorba tipova u naredbi pridjele vrijednosti

Pretvorba tipova u naredbi pridjele vrijednosti se uvijek vrši tako da se vrijednost koja se dobije iz izraza koji je na desnoj strani pretvara u tip koji ima varijabla na lijevoj strani. U slučaju da je s lijeve strane tip veće opsežnosti pretvorba se uglavnom može izvršiti bez gubitka točnosti. Primjerice, nakon izvršenja naredbi

float x; int i = 3; x = i; printf("x=%f", x);

bit će ispisano: x=3.00000.

U slijedećem slučaju pretvorba tipa int u tip unsigned neće imati smisla. Nakon izvršenja naredbi:

unsigned u; int i = -3; u = i; printf("u=%u", u);

bit će ispisano: u= 4294967293.

Kada se u izrazima miješaju tipovi int i unsigned, logični rezultat možemo očekivati samo za pozitivne brojeve.

Kada se s lijeve strane nalazi tip manje opsežnosti, pretvorba se vrši sa smanjenjem točnošću. Često se vrijednost tipa float ili double pridjeljuje cjelobrojnoj varijabli, primjerice za

double d = 7.99; int i ; i = d; printf("i=%d", i);

bit će ispisano i = 7.

Pravilo je da se pri pretvorbi realnog u cijeli broj odbacuje decimalni dio. To vrijedi bez obzira koliki je decimalni dio.

U mnogim programskim zadacima pojavit će se potreba da se pretvorba realnog broja u cijeli broj obavi na način da se vrijednost cijelog broja što manje razlikuje od vrijednosti realnog broja. To znači da ako je d=7.99, tada je poželjno da se ova vrijednost pretvori u cjelobrojnu vrijednost 8. To se može postići tako da se prije pretvorbe u cijeli broj decimalnom broju doda vrijednost 0.5, ako je pozitivan, odnosno da se od decimalnog broja odbije vrijednost 0.5 ako je negativan. U tu svrhu može se definirati funkciju Double2Int(), koja vraća cjelobrojnu vrijednost realnog argumenta;

int Double2Int(double x) { /* funkcija vraća cijeli broj koji je * najbliži relnoj vrijednosi x */ if(x>0)

Page 80: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

80

return x+0.5; else return x-0.5; }

Ekplicitna pretvorba tipova

Ukoliko se ispred nekog izraza ili varijable u zagradama zapiše oznaka tipa, primjerice

(float) x

time se eksplicitno naređuje kompilatoru da se na tom mjestu izvrši pretvorba vrijednosti varijable x u tip float.

Kada se oznaka tipa zapiše u zagradama to predstavlja operator pretvorbe tipa (eng. cast operator).

Primjenu ovog operatora ilustrira program u kojem se vrijednost dijeljenja cijelog broja s cijelim brojem pridijeljuje realnoj varijabli.

int main() { int i1 = 100, i2 = 40; float f1; f1 = i1/i2; printf("%lf\n", f1); return(0); }

Dobije se ispis:

2.000000

Pri dijeljenju je izgubljen decimalni dio iako je rezultat izraza i1/i2 pridijeljen realnoj varijabli. Zašto? Zato jer se pretvorba tipa vrši samo ako se u izrazu nalaze različiti tipovi. Pošto su u izrazu i1/i2 oba operanda tipa int izvršava se dijeljenje s cijelim brojevima. Ako želimo da se sačuva i decimalni dio može se primijeniti operator pretvorbe u jednom od tri oblika:

f = (float)i1/i2; ili f = i / (float)j; ili f = (float)i / (float)j;

Dovoljno je da se pretvorba tipa označi na samo jednom operandu, jer se izrazi računaju tako da se uvijek vrši pretvorba u tip veće opsežnosti.

Pokažimo još jedan primjer u kojem je potrebno primijeniti operator pretvorbe tipova

short int i = 32000, j = 32000; long li; li = (long)i + j;

Operator (long) je primjenjen zbog toga jer maksimalna vrijednosti za tip short int iznosi 32767. Stoga, ako bi se zbrojile dvije short int kodirane vrijednosti iznosa 32000 rezultat bi bio veći od 32767. Operator (long) ispred jednog operanda osigurava da će se zbrajanje izvršiti na način kao da su operandi tipa long.

Page 81: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

81

6.3 Definiranje sinonima tipa pomoću typedef Kada se ispred deklaracije napiše typedef, primjerice typedef int cijelibroj; time se označava da identifikator, u ovom slučaju cijelibroj, neće biti deklariran kao varijabla ili funkcija, već da taj identifikator postaje sinonim za tip koji je opisan deklaracijom. U ovom primjeru, identifikator cijelibroj postaje sinonim za tip int, pa ga se u kasnije može koristiti u drugim deklaracijama, na isti način kako se koristi i originalni tip, primjerice

cijelibroj i; /* deklaracija sa typedef tipom */

Važno je napomenuti da se pomoću typedef deklaracije stvaraju sinonimi tipova; a ne neki novi tipovi. Njihova je upotreba korisna za povećanje apstraktnosti programskog zapisa. Prema ANSI standardu, u C jeziku je definirano nekoliko typedef tipova kako bi se jasnije označilo područje njihove primjene. Primjerice, size_t predstavlja tip unsigned int, kojim se često označava veličina, u bajtima, objekata smještenih u datotakama ili u memoriji. Implementacija je provedena deklaracijom

typedef unsigned int size_t;

u datoteci "stddef.h".

Drugi primjeri su FILE, time_t, ptrdiff_t i wchar_t (pogledajte njihovo značenje u opisu standardne C-biblioteke).

6.4 Formalni zapis sintakse C-jezika Pisanje programa podliježe jezičnim pravilima:

1. leksička pravila određuju kako se tvore leksemi na zadanom alfabetu (ASCII skup), 2. sintaktička (gramatička) pravila određuju kojim se redom leksemi slažu u programske

iskaze, 3. semantička pravila određuju značenje programskih iskaza.

Leksička struktura C jezika se temelji na pravilima koja određuju kako se formiraju leksemi jezika (niz znakova koji čini prepoznatljivu nedjeljivu cjelinu), na zadanom alfabetu (ASCII skup znakova).

Temeljne leksičke kategorije su: 1. Ključne riječi jezika (if, while, else, do, int, char, float,..) služe za definiranje programskih

iskaza. Pišu se malim slovima. 2. Identifikatori služe za zapis imena varijabli, funkcija i korisničkih tipova. Pišu se pomoću

niza velikih i malih slova, znamenki i znaka podvlake ('_'), uz uvjet da prvi znak u nizu mora biti slovo ili podvlaka.

3. Literalne konstante služe za zapis numeričkih i tekstualnih (znakovnih) konstanti (pr. 135,

3.14, 'A', "Hello World").

Page 82: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

82

4. Operatori (+,-*/,..=, []..(), &, .+=,*=.) služe označavanju aritmetičko-logičkih i drugih operacija koje se provode sa memorijskim objektima (funkcije i varijable) i konstantama.

5. Leksički separatori su znakovi koji odvajaju lekseme. Jedan ili više znakova razmaka,

tabulatora i kraja retka tretiraju se kao prazno mjesto, kojim se razdvajaju leksemi. Operatori, također, imaju značaj leksičkih separatora. Znak točka-zarez (';') predstavlja specijalni separator koji se naziva terminator naredbi.

6. Komentar se piše kao proizvoljni tekst. Početak komentara se označava znakovima /*, a

kraj komentara s */. Komentar se može pisati u bilo kojem dijelu programa, i u više linija teksta. Mnogi kompilatori kao komentar tretiraju i tekst koji se unosi iza dvostruke kose crte //, sve do kraja retka.

7. Specijalne leksičke direktive su označene znakom # na početku retka. Izvršavaju se prije

procesa kompiliranja, pa se nazivaju i pretprocesorske direktive. Primjerice, #include <stdio.h> je pretprocesorska direktiva kojom se određuje da se u proces kompiliranja uvrsti sadržaj datoteke imena stdio.h.

Kao što se zapis u prirodnom jeziku sastoji od različitih elemenata (subjekt, predikat, pridjev, rečenica, poglavlje itd.), tako se i zapis u programskom jeziku sastoji od temeljnih elemenata, koje prikazuje tablica 6.2.

Elementi programa Značenje Primjer Tipovi oznake za skup vrijednosti

s definiranim operacijama int , float , char

Konstante literalni zapis vrijednosti osnovnih tipova

0 , 123.6 , "Hello"

Varijable imenovane memorijskih lokacije koje sadrže vrijednosti nekog tipa

i , sum

Izrazi zapis proračuna vrijednosti kombiniranjem varijabli, funkcija, konstanti i operatora

sum + i

Naredbe ili iskazi zapisi pridjele vrijednosti, poziva funkcije i kontrole toka programa

sum = sum + i; while (--i) if(!x).. else ..;

Funkcije (potprogrami)

imenovano grupiranje naredbi main() printf(...)

Kompilacijska jedinica

skup međuovisnih varijabli i funkcija koji se kompilira kao jedinstvena cjelina

datoteka.c

Tablica 6.2 Temeljni elementi zapisa programa u C jeziku

Navedeni elementi jezika se iskazuju kombinacijom leksema prema strogim gramatičkim, odnosno sintaktičkim pravilima, koji imaju nedvosmisleno značenje.

U prirodnim jezicima iskazi mogu imati više značenja, ovisno o razmještaju riječi, o morfologiji (tvorba riječi) i fonetskom naglasku. U programskim jezicima se ne koristi morfološka i fonetska komponenta jezika, pa se gramatika svodi na sintaksu, također, dozvoljen je samo onaj raspored riječi koji daje nedvosmisleno značenje. Uobičajeno se kaže da gramatika programskih jezika spada u klasu bezkontesktne gramatike.

Page 83: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

83

Slika 2.1. Osnovne faze u procesu kompiliranja

Za opis sintakse nekog jezika koristi se posebni jezik koji se naziva metajezik. Jezik koji se opisuje metajezikom naziva se ciljni jezik. Za opis semantike nekog jezika ne postoje prikladni metajezici već se semantika izražava opisno, primjenom prirodnih jezika.

Prije nego se izvrši opis metajezika, koji će biti upotrijebljen za opis sintakse C jezika, bit će opisani neki pojmovi iz teorije programskih jezika.

Na slici 2.1 ilustriran je proces kompiliranja. On se odvija na sljedeći način. Izvorni kod može biti spremljenu u jednoj datoteci ili u više datoteka koje se u toku jezičkog pretprocesiranja formiraju kao jedna datoteka, koja se naziva kompilacijska jedinica. Zatim se vrši leksička analiza izvornog koda, na način da se izdvoje leksemi (nizovi znakova koji predstavljaju nedjeljivu cjelinu). Ukoliko je leksem zapisan u skladu s leksičkom strukturom jezika on predstavlja terminalni simbol jezika (token) kojem se u radu kompilatora pridjeljuje jedinstveno značenje. U jezičke simbole spadaju: ključne riječi (if, else, while,...), specijalni simboli (oznake operatora i separatora), identifikatori (imena varijabli, konstanti, funkcija, procedura i labele), literalne numeričke i tekstualne konstante. Pojedinom simbolu pridjeljuju se različiti atributi koji se koriste u procesu generiranja koda. Primjerice, za varijable se unosi atribut koji opisuje tip varijable, ili uz literalno zapisanu numeričku konstantu se unosi i binarno kodirana numerička vrijednost konstante. Sintaktički analizator (parser) dobavlja jezičke simbole i određuje da li su oni grupirani u skladu s definiranom sintaksom. Ukoliko je to zadovoljeno, vrši se prevođenje u objektni kod usklađeno sa semantikom jezika.

Pogreške u procesu kompiliranja se dojavljuju kao:

• leksičke pogreške (pr. nije ispravno zapisano ime varijable) • sintaktičke pogreške (pr. u aritmetičkom izrazu nisu zatvorene zagrade) • semantičke pogreške (pr. primijenjen operator na dva nekompatibilna operanda)

U programu mogu biti prisutne i logičke pogreške (pr. petlja se ponavlja beskonačno). Njih može otkriti korisnik tek prilikom izvršenja programa. Za pojašnjenje navedenih pojmova razmotrimo iskaz: if (a > 3) max = 5.4; else max = a;

Page 84: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

84

Ovaj iskaz predstavlja ispravno zapisani sintaktički entitet - IskazIf. U njemu se pojavljuju sljedeći simboli: ključne riječi (if, then, else), operatori (>, =), identifikatori varijable (a i max), numeričke konstante (3 i 5.4) i terminator iskaza (;). Napomenimo da "razmak" predstavlja leksički separator. On se ne smatra simbolom jezika i može se umetnuti između leksema proizvoljan broj puta. Odnos leksema, tokena i atributa prikazuje donja tablica.

Leksem kategorija tokena atribut "if", "else" ključna riječ - "max", "a" Identifikator varijabla "=", ">" operatori - ";" terminator naredbe ( ...) separator izraza "5.4", "3" konstanta numerička vrijednost: 5.4 i 3

Za IskazIf u C jeziku vrijedi sintaktičko pravilo: IskazIf "je definiran kao" if (Izraz) Iskaz else Iskaz "ili kao" if (Izraz) Iskaz

Gornji iskaz zadovoljava ovo sintaktičko pravilo jer (a>3) predstavlja relacijski izraz, dakle predstavlja sintaktički entitet Izraz, a iskazi x=5.4; i x=a; predstavljaju iskaze dodjele vrijednosti, dakle pripadaju sintaktičkom entitetu Iskaz. Ako se izneseno sintaktičko pravilo shvati kao zapis u nekom sintaksnom metajeziku onda IskazIf, Izraz i Iskaz predstavljaju metajezičke varijable koje u odnosu na ciljni jezik predstavljaju neterminalne simbole, "je definiran kao" i "ili kao" su metajezički operatori, a leksemi: if, then i else i znakovi zagrada su metajezičke konstante koje odgovaraju simbolima ciljnog jezika, pa se nazivaju terminalni simboli ili tokeni. Uočimo da "ili kao" operator ima značaj logičkog operatora ekskluzivne disjunkcije.

Sintaktička pravila, kojima se jedan neterminalni simbol definira pomoću niza terminalnih i/ili neterminalnih simbola, nazivaju se produkcije jezika.

Prema ANSI/ISO standardu produkcije C-jezika se zapisuju na sljedeći način:

1. Operator "je definiran kao" je zamijenjen znakom dvotočke, a produkcije imaju oblik: neterminalni_simbol : niz terminalnih i/ili neterminalnih simbola

2. Alternativna pravila ("ili kao") se pišu u odvojenim redovima. 3. Neterminalni simboli se pišu kurzivom. 4. Terminalni simboli se pišu na isti način kao u ciljnom jeziku 5. Opcioni simboli se označavaju indeksom opt (Simbolopt ili Simbolopt).

Primjerice, zapis produkcije if-else iskaza glasi

IskazIf : if (Izraz) Iskaz else Iskaz if (Izraz) Iskaz

Ovo se pravilo može se napisati i na sljedeći način:

IskazIf : if (Izraz) Iskaz ElseIskazopt ElseIskaz : else Iskaz

Page 85: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

85

U prvom je pravilu uveden je ElseIskaz kao opcioni neterminalni simbol. Ako postoji, onda je njegova sintaksa opisana drugim pravilom, a ako ne postoji onda prvo pravilo predstavlja pravilo proste uvjetne naredbe.

Gornja pravila ćemo proširiti na način da se operator "ili kao" može eksplicitno označiti okomitom crtom (|), zbog dva razloga:

1. Na taj način gornja pravila (1-4) su ekvivalentna popularnoj BNF notaciji (BNF notacija je metajezik razvijen 1960. godine prilikom definicije programskog jezika ALGOL, pri čemu su bitne doprinose dali J.W.Bakus i P.Naur, pa BNF predstavlja kraticu za "Backus-ova normalna forma" ili "Backus-Naur-ova forma").

2. Na taj način se alternativne produkcije mogu pisati u istom redu

Pomoću prethodno definiranih pravila lako se može definirati i leksička struktura jezika. Primjerice, temeljni se leksički objekti znamenka i slovo mogu definirati pravilima:

slovo : A⎪B⎪C⎪D⎪E⎪F⎪G⎪H⎪I⎪J⎪K⎪L⎪M⎪N⎪O⎪P⎪Q⎪R⎪S⎪T⎪U⎪V⎪W⎪X⎪Y⎪Z ⎪a⎪b⎪c⎪d⎪e⎪f⎪g⎪h⎪i⎪j⎪k⎪l⎪m⎪n⎪o⎪p⎪q⎪r⎪s⎪t⎪u⎪v⎪w⎪x⎪y⎪z. znamenka : 0⎪1⎪2⎪3⎪4⎪5⎪6⎪7⎪8⎪9. heksa_znamenka : 0⎪1⎪2⎪3⎪4⎪5⎪6⎪7⎪8⎪9⎪A⎪B⎪C⎪D⎪E⎪F⎪a⎪b⎪c⎪d⎪e⎪f. oktalna_znamenka : 0⎪1⎪2⎪3⎪4⎪5⎪6⎪7⎪.

Koristeći objekte znamenka i slovo može se definirati objekt znak (koji može biti slovo ili znamenka):

znak : znamenka ⎪ slovo. Sintaksa znaka može po potrebi biti i drugačije definirana, naročito ukoliko se pod pojmom znak mogu koristiti i specijalni znakovi, ili još i šire, cijela ASCII kolekcija simbola.

Vrlo često, potreban element jezika je niz znakova. Njega se može definirati korištenjem rekurzivnog pravila:

niz_znakova : znak ⎪ niz_znakova znak, što se tumači na sljedeći način: niz znakova je ispravno zapisan ako sadrži samo jedan znak ili ako sadrži niz znakova i s desne strane još jedan znak. Dakle, alternativno pravilo prepoznaje sve nizove koji imaju dva ili više znakova. Može se napisati i slijedeće: niz_znakova : znak ⎪ znak niz_znakova, što se tumači ovako: niz znakova je ispravno zapisan ako sadrži samo jedan znak ili ako iza znaka sadrži niz znakova. Uočimo da alternativno pravilo, također, prepoznaje nizove koji sadrže dva ili više znakova.

Identifikatori u C-jeziku (nazivi varijabli, labela, funkcija i tipova) moraju početi sa slovom ili znakom podvlake '_', pa vrijedi :

identifikator : slovo | _ | identifikator slovo | identifikator znamenka | identifikator _

Page 86: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

86

Na osnovu ovog pravila, kao ispravno zapisani identifikatori, ocjenjuju se: BETA7 , A1B1 , x , xx , xxx , dok sljedeći zapisi ne predstavljaju indetifikatore: 7, A+B , 700BJ , -beta , x*5 , a=b , x(3).

Pod pojmom liste identifikatora podrazumijeva niz identifikatora međusobno razdvojenih zarezom.

lista_identifikatora : identifikator ⎪ identifikator , lista_identifikatora

U Dodatku B dana je potpuna specifikacija sintakse C jezika.

Page 87: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

87

7 Proste i strukturalne naredbe C jezika

Naglasci: • proste i strukturalne naredbe • naredbe bezuvjetnog skoka i označene naredbe • naredbe s logičkom i cjelobrojnom selekcijom • tipovi petlji i beskonačne petlje

Naredbe su programski iskazi pomoću kojih se kontrolira izvršenje programa. Prema razini apstrakcije računarskog procesa, kojeg predstavljaju, dijele se na proste naredbe i strukturalne naredbe. U prethodnim poglavljima se korištene strukturalne naredbe tipa sekvence, selekcije (if-else-naredba) i iteracije (while-petlja), te proste naredbe pridjele vrijednosti i poziva potprograma. Interesantno je napomenuti da se pomoću tih naredbi može napisati bilo koji algoritam koji je moguće izvršiti računalom. U ovom poglavlju će biti opisane sve naredbe C jezika koje se koriste za kontrolu toka programa.

7.1 Proste naredbe Proste ili primitivne naredbe su one naredbe koje odgovaraju naredbama strojnog jezika. U

C jeziku "najprostija" naredba je izraz iza kojeg se napiše znak točka-zarez. Takova naredba se naziva naredbeni izraz. Sintaksa naredbe je:

naredbeni izraz : izrazopt ;

Primjerice, 1+3*7; je naredba izračun vrijednosti izraza 1+3*7. Kada računalo izvrši operacije opisane ovim izrazom, rezultat će ostati negdje u memoriji ili u procesoru računala, stoga ova naredba nema nikakvog smisla. Ako se pak napiše naredba

x = 1+3*7;

tada će rezultat biti spremljen u memoriji na adresi koju označava varijabla imena x. Do sada je ovakva naredba nazivana naredba pridjele vrijednosti, jer se ona tako naziva u većini programskih jezika. U C jeziku se ova naredba zove naredbeni izraz pridjele vrijednosti, jer znak = predstavlja operator pridjele vrijednosti koji se može koristiti u izrazima. Primjerice, u naredbi

x = 3 + (a=7);

znak = se koristi dva puta. Nakon izvršenja ove naredbe vrijednost varijable a je 7, a vrijednost varijable x je 10. Prema iznesenom sintaktičkom pravilu naredbom se smatra i znak točka-zarez:

; /* ovo je naredba C jezika */

Page 88: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

88

Ova se naredba naziva nulta ili prazna naredba. Njom se ne izvršava nikakvi proces. Makar to izgledalo paradoksalno, ovu se naredbu često koristi, i često je uzrok logičkih pogreški u programu. Zašto se koristi i kako nastaju pogreške zbog korištenja ove naredbe bit će pokazano kasnije.

Upoznavanje s naredbenim izrazima završit će sa sljedećim primjerima prostih naredbi:

x++; /* povećaj vrijednost x za 1 */ --x; /* umanji vrijednost x za 1 */ printf("Hi"); /* poziv potprograma */ x=a+3.14+sin(x); /* kompleksni izraz s pozivom funkcije */

Posljednju naredbu, u kojoj se računa kompleksni izraz, s pozivom funkcije sin(x), moglo se zapisati pomoću više naredbenih izraza:

x = a; /* vrijednost od a pridijeli varijabli x */ x += 3.14; /* uvećaj x za 3.14 */ tmp=sin(x); /* pomoćnoj varijabli tmp pridijeli vrijednost */ /* koju vraća funkcija sin(x) */ x += tmp; /* uvećaj x za vrijednost varijable tmp */

Operacijska semantika, odnosno način kako se naredbe izvršavaju u računalu, u oba zapisa je potpuno ista, jer C prevodilac složene izraze razlaže u više prostih izraza koji se mogu direktno prevesti u strojni kôd procesora.

U proste naredbe spadaju još naredbe bezuvjetnog i uvjetnog skoka. Pomoću ovih naredbi se može eksplicitno zadati da se izvršenje programa nastavi naredbom koja je označena nekim imenom.

Sintaksa označene naredbe je: označena_nareba :

identifikator : naredba Identifikator kojim se označava neka naredba često se naziva programska labela. Sintaksa naredbe bezuvjetnog skoka je:

naredba_skoka :

goto identifikator ; Semantika naredbe je da se izvrši skok, odnosno da se izvršenje programa nastavi naredbom koja je označena identifikatorom i znakom dvotočke. Primjerice, u nizu naredbi:

goto next; naredba2 next: naredba3

nikad se neće izvršiti naredba2, jer se u prethodnoj naredbi vrši bezuvjetni skok na naredbu koja je označena identifikatorom next. Skok se može vršiti i unatrag, na naredbe koje su već jednom izvršene. Na taj način se mogu realizirati iterativni procesi – petlje. Naredba skoka i naredba na koju se vrši skok, moraju biti definirani unutar iste funkcije.

Zapis naredbe uvjetnog skoka, koji se izvodi na temelju ispitivanja logičke vrijednosti nekog izraza, je:

if ( izraz ) goto identifikator ;

Page 89: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

89

što znači: ako je izraz logički istinit (različit od nule) vrši se skok na označenu naredbu, a ako nije izvršava se slijedeća naredba.

Naredbe uvjetnog i bezuvjetnog skoka vjerno opisuju procese u računalu, međutim njihova se upotreba ne preporučuje. Sljedeći primjer pokazuje zašto programeri "ne vole goto naredbu". Razmotrimo zapis:

if (izraz) goto L1; goto L2; L1: naredba L2: .....

U prvoj se naredbi ispituje vrijednost izraza. Ako je on različit od nule, izvršava se naredba označena s L1, u suprotnom izvršava se naredba goto L2. Primjenom logičke negacije na izraz u prvoj naredbi dobije se ekvivalentni algoritam:

if (!izraz) goto L2: naredba L2: ......

Mnogo jednostavnije se ovaj programski tijek zapisuje tzv. uvjetnom naredbom:

if (izraz) naredba

Ova naredba spada u strukturalne naredbe selekcije. Ona, već na "prvi pogled", jasno iskazuje koji proces treba izvršiti. Ako se pak pogleda prethodna dva zapisa, u kojima je korištena goto-naredba, trebat će znatno više mentalnog napora za razumijevane opisanog procesa. Ovaj problem posebno dolazi do izražaja kod većih programa, gdje primjena goto naredbe dovodi do stvaranja nerazumljivih i "zamršenih" programa.

Jedino kada se može opravdati upotreba goto naredbe jest kada se želi napisati algoritam koji treba biti "ručno" preveden na asemblerski jezik. U svim ostalim slučajevima, u programiranju i u razvoju algoritama, treba koristiti naredbe selekcije i petlje kojima se dobija jasna i pregledna struktura programa.

7.2 Strukturalne naredbe

7.2.1 Složena naredba ili blok Pod pojmom složene naredbe podrazumijeva se niz naredbi i deklaracija napisan unutar

vitičastih zagrada. Naziva se i blok jer se u okviru neke druge strukturalne naredbe može tretirati kao cjelina. Lijeva zagrada '{' označava početak, a desna zagrada '}' označava kraj bloka.

Sintaksa složene naredbe je: složena-naredba :

{ niz-deklaracijaopt niz-naredbiopt } Unutar bloka dozvoljeno je deklarirati varijable, ali samo na mjestu neposredno iza vitičastih zagrada.

Pogledajmo programski odsječak u kojem se vrši zamjena vrijednosti dvije varijable x i y.

int x, y; ........

Page 90: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

90

x=7; y=5; { int tmp; /* tmp je lokalna varijabla bloka*/ tmp = x; /* tmp == 7 */ x = y; /* x == 5 */ y = tmp; /* y == 7 */ } printf("x=%d, y=%d", x, y)

Zamjena vrijednosti se vrši pomoću varijable tmp, koja je deklarirana unutar bloka, i koja ima karakter lokalne varijable, što znači da joj se ime može koristiti samo unutar bloka. Uočite da se najprije vrijednost od x upisuje u tmp. Zatim se varijabli x pridjeljuje vrijednost varijable y, i konačno se varijabli y pridjeljuje vrijednost od x, koja je bila sačuvana u varijabli tmp. Nakon izlaska iz bloka nije potrebna varijabla tmp. U C-jeziku se automatski obavlja odstranjenje iz memorije lokalnih varijabli po izlasku iz bloka u kojem su definirane. Kasnije će o ovom problemu biti više govora.

Sa semantičkog stajališta blok analizirano kao niz deklaracija i naredbi, dok u analizi sintakse i strukture programa, blok predstavlja jedinstvenu naredbu. To ujedno znači da u zapisu sintakse, na svakom mjestu gdje pišemo naredba, podrazumijeva se da može biti napisana i prosta i složena naredba i ostale strukturalne naredbe.

7.2.2 Naredbe selekcije Općenito se pod selekcijom nazivaju programske strukture u kojima dolazi do grananja

programa, a nakon prethodnog ispitivanja vrijednosti nekog izraza.

U C jeziku se koriste se tri tipa naredbi selekcije: 1. Uvjetna naredba (if- naredba) 2. Uvjetno grananje (if-else naredba) 3. Višestruko grananje (switch-case naredba)

U prva dva tipa naredbi grananje se vrši na temelju ispitivanja logičke vrijednosti nekog izraza, a u switch-case naredbi grananje može biti višestruko, ovisno o cjelobrojnoj vrijednosti nekog selektorskog izraza.

Uvjetna naredba (if-naredba)

Sintaksa if-naredbe je :

if_naredba: if ( izraz ) naredba

gdje naredba može biti bilo koja prosta, složena ili strukturalna naredba. Značenje naredbe je: ako je izraz različit od nule se izvršava naredba, a ako je izraz jednak nuli, program se nastavlja naredbom koja slijedi iza if-naredbe.

Page 91: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

91

Slika 7.1 Dijagram toka if- naredbe

Uzmimo primjer da analiziramo dvije varijable : x i y. Cilj je odrediti koja je od te dvije vrijednosti manja, a zatim tu manju vrijednost upisati u varijablu imena min. To se može ostvariti naredbama:

min = y; /* pretpostavimo da je y manje od x */ if (x < y) /* ako je x manje od y */ min = x; /* minimum je jednak x-u */

Uvjetno grananje (if-else naredba)

Sintaksa if-else naredbe je if-else-naredba:

if ( izraz ) naredba1 else naredba2 gdje naredba1 i naredba2 predstavljaju bilo koji prostu, složenu ili strukturalnu naredbu. Značenje if-else-naredbe je: ako je izraz različit od nule, izvršava se naredba1, inače izvršava se naredba2. Primjerice, iskaz:

if (x < y) min = x; else min = y;

omogućuje određivanje minimalne vrijednosti.

Slika 7.2 Dijagram toka if-else naredbe

Page 92: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

92

Uzmimo sada da je potrebno odrediti da li je vrijednost varijable x unutar intervala {3,9}. Problem se može riješiti tako da se unutar if-else naredbe, u kojem se ispituje donja granica intervala, umetne if-else naredba kojom se ispituje gornja granica intervala.

Tri sintaktički i semantički ekvivalentna if-else iskaza (nakon izvršenja, varijabla unutar ima vrijednost 1 ako je x unutar intervala {3,9}, inače ima vrijednost 0.)

if (x >= 3) { if (x <= 9) unutar = 1; else unutar = 0; } else unutar = 0;

if (x >= 3) if (x <= 9) unutar = 1; else unutar = 0; else unutar = 0;

if (x >= 3) if (x <= 9) unutar = 1; else unutar = 9; else unutar = 0;

U prvom se zapisu može, bez dodatnih pojašnjenja, znati pripadnost odgovarajućih if i else naredbi, jer vitičaste zagrade označavaju da umetnuta if-else naredba predstavlja prosti umetnuti iskaz. Ta pripadnost nije očita u drugom, i posebno ne u trećem zapisu, iako su to sintaktički potpuno ispravni C iskazi, jer se u C-jeziku koristi pravilo da "else naredba" pripada najbližem prethodno napisanom "if uvjetu". Dobar je programerski stil da se umetnuti iskazi pišu u odvojenim redovima s uvučenim početkom reda, kako bi se naglasilo da se radi o umetnutom iskazu.

“Uvlačenje redova” je stil pisanja programskih algoritama kojim se dobiva bolja preglednost strukture programskih iskaza.

Prije izneseni problem se može riješiti korištenjem samo jedne if-else naredbe:

if (x >= 3 && x <= 9 ) unutar = 1; else unutar = 0;

Pregledom ovog iskaza već se na prvi pogled može utvrditi koju radnju obavlja, jer se u početku if naredbe ispituje puni interval pripadnosti x varijable.

Često je moguće smanjiti broj umetnutih if-else naredbi uvođenjem prikladnih logičkih izraza.

U programiranju se često pojavljuje potreba za višestrukim selekcijama. Primjerice,

dijagram toka na slici 7.3 prikazuje slučaj u kojem se ispituje više logičkih izraza (L1,L2,..Ln). Ukoliko je ispunjen uvjet Li izvršava se naredba Ni, a ukoliko nije ispunjen ni jedan uvjet izvršava se naredba Ne.

Page 93: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

93

Slika 7.3 Dijagram toka za višestruku logičku selekciju

Programski se ovakva selekcija može realizirati pomoću umetnutih if-else naredbi u obliku:

if (L1) N1 else if (L2) N2 ...... else if (Ln) Nn else Ne

Primjer: U programu ifelseif.c od korisnika traži da odgovori na upit:

Predjednik SAD je: (a) Bill Clinton (b) Bill Gates (c) Bill Third Otipkaj slovo.

Ako korisnik pritisne malo ili veliko slovo 'a', program ispisuje poruku "Točno". U slučaju (b) i (c) poruka treba bit "Netočno". Ako pritisne slovo različito od 'a','b' ili 'c', tada se ispisuje poruka "Otipkali ste pogrešno slovo". Za dobavu znaka s tipkovnice koristi se standardna funkcija getchar(), koja vraća ASCII kôd pritisnte tipke.

/* Datoteka: ifelseif.c ---------------*/ /* Primjer višestruke logičke selekcije */ #include <stdio.h> int main(void) { char ch; printf(" Predjednik SAD je:\n"; printf(" (a) Bill Clinton\n (b) Bill Gates\n (c) Bill Third\n"); printf("\nOtipkaj slovo.\n"); ch=getchar(); if (ch =='A' || ch =='a') printf ("Tocno\n"); else if (ch =='B' || ch =='b') printf ("Nije tocno\n"); else if (ch =='C' || ch =='c') printf ("Nije tocno\n"); else printf("Otipkali ste pogresno slovo"; return 0; }

Page 94: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

94

Višestruko grananje (switch-case naredba)

Prethodne selekcije su vršene na temelju ispitivanja logičke vrijednosti nekog izraza. Sada će biti predstavljena switch-case naredba pomoću koje se selekcija grananja vrši na temelju ispitivanja cjelobrojne vrijednosti nekog izraza kojeg se naziva selektorski izraz. Logika switch-case naredbe je prikazana na slici 7.4.

Slika 7.4 Prikaz selekcije u switch-case naredbi

U dijagramu toka "selektorski" izraz je označena sa sel. Skup {a,b,..z} podrazumjeva skup

različitih konstanti cjelobrojnog tipa.U slučaju da izraz sel poprimi vrijednost konstante “a” izvršava se naredba Na. Ako izraz sel ima vrijednost konstante “b” izvršava se naredba Nb. Ako izraz sel ima vrijednost iz skupa {z1,z2,..z3} izvršava se naredba Nz, a ako vrijednost selektorskog izraza nije iz skupa {a,b,..,z1,z2,..z3} izvršava se naredba Nx. To se u C jeziku zapisuje iskazom:

switch (sel) { case a: Na; break; case b: Nb; break; .... case z1: case z2: case z3: Nz; break; default: Nx; }

Page 95: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

95

Naredba break predstavlja naredbu skoka na prvu naredbu izvan aktivnog bloka. Ime break (prekini) simbolički označava da naredba break "prekida" izvršenje naredbi u aktivnom bloku. Ukoliko iza označene case-naredbe nije navedena break-naredba, nastavlja se ispitivanje slijedeće case-naredbe. Ukoliko u ni jednoj case-naredbi nije pronađena vrijednost konstante koja je jednaka vrijednosti selektorskog izraza, izvršava se naredba koja je označena s default.

Semantiku prethodnog iskaza pokazuje ekvivalentna if-else konstrukcija:

if (sel == a) Na; else if(sel == b) Nb; else if (sel == z1 || sel == z2 || sel == z3 ) Nz; else Nx;

U sljedećem primjeru dan je programski fragment kojim se ispituje vrijednost broja kojeg unosi korisnik.

unsigned x; scanf("%d", &x); switch (x) { case 1: printf("otkucali ste broj 1") break; case 2: case 3: case 4: case 5: printf("otkucali ste jedan od brojeva: 2,3,4,5"); break; default: printf("otkucali ste 0 ili broj veći od 5"); }

Sintaksa switch-naredbe je prema ANSI standardu dosta slobodno definirana:

switch-naredba: switch ( izraz ) naredba

pa bi prema tom pravilu za naredbu mogla biti zapisana bilo koja naredba. Međutim, semantički ima smisla koristiti samo tzv. označene-naredbe:

case konstanti-izraz : naredba i default : naredba

te break naredbu, kojom se izlazi iz switch-bloka . Prema prethodnom pravilu sintaktički je potpuno ispravna naredba

switch(n) case 1: printf(" n je jednak 1\n");

Njome je iskazano da će biti ispisano "n je jednak 1" u slučaju kada je n jednako 1. Ova naredba nema praktičnog smisla, jer switch-naredba nije efikasno rješenje za ispitivanje samo jednog slučaja. Tada je bolje koristiti if-naredbu. U slučaju kada se koristi više označenih naredbi (što je redovit slučaj) sintaksa switch-case naredbe se može zapisati u znatno razumljivijem obliku:

Page 96: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

96

switch-case-naredba: switch ( izraz ) { niz-deklaracijaopt niz-case-naredbi default-naredbaopt }

niz-case-naredbi: case-naredba | niz-case-naredbi case-naredba case-naredba: case konstanti-izraz : niz-naredbiopt prekidopt default-naredba: default: niz-naredbiopt prekid: break ;

7.2.3 Naredbe iteracije - petlje Iterativni procesi ili petlje su procesi u kojima se ciklički ponavlja programski kod koji je definiran unutar petlje, sve dok za to postoje potrebni uvjeti. Uvjete ponavljanja ili izlaska iz petlje postavlja programer. S obzirom na način kako su postavljeni uvjeti izlaska iz petlje, definirani su slijedeći tipovi petlji:

1. Petlje s uvjetnim izlazom na početku petlje 2. Petlje s uvjetnim izlazom na kraju petlje 3. Petlje s višestrukim uvjetnim izlazom 4. Beskonačne petlje

Pokazat ćemo kako se ovi tipovi petlji realiziraju u C jeziku.

Slika 7.5 Petlje

Za iskaze petlje, kao i za sve strukturalne iskaze, vrijedi da unutar njih mogu biti definirani svi tipovi strukturalnih iskaza. Prema tome, unutar petlje može biti definiran proizvoljan broj umetnutih petlji. Preklapanje strukturalnih iskaza nije dozvoljeno.

Petlja s uvjetnim izlazom na početku petlje (while i for petlje)

Sintaksa while naredbe glasi: while-naredba:

while ( izraz ) naredba Značenje je: dok je (eng. while) izraz različit od nule, izvršava se naredba. Izraz predstavlja uvjet ponavljanja petlje.

Page 97: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

97

Slika 7.6 Dijagram toka while petlje

Uvjet ponavljanja petlje se ispituju na samom ulazu u petlju. Postoji mogućnost da se naredba uopće ne izvrši ako je početno izraz jednak 0 . Primjerice, za izračunavanje vrijednosti f (n) = n! (n≥0), mogu se koristiti iskazi:

i = 1; f = 1; while (i < n) { i++; f *= i; }

i = n; f = 1; while (i > 1) { f *= i; i–-; }

Ako je n<2 naredbe unutar while petlje se ne izvršavaju, stoga je prije početka petlje definirano da f ima vrijednost 1. Kontrola izvršenja petlje obavlja se pomoću cjelobrojne kontrolne varijable i, kojoj se vrijednost iterativno uvećava za 1 (u drugom iskazu se smanjuje za 1). U principu, može se koristiti više kontrolnih varijabli.

Prije početka while-petlje gotovo uvijek treba inicirati vrijednost neke kontrolne varijable, koja se koristi u uvjetu ponavljanja petlje. Stoga se može napisati obrazac korištenja while-petlje u obliku:

iniciraj_kontrolne_varijable while ( izraz_s_kontrolnim_varijablama ) {

niz_naredbiopt naredba_promjene_vrijednosti_kontrolnih_varijabliopt niz_naredbiopt

}

U nekim slučajevima je potrebno da naredba_promjene_vrijednosti_kontrolnih_varijabli bude prva naredba u petlji, u nekim slučajevima ona će biti posljednja naredba ili pak umetnuta naredba. Za zapis procesa u kojima je naredba_promjene_vrijednosti_kontrolnih_varijabli posljednja naredba petlje često je prikladnije koristiti for – petlju.

For-petlja se zapisuje tako da se iza ključne riječi for u zagradama zapišu tri izraza međusobno odvojena točka-zarezom, a iza njih naredba koja čini tijelo petlje:

for ( izrazopt ; izrazopt ; izrazopt ) naredba

Page 98: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

98

Primjerice, segment programa u kojem se računa n –faktorijela, se može zapisati pomoću for-petlje u obliku:

f=1 for (i=n; i > 1; i--) f *= i;

U prvom naredbenom izrazu se inicira kontrolna varijabla i na vrijednost n, u drugom izrazu se zapisuje uvjet ponavljanja pelje (i>1), a u trećem izrazu se zapisuje naredbeni izraz kojim se definira promjena kontrolne varijable pri svakom ponavljanju petlje(i--).

Semantiku for-petlje može se objasniti pomoću ekvivalentne while-petlje

izrazopt ; while ( izrazopt ) {

naredba izrazopt;

}

U svakom od ovih izraza može se navesti više izraza odvojenih zarezom. Primjerice, za proračun n-faktorijela (f=n!) vrijede ekvivalentni iskazi:

(1) f=1; for(i=2; i<=n; i=i+1) f=f*i; (2) for(f=1, i=2; i<=n; i=i+1) f=f*i; (3) for(f=1, i=2; i<=n; f=f*i, i=i+1) ;

Drugi iskaz koristi listu izraza za početne uvjete, a u trećem je iskazu čak i naredba iz bloka petlje f=f*i uvrštena u listu izraza iteracije. Samim time, naredba petlje je transformirana u nultu naredbu (tj. naredbu koja stvarno ne postoji, ali je sintaktički prisutna postavljanjem znaka točka-zarez kao znaka za završetak naredbe).

Ovaj primjer ujedno ukazuje na jednu od najčešćih pogrešaka pri pisanju programa u C jeziku, a to je u slučaju kada se točka-zarez napiše odmah iza zagrada for naredbe. Time prestaje djelovanje for petlje na naredbu koja je definirana iza zagrada jer točka-zarez označava kraj naredbe makar to bila i nulta naredba. Ista pogreška se često javlja kod zapisa if i while naredbi.

Postavlja se pitanje: kada koristiti for-naredbu, a kada koristiti while-naredbu. U C jeziku je to pitanje jezičkog stila, jer su to dvije ekvivalentne naredbe.

Petlje s uvjetnim izlazom na kraju petlje ( do-while naredba)

Sintakse do-while naredbe je:

do-while-naredba: do naredba while ( izraz ) ;

Page 99: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

99

Izraz predstavlja uvjet za ponavljanje petlje. Značenje je: izvrši naredbu, a zatim ponavljaj tu naredbu dok je izraz logički istinit. Temeljna karakteristika ove naredbe je da se naredbe u tijelu petlje izvršavaju barem jedan put.

Slika 7.7 Dijagram toka do-while petlje

Primjer: Izrada jednostavnog izbornika.

/* Datoteka: do-while.c * Primjer do while petlje *****************************/ #include <stdio.h> int main(void) { char ch; do { printf("Predjednik SAD je:\n"); printf("(1) Bill Clinton\n(2) Bill Gates\n(3) Bill Third\n"); printf("\nOtipkaj 1, 2 ili 3 <enter>!\n"); ch = getchar(); }while( ch != '1' && ch != '2'&& ch != '3'); if(ch == '1') printf("Tocno\n"); else printf("Nije tocno\n"); return 0; }

Naredbe za prekid i djelomično izvršenja petlje (break i continue)

U C jeziku iskaz break; predstavlja i naredbu za prekid petlje, a iskaz continue; predstavlja naredbu za povrat na početak petlje. Logika break iskaza je:

Pseudo-asembler C jezik L1: početak petlje .......

početak petlje { .......

Page 100: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

100

if (L) goto L2; ....... kraj petlje L2: ........

if (L) break; ....... } kraj petlje ......

Prekid petlje, sam za sebe nema nikakovog smisla već se uvijek iskazuje u sklopu neke uvjetne naredbe. Ukoliko postoji više umetnutih petlji, prekida se izvršenje samo one unutarnje petlje u kojoj je definiran iskaz prekida. Logika continue iskaza je: Pseudo-asembler C jezik L1: početak petlje ....... if (L) goto L1; ....... kraj petlje

početak petlje { ....... if (L) continue; ....... } kraj petlje

Bekonačne petlje

Ukoliko je u stukturi petlje uvjet za ponavljanje petlje uvijek istinit, kao u iskazu

while (1) { N }

dobije se tzv. beskonačna pelja Ona očito ne predstavlja suvislu algoritamsku strukturu jer je trajanje njenog izvršenja beskonačno, to je struktura koja ima ulaz ali nema izlaza. S programskog pak stajališta beskonačne petlje imaju smisla kod onih programa kod kojih se ciklički ponavlja jedan ili više procesa za vrijeme dok je računalo uključeno. Primjerice, jedan takovi program je i operativni sustav računala. Od beskonačne petlje se uvijek može dobiti petlja koja ima izlaz, ako se u zapis bekonačne petlje doda uvjetna naredba za prekid petlje (pomoću break ili goto naredbe). Primjerice, ako sekvencu petlje N čine dva iskaza B1 i B2, tada

while (1) { B1 if (L) break; B2 }

predstavlja petlju s uvjetnim izlazom unutar same petlje. Beskonačna petlja se može realizirati i pomoću for petlje:

for(;;) { /* beskonačna petlja */ }

Primjer: U programu cont.c korisnik unosi niz znakova, završno sa <enter>. Program koristi break i continue naredbe u beskonačnoj pelji, koja se izvršava sve dok se ne otkuca 5 malih slova. Nakon toga program ispisuje tih 5 malih slova. Ako se otkuca <enter> prije nego je uneseno 5 malih slova program se prekida i ispisuje poruku: "PREKINUT UNOS".

Page 101: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

101

/* Datoteka: cont.c * filtrira unos znakova s tipkovnice * tako da se propušta prvih 5 malih slova */ #include<stdio.h> #include<ctype.h> int main() { char slovo; int i; /* i registrira broj malih slova */ printf ("Upisite niz znakova i <enter>: "); i= 0; while(1) /* ili for(;;) */ { slovo= getchar(); if(slovo == '\n') { printf("\nPREKINUT UNOS!\n"); break; } if (islower(slovo)) { i++; printf("%c", slovo); } if(i<=5) continue; else { printf("UNOS OK!"); break; } } }

Page 102: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

102

8 Nizovi

Naglasci: • jednodimenzionalni nizovi • inicijalizacija nizova • višedimenzionalni nizovi • prijenos nizova u funkcije

U ovom je poglavlju opisano kako se formiraju i koriste nizovi. Rad s nizovima je "prirodni" način korištenja računala, jer memorija računala nije ništa drugo nego niz bajta. U programiranju, kao i u matematici, zanimaju nas nizovi kao kolekcija istovrsnih elemenata koji su poredani jedan za drugim.

Elementi niza su varijable koje se označavaju indeksom:

ai označava i-ti element niza u matematici a[i] označava i-ti element niza u C jeziku i=0,1,2....

8.1 Jednodimenzionalni nizovi

8.1.1 Definiranje nizova Niz je imenovana i numerirana kolekcija istovrsnih objekata koji se nazivaju elementi niza.

Elementi niza mogu biti prosti skalarni tipovi i korisnički definirani tipovi podataka. Označavaju se imenom niza i cjelobrojnom izrazom – indeksom – koji označava poziciju elementa u nizu. Indeks niza se zapisuje u uglatim zagradama iza imena niza. Primjerice, x[3] označava element niza x indeksa 3.

Sintaksa zapisa elementa jednodimenzionalnog niza je

element_niza: ime_niza [ indeks ] indeks: izraz_cjelobrojnog_tipa

Prvi element niza ima indeks 0, a n-ti element ima indeks n-1. Prema tome, x[3] označava četvrti element niza.

S elementima niza se manipulira kao s običnim skalarnim varijablama, uz uvjet da je prethodno deklariran tip elemenata niza. Sintaksa deklaracije jednodimenzionalnog niza je:

deklaracija_niza:

oznaka_tipa ime_niza [ konstantni_izraz ] ; Primjerice, deklaracijom

int A[9];

definira se A kao niz od 9 elementa tipa int.

Page 103: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

103

Deklaracijom niza rezervira se potrebna memorija, na način da elementi niza zauzimaju sukcesivne lokacije u memoriji.

Vrijedi pravilo:

adresa(A)=adresa(A[0])

adresa(A[n])=adresa(A[0]) + n*sizeof(A[0]))

Elementima niza se pristupa pomoću cjelobrojnog indeksa, primjerice:

A[0] = 7; int i=5; A[2]= A[i]; for(i=0; i<9; i++) printf("%d ", A[i];

Memorijski raspored niza A

adresa sadržaj 1000 data[0] 1004 data[1] 1008 data[2] 1012 data[3] 1016 data[4] 1020 data[6] 1024 data[5] 1028 data[7] 1032 data[8] Napomena: int zauzima 4 bajta

U C jeziku se ne vrši provjera da li je vrijednost indeksnog izraza unutar deklariranog

intervala. Primjerice, iskaz:

A[12] = 5;

je sintaktički ispravan i kompilator neće dojaviti grešku. Međutim, nakon izvršenja ove naredbe može doći do greške u izvršenju programa, ili čak do pada operativnog sustava. Radi se o tome da se ovom naredbom zapisuje vrijednost 5 na memorijsku lokaciju za koju nije rezervirano mjesto u deklaraciji niza. Primjer: U programu niz.c pokazano je kako se niz koristi za prihvat veće količine podataka - realnih brojeva. Zatim, pokazano je kako se određuje suma elemenata niza te vrijednost i indeks elementa koji ima najveću vrijednost.

/* Datoteka: niz1.c */ #include <stdio.h> #define N 5 int main() { int i, imax; double suma, max; double A[N]; /* niz od N elemenata */ /* 1. izvjesti korisnika da otkuca 5 realnih brojeva */ printf("Otkucaj %d realnih brojeva:\n", N); for (i=0; i<N; i++) scanf("%lg", &A[i]); /* 2. izračunaj sumu elemenata niza */ suma = 0; for (i=0; i<N; i++)

Page 104: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

104

suma += A[i]; printf("Suma unesenih brojeva je %f\n", suma); /*3.odredi indeks(imax) i vrijednost(max) najvećeg elementa */ imax = 0; max = A[0]; for(i=1; i<N; i++) { if(A[i] > max ) { max = A[i]; imax=i; } } printf ("%d. element je najveci (vrijednost mu je %f)\n", imax+1, max); return 0; }

Izvršenje programa može izgledati ovako:

Otkucaj 5 realnih brojeva: 5 6.78 7.1 8 0.17 Suma unesenih brojeva je 27.050000 4. element je najveci (vrijednost mu je 8.000000)

8.1.2 Inicijalizacija nizova Za globalno i statičko deklarirane nizove automatski se svi elementi postavljaju na

vrijednost nula. Kod lokalo deklariranih nizova ne vrši se inicijalizacija početnih vrijednosti elemenata niza. To mora obaviti programer. Za inicijalizaciju elemenata niza na neku vrijednost često se koristi for petlja, primjerice naredba

for (i = 0; i < 10; i++) A[i] = 1;

sve elemente niza A postavlja na vrijednost 1. Niz se može inicijalizirati i s deklaracijom sljedećeg tipa:

int A[9]= {1,2,23,4,32,5,7,9,6};

Lista konstanti, napisana unutar vitičastih zagrada, redom određuje početnu vrijednost elemenata niza. Ako se inicijaliziraju svi potrebni elementi niza, tada nije nužno u deklaraciji navesti dimenziju niza. Primjerice,

int A[]= {1,2,23,4,32,5,7,9,6};

je potpuno ekvivalentno prethodnoj deklaraciji. Broj elemenata ovakvog niza uvijek se može odrediti pomoću iskaza:

int brojelemenata = sizeof(A)/sizeof(int);

Niz se može i parcijalno inicijalizirati. U deklaraciji

int A[10]= {1,2,23};

Page 105: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

105

prva tri elementa imaju vrijednost 1, 2 i 23, a ostale elemente prevodilac postavlja na vrijednost nula. Kada se inicijalizira znakovni niz, tada se u listi inicijalizacije mogu navesti znakovne konstante:

char znakovi[2]= {'O','K'};

Primjer: U programu hex.c korisnik unosi cijeli broj bez predznaka, zatim se vrši ispis broja u heksadecimalnoj notaciji.

/* Datoteka: hex.c * ispisuje broj, kojeg unosi korisnik, u heksadecimalnoj notaciji */ #include <stdio.h> int main() { int num, k; unsigned broj; char hexslova []= {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'} ; char reverse[8] = {0}; /* zapis do 8 heksa znamenki u reverznom redu */ printf("Otkucaj cijeli broj bez predznaka: "); scanf("%u", &broj); if(broj <0) broj = -broj; printf("Heksadecimalni zapis je: "); num=0; do { k = broj % 16; /* iznos heksa znamenke */ reverse[num++] = hexslova[k]; /* oznaka heksa znamenke */ broj /= 16; /* odstrani ovu znamenku */ } while(broj != 0); /* num sadrži broj heksa znamenki */ /* ispis od krajnjeg (n-1) do nultog znaka */ for(k=num-1; k>=0; k--) printf("%c", reverse[k]); printf("\n"); return 0; }

Pri izvršenju programa ispis može biti:

Otkucaj cijeli broj bez predznaka: 256001 Heksadecimalni zapis je: 3E801

Algoritam pretvorbe u heksadecimalnu notaciju je jednostavan. Temelji se na činjenici da

ostatak dijeljenja s 16 daje numeričku vrijednost heksadecimalne znamenke na mjestu najmanjeg značaja. Ta se vrijednost (k=0..15) korisi kao indeks znakovnog niza

Page 106: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

106

hexslova[k]. Vrijednost hexslova[k] je znak odgovarajuće heksadecimalne znamenke, kojeg se pridjeljuje nizu reverse[] ( u njemu će na kraju biti zapisana heksadecimalna notacija broja, ali obrnutm redoslijedom). Zatim se broj dijeli s 16 i dobavlja sljedeća heksadecimalna znamenka. Taj se proces ponavlja sve dok je rezultat dijeljenja različit od nule. Ispis broja u heksadecimalnoj notaciji se vrši tako da se ispiše sadržaj znakovnog niza reverzno, počevši od znamenke najvećeg značaja, završno s znamenkom najmanjeg značaja (koja se u tom nizu nalazi na indeksu 0).

Primjer: Histogram

U datoteci "ocjene.dat", u tekstualnom obliku, zapisane su ocjene u rasponu od 1 do 10. Sadržaj datoteke "ocjene.dat” neka čini sljedeći niz ocjena:

2 3 4 5 6 7 2 4 7 8 9 1 3 4 6 9 8 7 2 5 6 7 8 9 3 4 5 1 7 3 4 10 10 9 10 5 7 6 3 8 9 4 5 7 3 4 6 1 2 9 6 7 8 5 4 6 3 2 4 5 6 1 3 4 6 9 10 7 2 10 7 8 1

Zadatak je izraditi program imena hist.c pomoću kojeg se prikazuje histogram ocjena u 10 grupa (1, 2,.. 10), i srednja vrijednost svih ocjena. Podaci iz datoteke "ocjene.dat" se predaju programu "hist.exe" preusmjeravanjem standardnog ulaza (tipkovnice) na datoteku "ocjene dat". To se vrši iz komandne linije komandom:

c:\mydir\> hist < ocjena.dat. Na ovaj način se pristupa sadržaju datoteke kao da je otkucan s tipkovnice.

/* Datoteka: hist.c */ #include <stdio.h> int main(void) { int i, ocjena, suma; int izbroj[11] = {0}; /* niz brojača ocjena */ /* izbroj[0] sadrži ukupan broj ocjena * izbroj[i] sadrži podatak koliko ima ocjena veličine i. * Početno, elementi niza imaju vrijednost 0 */ while (scanf("%d", &ocjena) != EOF) { izbroj[ocjena]++; /* inkrementiraj brojač ocjena */ izbroj[0]++; /* inkrementiraj brojač broja ocjena */ } printf("Ukupan broj ocjena je %d\n", izbroj[0]); /* ispiši histogram - od veće prema manjoj ocjeni*/ for (i = 10; i > 0; i--) { int n = izbroj[i]; /* n je ukupan broj ocjena iznosa i */ printf("%3d ", i); /* ispiši ocjenu, a zatim */ while (n-- > 0) /* ispiši n zvjezdica */ printf("*"); printf("\n");

Page 107: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

107

} /* izračunaj sumu svih ocjena */ suma =0; for (i = 1; i < 11; i++) suma += izbroj[i]*i; /*ispiši srednju ocjenu */ printf ("Srednja ocjena je %4.2f\n", (float)suma / izbroj[0]); return 0; }

Program se poziva komandom:

c:>hist < ocjene.dat

Dobije se ispis:

Ukupan broj ocjena je 73 10 ***** 9 ******* 8 ****** 7 ********** 6 ********* 5 ******* 4 ********** 3 ******** 2 ****** 1 ***** Srednja ocjena je 5.79

Analiza programa hist.c:

Prvo je deklariran niz izbroj od 11 elemenata tipa int, a inicijaliziran je na vrijednost 0. Program će u tome nizu bilježiti koliki je broj ocjena neke vrijednosti (izbroj[1] će sadržavati broj ocjena veličine 1, izbroj[2] će sadržavati broj ocjena veličine 2, itd., a izbroj[0] sadrži ukupan broj ocjena).

Bilježenje pojavnosti neke ocjene se dobije inkrementiranjem brojača izbroj[ocjena]. Ocjene se dobavljaju preusmjerenjem datoteke na standardni ulaz. Za unos pojedine ocjene koristi se scanf() funkcija sve dok se na ulazu ne pojavi znak EOF (end-of-file), koji znači kraj datoteke. Nakon toga se ispisuje histogram na način da se uz oznaku ocjene ispiše onoliko zvjezdica koliko je puta ta ocjena zabilježena u nizu brojača ocjena.

Na kraju se računa srednja vrijednost ocjena na način da se suma svih ocjena podijeli s ukupnim brojem ocjena. Uočite da je u naredbi

printf ("Srednja ocjena je %4.2f\n", (float)suma / izbroj[0]);

izvršena eksplicitna pretvorba tipa operatorom (float), jer srednja vrijednost može biti realni broj.

8.2 Prijenos nizova u funkciju Nizovi mogu biti argumeti funkcije. Pri deklaraciji ili definiranju funkcije formalni

argument, koji je tipa niza, označava se na način da se deklarira niz bez oznake veličine niza, tj. u obliku

Page 108: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

108

tip ime_niza[] Pri pozivu funkcije, kao stvarni argument, navodi se samo ime niza bez uglatih zagrada. Primjer: u programu prod.c korisnik unosi niz od N realnih brojeva. Nakon toga, program računa produkt svih elemenata niza, pomoću funkcije produkt(), i ispisuje rezultat.

/* Datoteka: prod.c */ /* Računa produkt elemenata niza od 5 elemenata */ #include <stdio.h> #define N 5 /* radi s nizom od N elemenata */ double produkt(double A[], int brojelemenata) { int i; double prod = 1; for (i=0; i<brojelemenata; i++) prod *= A[i]; return prod; } int main() { int i; double A [N]; /* 1. izvjesti korisnika da otkuca 5 realnih brojeva */ printf("Otkucaj %d realnih brojeva:\n", N); for (i=0; i<N; i++) scanf("%lg", &A[i]); /* 2. izračunaj sumu elemenata niza */ printf("Suma unesenih brojeva je %g\n", produkt(A, N)); return 0; }

Uočite de se vrijednost elemenata niza može mijenjati unutar funkcije. Očito je da se niz ne prenosi po vrijednosti (by value), jer tada to ne bi bilo moguće.

Pravilo je: U C jeziku se nizovi – i to samo nizovi – u funkciju prenose kao memorijske reference (by reference), odnosno prenosi se adresa početnog elementa niza. Brigu o tome vodi prevodilac. Memorijska referenca (adresa) varijable se pamti "u njenom imenu" stoga se pri pozivu funkcije navodi samo ime, bez uglatih zagrada.

Nizovi, koji su argumenti funkcije, ne smiju se unutar funkcije tretirati kao lokalne varijable. Promjenom vrijednosti elementa niza unutar funkcije ujedno se mijenja vrijednost elementa niza koji je u pozivnom programu označen kao stvarni argument funkcije.

Page 109: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

109

Primjer: Prijašnji primjer programa za histogram ocjena bit će modificiran, na način da se definira tri funkcije: pojavnost ocjena u nizu izbroj bilježit će se funkcijom registriraj(), crtanje histograma vršit će funkcija histogram(), a proračun srednje vrijednosti vršit će funkcija srednja_ocjena().

/* Datoteka: histf.c */ #include <stdio.h> void registriraj( int ocjena, int izbroj[]) { izbroj[ocjena]++; izbroj[0]++; } void histogram(int izbroj[]) { int n,i; for (i = 10; i > 0; i--) { printf("%3d ", i); n=izbroj[i]; while (n-- > 0) printf("*"); printf("\n"); } } float srednja_ocjena(int izbroj[]) { /* izračunaj sumu svih ocjena */ int i, suma =0; for (i = 1; i <= 10; i++) suma += izbroj[i]*i; return (float)suma / izbroj[0]; } int main(void) { int i, ocjena, suma; int izbroj[11]={0}; while (scanf("%d", &ocjena) != EOF) registriraj(ocjena, izbroj); histogram(izbroj); printf ("Srednja ocjena je %4.2f\n",srednjaocjena(izbroj)); return 0; }

Primjer: Često je potrebno odrediti da li u nekom nizu od N elemenata postoji element vrijednosti x. U tu svrhu zgodno je definirati funkciju

int search(int A[], int N, int x);

koja vraća indeks elementa niza A koji ima vrijednost x. Ako ni jedan element nema vrijednost x, funkcija vraća negativnu vrijednost -1. Implementacija funkcije je:

int search (int A[], int N, int x) {

Page 110: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

110

int indx; for(indx = 0; indx < N; indx++) { if( A[indx] == x) /* element pronađen – prekini */ break; } if(indx == N) /* tada ni jedan element nema vrijednost x*/ return -1; else return indx; }

Uočite, ako u nizu postoji više elemenata koji imaju vrijednost x, vraća se indeks prvog pronađenog elementa.

8.3 Višedimenzionalni nizovi Višedimenzionalnim nizovima se pristupa preko dva ili više indeksa. Primjerice,

deklaracijom:

int x[3][4];

definira se dvodimenzionalni niz koji ima 3 x 4 = 12 elemenata. Deklaraciju se može čitati i ovako: definiran je niz kojem su elementi 3 niza s 4 elementa tipa int. Dvodimenzionalni nizovi se često koriste za rad s matricama. U tom slučaju nije potrebno razmišljati o tome kako je niz složen u memoriji, jer se elementima pristupa preko dva indeksa: prvi je oznaka retka, a drugi je oznaka stupca matrice.

Matrični prikaz niza je:

x[0][0] x[0][1] x[0][2] x[0][3] x[1][0] x[1][1] x[1][2] x[1][3] x[2][0] x[2][1] x[2][2] x[2][3]

Memorijski raspored elemenata dvodimenzionalnog niza, koji opisuju neku matricu, je takovi da su elementi složeni po redovima matrice; najprije prvi redak, zatim drugi, itd..

Memorijski raspored niza adresa sadržaj 1000 x[ 0][ 0] 1004 x[ 0][ 1] 1008 x[ 0][ 2] 1012 x[ 0][ 3] 1016 x[ 1][ 0] 1020 x[ 1][ 1] 1024 x[ 1][ 2] 1028 x[ 1][ 3] 1032 x[ 2][ 0] 1036 x[ 2][ 1] 1040 x[ 2][ 2] 1044 x[ 2][ 3]

Višedimenzionalni niz se može inicijalizirani već u samoj deklaraciji, primjerice

int x[3][4] = { { 1, 21, 14, 8}, {12, 7, 41, 2}, { 1, 2, 4, 3} };

Navođenje unutarnjih vitičastih zagrada je opciono, pa se može pisati i sljedeća deklaracija:

int x[3][4] = {1, 21, 14, 8, 12, 7, 41, 2, 1, 2, 4, 3};

Ovaj drugi način inicijalizacije se ne preporučuje, jer je teže uočiti raspored elemenata po redovima i stupcima.

Page 111: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

111

Elementima se pristupa preko indeksa niza. U sljedećem primjeru računa se suma svih elemenata matrice x;

int i, j, brojredaka=3, brojstupaca=4; int sum=0; for (i = 0; i < brojredaka; i++) for (j = 0; i < brojstupaca; j++) sum += x[i][j]; printf("suma elemenata matrice = %d", sum);

Prijenos višedimenzionalnih nizova u funkciju

Kod deklariranja parametara funkcije, koji su višedimenzionalni nizovi pravilo je da se ne navodi prva dimenzija (kao i kod jednodimenzionalnih nizova), ali ostale dimenzije treba deklarirati, kako bi program prevodilac "znao" kojim su redom elementi složeni u memoriji.

Primjer: Definirana je funkcija sum_mat_el() kojom se računa suma elemenata dvodimenzionalne matrice, koja ima 4 stupca:

int sum_mat_el(int x[][4], int brojredaka) { int i, j, sum=0; for (i = 0; i < brojredaka; i++) for (j = 0; i < 4; j++) sum += x[i][j]; return sum; }

Uočite da je u definiciji funkcije naveden i argument koji opisuju broj redaka matrice. Broj stupaca je fiksiran već u deklaraciji niza na vrijednost 4. Očito da ova funkcija ima ograničenu upotrebu jer se može primijeniti samo na matrice koje imaju 4 stupca. Kasnije, pri proučavanju upotrebe pokazivačkih varijabli, bit će pokazano kako se ova funkcija može modificirati tako da vrijedi za matrice proizvoljnih dimenzija.

Page 112: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

112

9 Blokovi, moduli i dekompozicija programa

Naglasci:

• blok struktura programa • lokalne i globalne varijable • automatske i statičke varijable • programski moduli i biblioteke • "skrivanja podataka" • dekompozicija programa "od vrha prema dolje" • igra "točkice i kružići"

9.1 Blokovska struktura programa Ukoliko se u programiranju ne koristi goto naredba, programi tada imaju prepoznatljivu

blokovsku strukturu. Praksa je pokazala da se time dobiju “čitljivi” programi, koje je lako održavati i dograđivati.

Blokovska struktura C programa ima četiri razine:

• razina datoteke (temeljna kompilacijska jedinica) • razina definicije (tijela) funkcije • razina bloka kontrolnih struktura (sekvenca, iteracija, selekcija) • razina bloka koji je omeđen vitičastim zagradama

Blok niže razine može biti umetnut unutar bloka više ili iste razine proizvoljan broj puta, jedino se ne smije vršiti definicija funkcije unutar tijela neke druge funkcije.

Cilj je programirati tako da svaki blok predstavlja cjelinu koja je što manje ovisna o ostatku programa. Da bi se to postiglo potrebno je dobro razumjeti pravila dosega identifikatora i postojanosti varijabli.

9.1.1 Doseg Doseg nekog identifikatora (eng. scope) je dio programa u kojem se taj identifikator može

koristiti. Deklaracijom argumenata funkcije i lokalnih varijabli stvaraju se novi identifikatori. Za

njih vrijede sljedeća pravila dosega:

• Doseg argumenata funkcije je tijelo funkcije. • Doseg lokalnih varijabli se proteže od mjesta deklariranja do kraja složenog iskaza koji

je omeđen vitičastim zagradama. • Identifikatori s različitim područjima dosega, iako mogu imati isto ime, međusobno su

neovisni. • Nisu dozvoljene deklaracije s istim imenom u istom dosegu. Primjerice,

Page 113: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

113

float epowx( float x, float epsilon) { int x; /* greška, ime x je već pridjeljeno parametru funkcije*/ ... }

U sljedećem primjeru varijable x i ex u funkciji main(), te x i ex u funkciji my_exp() su neovisne varijable iako imaju isto ime.

int main( void) { double eps, x, ex; ... doseg ex, x return 0; } float my_exp( double x, double epsilon) { doseg x int i; double ex = 1.0, preth_ex = 0.0, … ; ... doseg ex return ex; }

Lokalne deklaracije imaju prednost nad vanjskim deklaracijama. Kažemo da lokalna deklaracija prekriva vanjsku deklaraciju. Primjer:

f( int x, int a) { int y, b; y = x + a* b; if (...) { int a, b; /* a prekriva parameter a /* ... /* b prekriva lokalnu var. b iz vanjskog dosega */ y = x + a* b; } }

Uobičajeno se smatra da nije dobar stil programiranja kada se koriste ista imena u preklopljenim dosezima, iako je to sintaktički dozvoljeno.

9.1.2 Automatske i statičke varijable Lokalne varijable imaju ograničeno vrijeme postojanja, pa se nazivaju i automatske

varijable. One nastaju (u memoriji) pozivom funkcije u kojoj su deklarirane, a nestaju (iz memorije) nakon povrata u pozivnu funkciju.

Kada se argumenti prenose u funkcije može se uzeti da se tada vrijednost stvarnih argumenata kopira u formalne argumente, koji pak imaju lokalni doseg. Argumenti funkcije se inicijaliziraju kao lokalne varijable, pa za njihovu upotrebu vrijede pravila kao za lokalne varijable. To je ilustrirano u programu doseg.c.

/* Datoteka doseg.c */ #include <stdio.h>

Page 114: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

114

void f( int a, int x) { printf(" a = %d, x = %d\n",a, x); a = 3; { int x = 4; printf(" a = %d, x = %d\n", a, x); } printf(" a = %d, x = %d\n", a, x); x = 5; /*nema nikakovi efekt*/ } int main( void) { int a = 1, b = 2; f( a, b); printf(" a = %d, b = %d\n", a, b); return 0; } c:> cl args.c c:>args a = 1, x = 2 a = 3, x = 4 a = 3, x = 2 a = 1, b = 2

Ako se lokalna varijabla deklarira s prefiksom static, tada se za tu varijablu trajno rezervira mjesto u memoriji (postoji i nakon izvršenja funkcije), iako je njen doseg ograničen unutar tijela funkcije.

U sljedećem primjeru opisana je funkcija incrCounter(), kojom se realizira brojač po modulu mod. U funkciji je definirana statička varijabla count, čija se vrijednost inkrementira pri svakom pozivu funkcije. Ako vrijednost postane jednaka argumentu mod, count se postavlja na nulu.

/* Datoteka: countmod3.c */ #include <stdio.h> int incrCounter(int mod) { static int count=0; count++; if(count == mod) count = 0; return count; } int main(void) { int i,modul=3; for(i=0; i<=10; i++) printf("%d, ", incrCounter(modul)); printf("...\n"); return 0; }

Page 115: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

115

Dobije se ispis: 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2,...

Važno je uočiti: kada se statička lokalna varijabla u deklaraciji i inicijalizira (pr. static int count=0;), ta inicijalna vrijednost vrijedi samo pri prvom pozivu funkcije.

Inicijalizacija nije ista kod lokalnih statičkih i lokalnih automatskih varijabli. Vrijednost lokalnih automatskih varijabli se inicijalizira na vrijednost opisanu inicijalizacijom pri svakom pozivu funkcije, a vrijednost lokalnih statičkih varijabli se inicijalizira na vrijednost opisanu inicijalizacijom samo pri prvom pozivu funkcije.

9.1.3 Globalne varijable Globalne varijable su varijable koje se deklariraju izvan tijela funkcije. To su

"permanentne" varijable koje trajno zauzimaju memoriju za vrijeme trajanja programa. One se uvijek inicijaliziraju na vrijednost 0. Doseg globalnih varijabli je od točke definiranja do kraja datoteke u kojoj su definirane. Kasnije će biti pokazano kako se njihov doseg može proširiti i na druge datoteke

int main( void) { … /* ovdje ne postoji varijabla max */ } int max = 0; /* mjesto definicije varijable max */ void fun( … ) { … max = /* ovdje postoji globalna var. max */ }

Argumenti funkcije i lokalne varijable prekrivaju globalne varijable, ako imaju isto ime.

void fun( … ) { int max; /* lokalna var. max prekriva globalnu var max */ max = ... }

Primjer: Prethodni je program countmod3.c izmijenjen je na način da obje funkcije, incrCounter() i main(), koriste globalnu varijablu count.

/* Datoteka: count.c */ #include <stdio.h> int count; /* globalna varijabla, /* inicijalizirana na vrijednost 0 */ int incrCounter(int mod) { count++; if(count == mod) count = 0;

Page 116: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

116

return count; } int main(void) { int i,modul=3; for(i=0; i<=10; i++) { incrCounter(modul); printf("%d, ", count); } printf("...\n"); return 0; }

Eksterne globalne varijable

Ako se nekoj datoteci deklarira globalna varijabla, ona se može dosegnuti i iz drugih kompilacijskih jedinica, ako se u tim datotekama deklarira kao eksterna (ili vanjska) varijabla. Deklaracija eksterne varijable označava se prefiksom extern, primjerice:

extern int max; void dump( … ) { max = ... }

Primjer: Prethodni program brojača po modulu mod, napisan je u dvjema datotekama. U prvoj datoteci, imena "counter.c", definirana je globalna varijabla count i funkcija incrCounter(). U drugoj datoteci, imena "countmain.c", definirana je funkcija main() koji koristi vrijednost od count i funkciju incrCounter().

/* Datoteka1: counter.c */ /* stanje brojača prije poziva funkcije counter */ int count=0; incrCounter(int mod) { count++; if(count >= mod) count = 0; } /* Datoteka2: countmain.c */ extern int count; void incrCounter(int mod); int main(void) { int i,mod=3; for(i=0; i<=10; i++) { incrCounter(mod);

Page 117: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

117

printf("%d, ", count); } printf("...\n"); return 0; }

Program se kompilira komandom:

c:>cl countmain.c counter.c

U komandnoj liniji se navode imena obje izvorne datoteke. Konačni izvršni program će imati ime datoteke koja je prva zadana: countmod.exe. Rezultat izvršenja programa biti će isti kao i u prethodnom primjeru.

Statičke globalne varijable

Globalne varijable se također mogu deklarirati s prefiksom static. Takove varijable se nazivaju statičke globalne varijable. One su vidljive samo u kompilacijskoj jedinici (datoteci) unutar koje su i definirane. Ne može ih se koristiti u drugim datotekama.

Zašto se koriste statičke globalne varijable? To će biti objašnjeno kada se objasni ideja modularnog programiranja i princip "skrivanja podataka" (eng. data hidding).

9.1.4 Moduli Modul sadrži skup međuovisnih globalnih varijabli i funkcija zapisanih u jednoj ili više

datoteka. Moduli se obično formiraju u dvije grupe datoteka:

1. datoteke specifikacije modula (ime.h ) sadrže deklaracije funkcija i (eksternih) globalnih varijabli koje su implementirane unutar modula.

2. datoteke implementacije (ime.c ) sadrže definicije varijabli i funkcija

Implementacijske se datoteke mogu kompilirati kao samostalne kompilacijske jedinice, a dobiveni objektni kod se može pohraniti u biblioteku potprograma. Neposrednu korist od ovakvog načina formiranja programa najbolje će pokazati sljedeći primjer.

Primjer: bit će realiziran modul koji sadrži funkcije brojača po modulu mod. Problem će biti obrađen nešto općenitije nego u prethodnim primjerima. Najprije se vrši specifikacija modula. 1. Specifikacija modula je opisana u datoteci "counter.h".

/* Datoteka: counter.h * specifikacija funkcija brojača po modulu mod */ void reset_count(int mod); /* Funkcija: inicira brojač na početnu vrijednost nula * i modul brojača na vrijednost mod. Ako je mod<=1, * modul brojaca se postavlja na vrijednost INT_MAX */ int getCount(void); /* Funkcija: vraća trenutnu vrijednost brojača */ int getModulo(void); /* Funkcija: vraća trenutnu vrijednost modula brojača */ int incrCounter(void);

Page 118: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

118

/* Funkcija: incrementira vrijednost brojača za 1 * Ako vrijednost brojača postane jednaka ili veća, * od zadamog modula vrijednost brojača postaje nula. * Vraća: trenutnu vrijednost brojača */

Prema ovoj specifikaciji predviđeno je da se brojačem upravlja pomoću dvije funkcije: incrCounter(), koja inkrementira brojač, i reset_count(), koja postavlja početno stanje brojača. Stanje brojača se očitava pomoću funkcija getCount() i getModulo(). Nije predviđeno da korisnik pristupa globalnim varijablama.

2. Implementacija modula je opisana u datoteci "counter.c". U toj datoteci su definirane dvije statičke globalne varijable _count i _mod. Prefiks static znači da su one vidljive samo u ovoj datoteci. Početno je vrijednost _mod postavljena na maksimalnu moguću cjelobrojnu vrijednost. Zatim slijede definicije funkcija koje su određene specifikacijom.

/* Datoteka: counter.c * Implementacija funkcija brojača po modulu: mod */ #include <limits.h> /* zbog definicija INT_MAX*/ /* globalne varijable */ static int _count = 0; /* početno stanje brojača */ static int _mod = INT_MAX; /*2147483647*/ void resetCounter(int mod) { _count= 0; if(mod <= 1) _mod = INT_MAX; else _mod = mod; } int getCount(void) { return _count;} int getModulo(void){ return _mod; } int incrCounter(void) { _count++; if(_count >= _mod) _count = 0; return _count; }

3. Testiranje modula se vrši programom "testcount.c":

/* Datoteka: testcount.c */ #include <stdio.h> #include "counter.h" int main(void) { int i; resetCounter(5);

Page 119: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

119

printf("Brojac po modulu %d \n", getModulo()); for(i=0; i<=10; i++) { incrCounter(); printf("%d, ", getCount()); } printf("...\n"); return 0; } c:>cl testcounter.c counter.c c:> testcounter Brojac po modulu 5 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, ...

Nakon što je modul testiran, može ga se primijetiti i u drugim programima. Veza između glavnog programa i modula je opisana u deklaracijama specifikacijske datoteke "counter.h", pa za primjenu modula nije potrebno znati kako je implementiran, već samo kako se koriste njegove funkcije.

Može se reći da modul opisuje apstraktni objekt brojača, koji sam nadgleda vlastito stanje. Sve varijable koje su potrebne za definiranje stanja ovog objekta su deklarirane kao statičke, pa nisu vidljive izvan modula. Ovaj se princip, “skrivanja varijabli” od korisnika modula, naziva "data hidding" ili "data encapsulation", a neobično je popularan u softverskom inženjerstvu jer se njime postiže neovisnost podataka iz različitih modula. Na ovaj je način lakše provoditi projekte u kojima sudjeluje više programera.

9.1.5 Formiranje programskih biblioteka Prethodni program se može kompilirati na sljedeći način:

c:>cl testcounter.c counter.c

Kada se radi s modulima, često je zgodniji način razvoja programa da se modul, koji je testiran, prevede u objektnu datoteku. To se vrši komandom:

c:>cl –c counter.c

(Parametar komandne linije –c je poruka kompilatoru da se prijevod izvrši u strojni kod, bez stvaranja izvršne datoteke). Nakon ove komande dobije se datoteka "counter.obj". Sada se izvršni program može dobiti i pomoću komande:

c:>cl testcounter.c counter.obj

Dobra strana ovakovog pristupa je da se ubrzava proces stvaranja programa, jer ne treba uvijek iznova kompilirati datoteku "counter.c". Više objektnih datoteka se može združiti u programske biblioteke. Uobičajeno, programske datoteke imaju ekstenziju .lib (ili .a na Unixu). Pokazat ćemo kako se formira biblioteka potprograma pomoću Microsoft program "lib.exe". Pretpostavka je da želimo objektnu datoteku "counter.obj" uvrstiti u biblioteku koja se zove "mylib.lib". To se ostvaruje komandom:

c:>lib /OUT:mylib.lib counter.obj

(/OUT: je parametar komandne linije iza kojeg se navodi ime biblioteke).

Page 120: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

120

Izvršni se program može dobiti komandom:

c:>cl testcount.c mylib.lib

Očito je da kompilator kôd za modul brojača dobiva iz biblioteke "mylib.lib". Na sličan način formirana je standardna biblioteka C-jezika.

9.2 Funkcionalna dekompozicija programa "od vrha prema dolje" Kada se razvija neki program, polazište je zadatak kojeg treba obaviti. Analizom problema

određuju se radnje i algoritmi kojima se može taj zadatak obaviti. U proceduralnim je jezicima najbolji način programske realizacije neke radnje da se ona specificira i implementira pomoću neke funkcije.

Radnje, odnosno zadaci, koje treba obaviti neka funkcija, također se mogu realizirati pomoću niza funkcija. U tom slučaju kažemo da se razvoj programa vrši funkcionalnom dekompozicijom "od vrha prema dolje". Praksa je pokazala da je funkcionalna dekompozicija prihvatljiva kao metoda programiranja u većini slučajeva.

Kao studijski primjer razvoja programa funkcionalnom dekompozicijom izradit ćemo program pomoću kojeg se igra popularna igra "točkice i kružići" (ili tic-tac-toe).

Tic-Tac-Toe Tic-Tac-Toe je igra u kojoj se nadmeću dva igrača. Igrači, jedan za drugim označavaju polja u 9 kvadratića, prvi s križićem, a drugi s točkicom. Pobjednik je onaj koji redno, stupčano ili dijagonalno prvi ispuni 3 polja. Prvi igrač unosi križić, a drugi točkicu.

Primjer igre u kojoj je pobijedio drugi igrač, tj. onaj koji unosi točkicu.

Označavanje pozicije kvadratića na igračkoj ploči

| o | X | o | |---|---|---| | X | o | X | |---|---|---| | o | | X |

| 1 | 2 | 3 | |---|---|---| | 4 | 5 | 6 | |---|---|---| | 7 | 8 | 9 |

Kako napisati program u kojem će jedan od igrača biti računalo, a drugi igrač je čovjek. Problem se može formulirati na sljedeći način:

1. Nacrtaj igraču ploču, i upute za igru. 2. Postavi početno stanje (ploča je prazna). 3. Ispitaj da li prvi potez vuče računalo ili čovjek. 4. Prati igru sve dok se ne ostvari pobjednička kombinacija, ili dok se ne ispune svi

kvadratići. 5. Ako korisnik želi ponoviti igru vrati se na korak 2, inače završi program.

Problem je postavljen sasvim općenito. Sada treba izvršiti razradu problema, tako da se detaljnije opišu pojedini koraci u opisanom apstraktnom algoritmu. Pored postupaka koje treba obaviti, neophodno je odrediti strukturu podataka, kojom će se pratiti stanje programa.

U ovom slučaju, za stanje igrače ploče koristit će se znakovna matrica od 3x3 elementa, deklarirana s

Page 121: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

121

char kvadrat[3][3];

Pojedini element matrice može poprimiti samo tri vrijednosti: ' ', 'X' i 'o' (prazno, križić i točkica). Za označavanje igrača koristit će se dvije globalne varijable

char racunalo, covjek;

prva će sadržavati znak koji unosi računalo, a druga znak koji unosi čovjek. Ako jedna od ovih varijabli, primjerice covjek, ima vrijednost 'X', to znači da prvi potez vuče čovjek, inače, prvi potez vuče računalo.

Na temelju početnog algoritma može se zaključiti da se problem može realizirati pomoću 5 neovisnih funkcija. Specifikacija tih funkcija je sljedeća:

void Upute(void); /* Ispisuje igraču ploču, i upute za igru */ void OcistiPlocu(void); /* Postavlja početno stanje, * svi elementi matrice kvadrat imaju vrijednost ' ' */ void PostaviPrvogIgraca(void); /* Na temelju interakcije s korisnikom programa odlučuje da li * prvi potez vuče računalo ili čovjek. * Ako prvi potez vuče: racunalo = 'o'; covjek = 'X'; * inače: racunalo = 'X'; covjek = 'o'; */ void IgrajIgru(void); /* Ovom se funkcijom kontrolira tijek igre, interakcija s * korisnikom, odlučuje o potezima koje inteligentno izvršava * računalo i izvještava se o ispunjenosti igrače ploče. Funkcija * završava kada se ispune uvjeti za pobjedu prvog ili drugog * igrača ili ako su označeni svi kvadratići. */ int PonoviIgru(void); /* Ovom funkcijom se od korisnika traži da potvrdi da li želi * ponoviti igru. Ako korisnik želi ponoviti igru tada funkcija * vraća 1, inače vraća vrijednost 0. */

Uz pretpostavku da će se kasnije uspješno implementirati ove funkcije, glavni program se može napisati u obliku:

/* Program TicTacToe.c */ #include <stdio.h> void Upute(void); void OcistiPlocu(void); void PostaviPrvogIgraca(void); void IgrajIgru(void); int PonoviIgru(void); char kvadrat[3][3]; char racunalo, covjek;

Page 122: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

122

int main(void) { Upute(); /* korak 1*/ do { OcistiPlocu(); /* korak 2*/ PostaviPrvogIgraca(); /* korak 3.*/ IgrajIgru(); /* korak 4.*/ }while(PonoviIgru()); /* korak 5.*/ return 0; }

Što je do sada napravljeno?

Problem je rastavljen (dekomponiran) na pet manjih, međusobno neovisnih problema. Neki od ovih problema se mogu odmah riješiti, primjerice funkcije Upute() i OcistiPlocu() se mogu implementirati na sljedeći način: void Upute() { printf("\nIgra - tic tac toe - krizici i tockice\n\n"); printf("\t | 1 | 2 | 3 | \n"); printf("\t |---|---|---| \n"); printf("\t | 4 | 5 | 6 | \n"); printf("\t |---|---|---| \n"); printf("\t | 7 | 8 | 9 | \n\n"); printf("Cilj igre je ispuniti tri kvadratica u redu: \n"); printf("horizontalno, vertikalno ili dijagonalno. \n" printf("Igra se protiv racunala.\n"); printf("Prvi igrac je oznacen krizicem X, a drugi tockicom o.\n\n"); } void OcistiPlocu(void) { int redak, stupac; for (redak = 0; redak < 3; redak++) for (stupac = 0; stupac < 3; stupac++) kvadrat[redak][stupac] = ' '; } Za ostale funkcije potrebna je daljnja dorada problema. Posebno opsežan problem je definiranje funkcije IgrajIgru() u kojoj se obavlja više operacija. Prije definiranja te funkcije izvršit će se definiranje funkcija PostaviPrvogIgraca() i PonoviIgru() jer je njihova implementacija jednostavna i može se odrediti neposredno iz zadane specifikacije.

Dorada funkcije PostaviPrvogIgraca():

1. Izvijesti korisnika da on bira tko će povući prvi potez. Ako želi biti prvi na potezu neka pritisne tipku 'D', inače neka pritisne tipku 'N'.

2. Motri korisnikov odziv, sve dok se ne pritisne jedna od ove dvije tipke. 3. Ako je pritisnuta tipka 'D', tada: racunalo = 'o'; covjek = 'X';

inače: racunalo = 'X'; covjek = 'o';.

void PostaviPrvogIgraca(void) { int key; printf("Da li zelite zapoceti prvi? (d/n)\n"); do { key = toupper(getchar());

Page 123: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

123

} while ((key != 'D') && (key != 'N')); if (key == 'D') { racunalo = 'o'; covjek = 'X'; } else { racunalo = 'X'; covjek = 'o'; } }

Dorada funkcije PonoviIgru():

1. Upitaj korisnika da li želi ponoviti igru. 2. Motri korisnikov odziv, sve dok se ne pritisne tipku 'D' ili 'N'. 3. Ako je pritisnuta tipka 'D' funkcija vraća vrijednost 1, inače vraća vrijednost 0.

int PonoviIgru(void) { int key; printf("Da li zelite ponovo igrati? (D/N) "); do { key = toupper(getchar()); } while ((key != 'D') && (key != 'N')); return ( key == 'D'); }

Dorada funkcije IgrajIgru():

Prvo treba uočiti da može biti maksimalno 9 poteza, jer ploča ima 9 kvadratića. Ako poteze numeriramo od 1 do 9 onda vrijedi da prvi igrač vuče poteze koji su numerirani 1,3,5... Dakle, kada je neparna vrijednost poteza, prvi igrač unosi 'X' u izabrani kvadratić, a kada je parna vrijednost poteza drugi igrač unosi 'o'. Operacije koje izvodi ova funkcija mogu se iskazati algoritmom:

1. Za maksimalno 9 poteza 1.1 Dobavi izbor aktivnog igrača (računalo ili korisnik) 1.2 Nacrtaj ploču s točkicama i križićima 1.3 Ako je pobjednik računalo ispiši: "Pobijedio sam te!" i prekini igru, inače, ako je pobjednik korisnik, ispiši: "Pobijedio si!" i prekini igru. 2. Ako nije određen pobjednik ni nakon 9 poteza, ispiši poruku: "Ovaj put nema pobjednika"

Ovaj se algoritam može programski realizirati pomoću varijable potez, u kojoj se bilježi redni broj poteza, i sljedeće tri funkcije:

void DobaviPotez(int potez); /* na temelju rednog broja poteza određuje se koji je igrač * na potezu, dobavlja njegov izbor i * označava novo stanje matrice kvadrat */ void NacrtajPlocu(void); /* crta ploču na temelju stanja matrice kvadrat */ int PostajeDobitnik(char simbol); /* određuje da li simbol (X ili o) ispunja matricu na način da * je postignuta dobitnička kombinacija */

Uz pretpostavku uspješne implementacije ovih funkcija, može se funkcija IgrajIgru() napisati u obliku:

Page 124: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

124

void IgrajIgru(void) { int potez = 1; while (potez <= 9) { DobaviPotez(potez); NacrtajPlocu(); if (PostajeDobitnik(racunalo)){ printf("\nPobijedio sam te!!!\n\n"); break; } else if (PostajeDobitnik(covjek)) { printf("\nCestitam, pobijedio si!\n\n"); break; } potez++; } if (potez > 9) printf("\nOvaj put nema pobjednika.\n\n"); }

Realizacija funkcija DobaviPotez(), NacrtajPlocu() i PostajeDobitnik() /* Funkcija: DobaviPotez(int potez) * Izbor se dobije tako da se utvrdi * da li je varijabla potez parna ili neparna. * Ako je parna, igra 'X', inače igra 'o' */ void DobaviPotez(int potez) { if (potez % 2 == 1) if (racunalo == 'X') PotezRacunala(); else PotezCovjeka(); else if (racunalo == 'o') PotezRacunala(); else PotezCovjeka(); } /* Funkcija: NacrtajPlocu() * prikazuje igraču ploču na standardnom izlazu */ void NacrtajPlocu(void) { int redak, stupac; printf("\n"); for (redak = 0; redak < 3; redak++) { printf("\t| %c | %c | %c |\n", kvadrat[redak][0], kvadrat[redak][1], kvadrat[redak][2]); if (redak != 2) printf("\t|---|---|---|\n"); } printf("\n"); return; } /* Funkcija: int PostajeDobitnik(char simbol) * Provjera da li je simbol (X i o) pobjednik, ispitivanjem

Page 125: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

125

* ispunjenosti redaka, stupaca ili dijagonala ploče */ int PostajeDobitnik(char simbol) { int redak, stupac; for (redak = 0; redak < 3; redak++) /* ispitajmo 3 retka */ { if ( (kvadrat[redak][0] == simbol) && (kvadrat[redak][1] == simbol) && (kvadrat[redak][2] == simbol)) return 1; } for (stupac = 0; stupac < 3; stupac++) /* ispitajmo 3 stupca */ { if ( (kvadrat[0][stupac] == simbol) && (kvadrat[1][stupac] == simbol) && (kvadrat[2][stupac] == simbol)) return 1; } /* i konačno dvije dijagonalne kombinacije */ if ( (kvadrat[0][0] == simbol) && (kvadrat[1][1] == simbol) && (kvadrat[2][2] == simbol)) return 1; if ( (kvadrat[0][2] == simbol) && (kvadrat[1][1] == simbol) && (kvadrat[2][0] == simbol)) return 1; return 0; } /* Funkcija: PotezCovjeka(void) */ /* vrši dobavu poteza čovjeka */ void PotezCovjeka(void) { int pozicija; do { printf("Otipkaj poziciju znaka %c (1..9): ", covjek); scanf("%d", &pozicija); } while (!IspravnaPozicija(pozicija)); kvadrat[(pozicija - 1) / 3][ (pozicija - 1) % 3] = covjek; } /* Funkcija: IspravnaPozicija(int pozicija) * vraća 1 ako pozicija prazna, inače vraća 0 */ int IspravnaPozicija(int pozicija) { int redak, stupac; redak = (pozicija - 1) / 3; stupac = (pozicija - 1) % 3; if ((pozicija >= 1) && (pozicija <= 9))

Page 126: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

126

if (kvadrat[redak][stupac] == ' ') return 1; return 0; } /* Funkcija: PotezRacunala() * inteligentno određuje potez racunala * (algoritam je napisan u obliku komentara) */ void PotezRacunala(void) { int pozicija; /* pronađi kvadrat u kojem */ pozicija = DobitnaPozicija(racunalo); /* može pobijediti racunalo */ if (!pozicija) /* ako ga nema, pronađi */ pozicija = DobitnaPozicija(covjek); /* gdje čovjek pobijeđuje */ if (!pozicija) /* ako ga nema */ pozicija = PraznaSredina(); /* centar je najbolji potez */ if (!pozicija) /* ako ga nema */ pozicija = PrazanUgao(); /* najbolji je potez u kutovima */ if (!pozicija) /* ako ga nema */ pozicija = PrazanaStrana(); /* ostaje mjesto na stranicama */ printf("\nJa sam izabrao kvadratic: %d!\n", pozicija); kvadrat[(pozicija - 1) / 3][ (pozicija - 1) % 3] = racunalo; } /* * Funkcija: int DobitnaPozicija(char simbol), * ako postoji dobitna kombinacija za simbol, * vraća poziciju kvadratića, inaće vraća 0; */ int DobitnaPozicija(char simbol) { int pozicija, redak, stupac; int rezultat = 0; /* Analiziraj stanje u svih 9 kvadratića. Za svaki kvadratić: * ako je prazan, ispuni ga danim simbolom, i provjeri da li je * je to dobitni potez. Ako jest, zapamti ga u varijabli rezultat * i ponovo poništi taj kvadratić. * Nakon završetka petlje rezultat sadrži dobitni potez ili nulu ako * nije pronađen dobitni potez. * Funkcija vraća vrijednost varijable rezultat */ for (pozicija = 1; pozicija <= 9; pozicija++) { redak = (pozicija - 1) / 3; stupac = (pozicija - 1) % 3; if (kvadrat[redak][stupac] == ' ') { kvadrat[redak][stupac] = simbol; if (is_wining(simbol)) rezultat = pozicija; kvadrat[redak][stupac] = ' '; } } return rezultat;

Page 127: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

127

} /* Funkcija: int PraznaSredina() * vraća 5 ako je srednji kvadratić prazan, inaće vraća 0 */ int PraznaSredina(void) { if (kvadrat[1][1] == ' ') return 5; else return 0; } /* Funkcija: int PrazanUgao() * vraća poziciju jednog od praznih kuteva, * ako su svi kutevi zauzeti, vraća 0 */ int PrazanUgao(void) { if (kvadrat[0][0] == ' ') return 1; if (kvadrat[0][2] == ' ') return 3; if (kvadrat[2][0] == ' ') return 7; if (kvadrat[2][2] == ' ') return 9; return 0; } /* Funkcija: int PrazanaStrana() * vraća poziciju jedne od praznih stranica kvadrata, * ako su sve pozicije zauzete vraća 0 */ int PrazanaStrana(void) { if (kvadrat[0][1] == ' ') return 2; if (kvadrat[1][0] == ' ') return 4; if (kvadrat[1][2] == ' ') return 6; if (kvadrat[2][1] == ' ') return 8; return 0; } Slijed dekompozicije funkcija tipa "od vrha prema dolje" ilustriran je na slici 9.1.

Page 128: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

128

TIc-tac-Toemain()

Upute OcistiPlocu PostaviPrvogIgraca Ponovi IgruIgrajIgru

NacrtajPlocu DobaviPotez PostajeDobitnik

PotezCovjekaPotezKompjutera

DobitnaPozicija PraznaSredina PrazanUgao PraznaStrana IspravnaPozicija

Slika 9.1 Dekompozicije funkcija u programu Tic-Tac-Toe

9.3 Zaključak Programi se u C jeziku mogu pisati u više odvojenih datoteka - modula. Ponovno prevođenje cijelog programa uzima dosta vremena, dok se pojedina datoteka, koja je manja od ukupnog programa, prevodi mnogo brže. U modulu se može definirati određeni skup funkcija koji se može koristiti i u drugim programima. Module, koji sadrže često korištene funkcije, može se u obliku strojnog koda uvrstiti u biblioteke potprograma. Modul se može pisati, testirati, i ispravljati neovisno od ostatka programa. Proces ispravljanja je pojednostavljen, jer se analizira manji dio programa. Moduli omogućavaju veću preglednost i logičku smislenost programskog koda, jer se u njima obično obrađuje jedinstvena problematika. Primjerice, za obradu matematičkih problema postoje različiti programski paketi s odvojenim modulima za rad s kompleksnim brojevima, vektorima, matricama, itd. Korištenjem principa odvajanja specifikacije od implementacije modula, i skrivanjem podataka koji bilježe stanja objekta koji modul opisuje, dobivaju se moduli neovisni od programa u kojem se koriste. To znatno olakšava timski rad u razvoju softvera.

Page 129: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

129

10 Rad s pokazivačima

Naglasci: • tip pokazivača • operacije s pokazivačima • ekvivalentnost reference niza i pokazivača • prijenos varijable u funkciju • void pokazivači • pokazivači na funkcije • polimorfne funkcije

Pokazivači su varijable koje sadrži adresu nekog memorijskog objekta: varijable ili funkcije. Njihova primjena omogućuje napredne programske tehnike: dinamičko alociranje memorije, apstraktni tip podataka i polimorfizam funkcija. Da bi se moglo shvatiti ove tehnike programiranja, u ovom će poglavlju biti pokazano kako se vrše temeljne operacije s pokazivačima, a u sljedećim će poglavljima biti pokazana njihova primjena u programiranju.

10.1 Tip pokazivača Pokazivačkim varijablama se deklaracijom pridjeljuje tip. Deklariranje pokazivačke varijable se vrši na način da se u deklaraciji ispred imena pokazivačke varijable upisuje zvjezdica *. Primjerice,

int *p,*q; /* p i q su pokazivači na int */

označava da su deklarirane pokazivačke varijable p i q, kojima je namjena da sadrže adresu objekata tipa int. Kaže se da su p i q "pokazivač na int".

Pokazivači, prije upotrebe, moraju biti inicijalizirani na neku realnu adresu. To se ostvaruje tzv. adresnim operatorom &:

p = &sum; /* p iniciran na adresu varijable sum */ q = &arr[2]; /* q iniciran na adresu trećeg elementa niza arr*/

Adresa Vrijednost Identifikator

0x09AC 0x09B0 ...... 0x0F10 0x0F14 0x0F18 ...... 0x1000 0x1004

-456 ...... ...... ...... 0x09AC 0x0F18

sum ...... ...... arr[0] arr[1] arr[2] ...... p q

Slika 10.1 Adresa i vrijednost varijabli

Page 130: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

130

10.2 Operacije s pokazivačima Temeljne operacije s pokazivačima su:

1. Deklariranje pokazivača je postupak kojim se deklarira identifikator pokazivača, na način da se između oznake tipa na koji pokazivač pokazuje i identifikatora pokazivača upisuje se operator indirekcije *.

int x, *p; /* deklaracija varijable x i pokazivača p */

2. Inicijalizacija pokazivača je operacija kojom se pokazivaču pridjeljuje vrijednost koja je jednak adresi objekta na koji on pokazuje. Za dobavu adrese objekta koristi se unarni adresni operator '&' .

p = &x; /*p sadrži adresu od x */

3. Dereferenciranje pokazivača je operacija kojom se pomoću pokazivača pristupa memorijskom objektu na kojega on pokazuje, odnosno, ako se u izrazima ispred identifikatora pokazivača zapiše operator indirekcije *, dobiva se dereferencirani pokazivač (*p). Njega se može koristiti kao varijablu, odnosno referencu memorijskog objekta na koji on pokazuje.

y = *p; /* y dobiva vrijednost varijable koju p pokazuje*/ /* isti učinak kao y = x */ *p = y; /* y se pridjeljuje varijabli koju p pokazuje */ /* isti učinak kao x = y */

Djelovanje adresnog operatora je komplementarno djelovanju operatora indirekcije, i vrijedi da naredba y = *(&x); ima isti učinak kao i naredba y = x;.

Unarni operatori * i & su po prioritetu iznad većine operatora (kada se koriste u izrazima).

y = *p + 1; ⇔ y = (*p) + 1;

Jedino postfiksni unarni operatori (-- ++, [], ()) imaju veći prioritet od * i & prefiks operatora:

y = *p++; ⇔ y = *(p++);

Pokazivač kojem je vrijednost nula (NULL) naziva se nul pokazivač.

p = NULL; /* p pokazuja na ništa */

S dereferenciranim pokazivačem (*p) se može manipulirati kao sa varijablom pripadnog tipa, primjerice,

int x, y, *px, *py; px = &x; /* px sadrži adresu od x – ne utječe na x */ *px = 0; /* vrijednost x postaje 0 - ne utječe na px */ py = px; /* py također pokazuje na x - ne utječe na px ili x */ *py += 1; /* uvećava x za 1 - ne utječe na px ili py */ y = (*px)++; /* y = 1, a x =2 - ne utječe na px ili py */

Page 131: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

131

10.3 Pokazivači kao argumenti funkcije Pokazivači se često koriste kao argumenti funkcije, jer se na taj način može prenositi

varijable u funkciju. To se postiže na način da se kao parametar funkcije deklarira pokazivač na neki objekt, primjerice:

void Increment(int *pVar);

Prema pravilu C jezika, pri pozivu funkcije se prenosi vrijednost stvarnog argumenta, a u funkciji se parametar funkcije tretira kao lokalna varijabla koja ima vrijednost stvarnog argumenta. U slučaju kada je parametar funkcije pokazivač, stvarni argument funkcije je adresa objekta koji ima isti tip kao pokazivač. Korištenjem indirekcije pokazivača može se pristupiti tom objektu i mijenjati njegov sadržaj, dakle taj se objekt može tretirati kao varijabla koju se koristi i u funkciji.

Ovaj način prenošenja parametara funkcije je prikazan u programu ptr-parm.c. U njemu se pomoću funkcije void Increment(int *pVar){(*pVAr)++;} može inkrementirati vrijednost bilo koje cjelobrojne varijable imena var. To se vrši s pozivom funkcije u obliku Increment(&var);. Uočite da se u definiciji funkcije koristi pokazivački parametar, a pri pozivu funkcije se kao argument koristi adresa varijable na koju ova funkcija djeluje.

/* Datoteka: ptr-parm.c */ #include <stdio.h> void Increment(int *pVar) { /* Funkcija inkrementira vrijednost varijable, čija se adresa * prenosi u funkciju kao vrijednost pokazivača pVar * Varijabli se pristupa pomoću indirekcije pokazivača pVar */ (*pVar)++; } int main() { int var = 7; Increment(&var); /* argument je adresa varijable */ printf ("var = %d\n", var); return 0; }

Ispis je:

var = 8

U nekim knjigama se ovaj način prijenosa argumenata naziva "prijenos reference" (call by reference), međutim taj naziv nije ispravan jer se u ovom slučaju ne prenosi referenca varijable ( ime koje označava adresu) već se prenosi vrijednost pokazivača (adresa), a varijabli na koju on pokazuje pristupa se indirekcijom pokazivača.

Prijenos varijabli u funkcije pomoću pokazivača koristi se uvijek kada želimo da se pomoću jedne funkcije istovremeno mijenja vrijednost više varijabli.

Page 132: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

132

Primjer: Definirana je funkciju swap(), pomoću koje se može izvršiti zamjena vrijednost dvije varijable:

/* Datoteka: swap.c * Zamjena vrijednosti dvije varijable pomoću swap funkcije */ */ #include <stdio.h> void swap( int *x, int *y) { int t; t = *x; *x = *y; *y = t; } int main() { int a = 1, b = 2; printf("a=%d b=%d\n", a, b); swap(&a , &b); printf("Nakon poziva swap(&a, &b)\n"); printf("a=%d b=%d\n", a, b); }

Ispis je:

a=1 b=2

Nakon poziva swap(&a, &b)

a=2 b=1

10.4 Pokazivači i nizovi Potrebno je najprije navesti nekoliko pravila koje vrijede za reference niza i pokazivače

koji pokazuju na nizove. Analizirat ćemo niz a i pokazivač p:

int a[10]; int *p;

Pravila su: 1. Ime niza, iza kojeg slijedi oznaka indeksa, predstavlja element niza s kojim se manipulira na isti način kao i sa prostim varijablama. 2. Ime niza, zapisano bez oznake indeksa je "pokazivačka konstanta - adresa" koja pokazuje na prvi element niza. Ona se može pridijeliti pokazivačkoj varijabli. Naredbom

p = a;

za vrijednost pokazivača p postavlja se adresa a[0]. Ovo je ekvivalentno naredbi:

Page 133: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

133

p = &a[0];

3. Pravilo pokazivačke aritmetike:

Ako p pokazuje na a[0], tada (p + i) pokazuje na a[i].

Ako pi pokazuje na a[i], tada pi + k pokazuje na a[i+k], odnosno, ako je

pi = &a[i];

tada vrijedi odnos;

*(pi+k) ⇔ *(p+i+k) ⇔ a[i+ k]

Gornja pravila određuju da se aritmetičke operacije s pokazivačima ne izvode na isti način kao što se izvode aritmetičke operacije s cijelim brojevima.

Za prosti cijeli broj x vrijedi:

vrijednost(x ± n) = vrijednost(x) ± n dok za pokazivač p vrijedi

vrijednost(p ± n) = vrijednost(p) ± n*sizeof(*p) 4. Ekvivalentnost indeksne i pokazivačke notacije niza.

Ako je deklariran niz array[N], tada izraz *array označava prvi element, *(array + 1) predstavlja drugi element, itd. Poopći li se ovo pravilo na cijeli niz, vrijede sljedeći odnosi:

*(array) ⇔ array[0] *(array + 1) ⇔ array[1] *(array + 2) ⇔ array[2] ... *(array + n) ⇔ array[n]

što odgovara činjenici da ime niza predstavlja pokazivačku konstantu. S pokazivačima se također može koristiti indeksna notacija. Tako, ako je inicijaliziran pokazivač p:

p = array;

tada vrijedi:

p[0] ⇔ *p ⇔ array[0] p[1] ⇔ *(p + 1) ⇔ array[1] p[2] ⇔ *(p + 2) ⇔ array[2] ... p[n] ⇔ *(p + n) ⇔ array[n]

Jedina razlika u korištenju reference niza i pokazivača na taj niz je u tome da se memorijskim referencama ne može mijenjati vrijednost adrese koju oni označavaju.

a++; je nedozvoljen izraz, jer se ne može mijenjati konstanta

Page 134: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

134

p++; je dozvoljen izraz, jer je pokazivač p varijabla Pošto se pokazivaču može mijenjati vrijednost, obično se kaže da se pomoću pokazivača može "šetati po nizu". Primjer: U obje sljedeće petlje ispisuje se vrijednost 10 elemenata niza a.

int *p = a; for (i = 0; i < 10; i++, p++) printf("%d\n", *p);

for (i = 0; i < 10; i++) printf("%d\n", a[i]);

U prvoj petlji se koristi pokazivač p za pristup elementima niza a[]. Početno on pokazuje na prvi element niza a[]. U petlji se zatim ispisuje vrijednost tog elementa (dereferencirani pokazivač *p), i inkrementira vrijednost pokazivača, tako da se u narednom prolazu petlje s njime referira sljedeći element.

Kompilator ne prevodi ove petlje na isti način, iako je učinak u oba slučaja isti: bit će ispisana vrijednost 10 elemenata niza a[]. Ako je učinak isti, možemo se dalje upitati koja će se od ovih petlji brže izvršavati. To ovisi o kvaliteti kompilatora i o vrsti procesora. Kod starijih procesora brže se izvršava verzija s pokazivačem, jer se u njoj u jednom prolazu petlje vrše dva zbrajanja, dok se u drugom slučaju mora (skriveno) izvršiti i jedno množenje. Ono je potrebno da bi se odredila adresa elementa a[i], jer je

adresa(a[i])= adresa(a[0]) + i*sizeof(int).

Kod novijih se procesora operacija indeksiranja izvršava veoma brzo, pa se u tom slučaju preporučuje korištenje verzije s indeksnim operatorom.

10.5 Pokazivači i argumenti funkcije tipa niza Pri pozivu funkcije, stvarni se argument kopira u formalni argument (parametar) funkcije.

U slučaju da je argument funkcije ime niza kopira se adresa prvog elementa, dakle stvarni argument koji se prenosi u funkciju je vrijednost pokazivača na prvi element niza. Stoga se prijenos niza u funkciju može deklarirati i pomoću pokazivača.

Primjer: Sljedeće tri funkcije imaju isti učinak i mogu se pozvati s istim argumentima:

void print(int x[], int N ) { int i; for (i = 0; i < N; i++) printf("%d\n", x[i]); }

void print(int *x, int N) { while (N--) {

printf("%d\n", *x); x++; } }

void print(int *x, int N) { int i; for (i=0; i<N;i++) printf("%d\n", x[i]); }

/* poziv funkcije print */ int niz[10], size=10; . . . . . print(niz, size);

Page 135: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

135

const osigurači

U prethodnom primjeru u funkciji print() se koriste vrijednosti elemenata niza x, a ne mijenja se njihova vrijednost. U takovim slučajevima je preporučljivo da se parametri funkcije deklariraju s prefiksom const.

void print(const int x[], int N ); ili void print(const int *x, int N );

Ovakva deklaracija je poruka kompilatoru da dojavi grešku ako u funkciji postoji naredba kojom se mijenja sadržaja elemenata niza, a programeru služi kao dodatno osiguranje da će izvršiti implementaciju funkcije koja neće mijenjati elemente niza.

Pomoću pokazivača se u funkcije mogu prenositi i proste varijable i nizovi.

Primjer: Napisat ćemo funkciju getMinMax() kojom se određuje maksimalna i minimalna vrijednost niza. Testirat ćemo je programom u kojem korisnik unosi 5 brojeva, a program ispisuje maksimalnu i minimalnu vrijednost.

/* Datoteka: minmax.c */ #include <stdio.h> #define N 5 /* radit ćemo s nizom od N elemenata */ void getMinMax(double *niz, int nelem, double *pMin, double *pMax) { /* funkcija određuje minimalni i maksimalni element niza * Parametri funkcije su: * niz – niz realnih brojeva * nelem – broj elemenata u nizu * pMin – pokazivac na minimalnu vrijednost * pMax - pokazivac na maksimalnu vrijednost /* int i= 0; *pMin = *pMax = niz[0]; for(i=1; i<N; i++) { if(niz[i] > *pMax ) *pMax = niz[i]; if(niz[i] < *pMin) *pMin = niz[i]; } } int main() { int i; double min, max, data [N]; /* 1. izvjesti korisnika da otkuca 5 realnih brojeva */ printf("Otkucaj %d realnih brojeva:\n", N); for (i=0; i<N; i++) scanf("%lg", &data[i]); getMinMax(data, N, &min, &max); /* ispisi minimalnu i maksimalnu vroijednost */ printf ("min = %lf max = %lf\n", min, max); return 0; }

Page 136: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

136

10.6 Patrametri funkcije tipa void pokazivača Ako se neki pokazivač deklarira pomoću riječi void,

void *p;

tada nije određeno na koji tip podatak on pokazuje. Njemu se može pridijeliti adresa bilo kojeg memorijskog objekta, ali se ne može vršiti pristup memorijskim objektima pomoću operatora indirekcije, jer on pokazuja na ništa. Većina današnjih kompilatora ne dozvoljava aritmetičke operacije s void pokazivačima.

Očito je da nema smisla koristiti void pokazivače kao regularne varijable. Oni pak mogu biti korisni kao parametri funkcija. Kada se void pokazivač koristi kao parametar funkcije tada se pri pozivu funkcije tom pokazivaču može pridijeliti adresa bilo kojeg memorijskog objekta, a unutar same funkcije se može s prefiksom (tip *) vršiti forsirana pretvorba pokazivačkog tipa.

Primjer: Definirana je funkcija void UnesiVrijednost(void *p, int tip), pomoću koje se može izvršiti unos različitih tipova podataka

/* Datoteka: unos.c * koristenje void pokazivaca kao parametra funkcije */ #include <stdio.h> #define CHAR 0 #define INT 1 #define FLOAT 2 #define DOUBLE 3 void UnesiVrijednost(void *p, int tip) { switch (tip) { case CHAR: printf( "Unesite jedan znak: \n"); scanf("%c", (char *) p); break; case INT: printf( "Unesite cijeli broj:\n"); scanf("%d", (int *) p); break; case FLOAT: printf( "Unesite realni broj:\n"); scanf("%g", (float *) p); break; case DOUBLE: printf( "Unesite realni broj:\n"); scanf("%lg", (double *) p); break; } fflush(stdin); /* odstrani višak znakova s ulaza*/ } int main() { double dval; int ival;

Page 137: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

137

UnesiVrijednost(&ival, INT); printf("Vrijednost je %d\n" , ival); UnesiVrijednost(&dval, DOUBLE); printf("Vrijednost je %lg\n" , dval); return 0; }

Uočite kako je korištena funkcija scanf(). Ispred imena argumenta nije korišten adresni

operator jer je vrijednost pokazivača p adresa. Ispred argumenta je eksplicitno označen tip. Ovakvi način korištenje void pokazivača je opasan, jer ako se pri pozivu funkcije ne pozovu kompatibilni argumenti, može doći do nepredvidivih rezultata, čak i do blokade računala.

10.7 Pokazivači na funkcije Funkcije su također memorijski objekti pa se može deklarirati i inicijalizirati pokazivače na

funkcije. Pravilo je da se za funkciju imena F, koja je deklarirana (ili definirana) u obliku:

oznaka_tipa F (list_ parametara); pokazivač na tu funkciju, imena pF, deklarira u obliku: oznaka_tipa ( *pF) (list_ parametara); Ime funkcije, napisano bez zagrada predstavlja adresu funkcije, pa se pridjelom vrijednosti:

pF = F;

inicira pokazivač pF na adresu funkcije F. Kada je pokazivač iniciran, indirekcijom pokazivača može se izvršiti poziv funkcije u obliku:

(*pF)(lista_argumenata);

ili još jednostavnije, sa

pF(lista_argumenata);

jer, oble zagrade predstavljaju operator poziva funkcije (kompilator sam vrši indirekciju pokazivača, ako se iza njega napišu oble zagrade). Primjerice, iskazom

double (*pMatFun)(double);

deklarira se pokazivač pMathFun kojem možemo pridijeliti adresu standardnih matematičkih funkcija jer one imaju isti tip parametara i rezultata funkcije (pr. double(sin(double)). Pokazivač na funkciju može biti i argument funkcije, primjerice funkcija

void Print( double (*pMatFun)(double), double x) { printf( "%lf", pMatFun (x)); }

Page 138: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

138

se može koristiti za ispis vrijednosti matematičkih funkcija.

Print(sin, 3.1); /* ispisuje se vrijednost sinusne funkcije za vrijednost argumenta 3.1*/ Print(cos, 1.7) /* ispisuje se vrijednost kosinus funkcije za vrijednost argumenta 1.7*/

Primjer: U programu pfunc.c koriste se pokazivači na funkciju za ispis vrijednosti standardnih i korisnički definiranih matematičkih funkcija. Izbor funkcije i argumenta funkcije vrši se u interakciji s korisnikom programa.

/* Datoteka: pfun.c * korištenje pokazivača na funkciju */ #include <stdio.h> #include <math.h> double Kvadrat (double x) { return x*x; } void PrintVal( double (*pFunc)(), double x) { printf( "\nZa x: %lf dobije se %lf\n", x, pFunc(x)); } int main() { double val=1; int choice; double (*pFunc)(double); printf("Upisi broj:"); scanf("%lf", &val); fflush(stdin); /* odstrani višak znakova s ulaza */ printf( "\n(1)Kvadrat \n(2)Sinus \n(3)Kosinus \n"); printf( "\nOdaberi 1, 2 li 3\n"); choice = getchar(); switch (choice) { case '1': pFunc = Kvadrat; break; case '2': pFunc = sin; break; case '3': pFunc = cos; break; default: return 0; } PrintVal (pFunc, val); return 0; }

Često se koriste nizovi pokazivača na funkciju. Primjerice, deklaracijom

#include math.h

Page 139: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

139

double (*pF[4])(double) = {sin, cos, tan, exp};

deklariran je niz od 4 elementa koji sadrže pokazivač na funkciju kojoj je parametar tipa double i koja vraća vrijednost tipa double. Također je izvršena i inicijalizacija elemenata niza na adresu standardnih matematičkih funkcija. Sada je naredba x = (*pF[1])(3.14) ekvivalentna naredbi x= cos(3.14). Primjer: U programu niz-pfun.c koristi se niz pokazivača na funkciju. U nizu se bilježe adrese funkcija sin(), cos() i korisnički definirane funkcije Kvadrat(). Zatim se od korisnika traži da unese broj i da odabere funkciju. Na kraju se ispisuje rezultat primjene funkcije na uneseni broj.

/* Datoteka: niz-pfun.c * korištenje niza pokazivača na funkciju */ #include <stdio.h> #include <math.h> double Kvadrat (double x) {return x*x;} int main() { double val=1; int izbor; double (*pF[3])(double)= {Kvadrat, sin, cos}; printf("Upisi broj:"); scanf("%lf", &val); fflush(stdin); printf( "\n(1)Kvadrat \n(2)Sinus \n(3)Kosinus \n"); printf( "\nOdaberi 1, 2 li 3\n"); scanf("%d" ,&izbor); if (izbor >=1 && izbor <=3) printf( "\nRezultat je %lf\n", (*pF[izbor-1])(val)); return 0; }

10.8 Kompleksnost deklaracija Očito je da se u C jeziku koriste vrlo kompleksne deklaracije. One na prvi pogled ne otkrivaju o kakovim se tipovima radi. Sljedeća tablica pokazuje deklaracije koje se često koriste.

U deklaraciji: x je ime koje predstavlja ... T x; objekt tipa T T x[]; (otvoreni) niz objekata tipa T T x[n]; niz od n objekata tipa T T *x; pokazivač na objekt tipa T T **x; pokazivač na pokazivač tipa T T *x[]; niz pokazivača na objekt T T *(x[]); niz pokazivača na objekt T

Page 140: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

140

T (*x)[]; pokazivač na niz objekata tipa T T x(); funkcija koja vraća objekt tipa T T *x() funkcija koja vraća pokazivač na objekt tipa T T (*x()); funkcija koja vraća pokazivač na objekt tipa T T (*x)(); pokazivač na funkciju koja vraća objekt tipa T T (*x[n])(); niz od n pokazivača na funkciju koja vraća objekt tipa T

Dalje će biti pokazano:

1. Kako sistematski pročitati ove deklaracije. 2. Kako se uvođenjem sinonima tipova (pomoću typedef) može znatno smanjiti

kompleksnost deklaracija.

Deklaracija nekog identifikatora se čita ovim redom:

Identifikator je (... desna strana deklaracije) (.. lijeva strana deklaracije) S desne strane identifikatora mogu biti uglate ili oble zagrade. Ako su uglate zagrade čitamo : identifikator je niz , Ako su oble zagrade čitamo identifikator je funkcija. Ukoliko ima više operatora s desne strane nastavlja se čitanje po istom pravilu.

Zatim se analizira zapis s lijeve strane identifikatora ( tu može biti operator indirekcije i oznaka tipa). Ako postoji operator indirekcije, čitamo: identifikator je (... desna strana) pokazivač na tip. Ako postoji dvostruki operator indirekcije, čitamo: identifikator je (... desna strana) pokazivač na pokazivač tip

Ukoliko je dio deklaracije napisan u zagradama onda se najprije čita značaj zapisa u zagradama.

Primjerice, u deklaraciji

T (*x[n])(double);

najprije se čita dio deklaracije (*x[n]) koji znači da je x niz pokazivača. Pošto je s desne strane ovog izraza (double) znači da je x niz pokazivača na funkciju koja prima argument tipa double i koja vraća tip T. Znatno jednostavniji i razumljiviji način zapisa kompleksnih deklaracija postiže se korištenjem sinonima za kompleksne tipove. Sinonimi tipova se definiraju pomoću typedef. Primjerice, typedef deklaracijom

typedef double t_Fdd(double); /* tip funkcije ... */

uvodi se oznaka tipa t_fdd koja predstavlja funkciju koja vraća double i prima argument tipa double.

Dalje se može definirati tip t_pFdd koji je pokazivač na funkciju koja vraća double i prima argument tipa double, s deklaracijom:

typedef t_Fdd *t_pFdd; /* tip pokazivača na funkciju ...*/

Page 141: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

141

što je ekvivalentno deklaraciji sinonima tipa:

typedef double (*t_pFdd)(double);

Pomoću tipa t_pFdd može se deklarirati niz pokazivača na funkciju iz prethodnog programa:

t_pFdd pF[3] = {Kvadrat, sin, cos};

Očito da je ovakovu deklaraciju znatno lakše razumjeti. Primjer: Primjeni li se navedene typedef deklaracije u programu niz-pfun.c, dobije se

/* Datoteka: niz-pfun1.c */ #include <stdio.h> #include <math.h> typedef double t_Fdd(double); /* tip funkcije koja ... */ typedef t_pFdd *t_pFdd; /* tip pokazivača na funkciju ...*/ double Kvadrat (double x) {return x*x;} void PrintVal(t_pFdd pFunc, double x) { printf( "\nZa x: %lf dobije se %lf\n", x, pFunc(x)); } int main() { double val=1; int izbor; t_pFdd pF[3] = {Kvadrat, sin, cos}; printf("Upisi broj:"); scanf("%lf", &val); fflush(stdin); printf( "\n(1)Kvadrat \n(2)Sinus \n(3)Kosinus \n"); printf( "\nOdaberi 1, 2 li 3\n"); scanf("%d" ,&izbor); if (izbor >=1 && izbor <=3) printf( "\nRezultat je %lf\n", (*pF[izbor-1])(val)); return 0; }

10.9 Polimorfne funkcije Funkcije koje se mogu prilagoditi različitim tipovima argumenata nazivaju se polimorfne

funkcije. Polimorfne funkcije se u C jeziku realiziraju pomoću parametara koji imaju tip void pokazivača i pokazivača na funkcije. Dvije takove funkcije implementirane su u standardnoj biblioteci C jezika. To su qsort() i bsearch() funkcija, čija je deklaracija dana u <stdlib.h>.

void qsort(void *a, /* pokazivač niza */ size_t n, /* broj elemenata */ size_t elsize, /* veličina elementa u bajtima */

Page 142: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

142

int (*pCmpF)(void *, void *)) qsort() funkcija služi za sortiranje elemenata niza a, koji sadrži n elemenata veličine elsize bajta. Elementi se sortiraju od manje prema većoj vrijednosti, odnosno prema kriteriju usporedbe koji određuje funkcija (*pCmpF)(). Usporedna funkcija mora biti deklarirana u obliku

int ime_funkcije(const void *p1, const void *p2);

Argumenti ove funkcije su pokazivači na dva elementa niza. Funkcija mora vratiti vrijednost nula ako su ta dva elementa jednaka, pozitivnu vrijednost ako je prvi element veći od drugoga, a negativnu vrijednost ako je prvi element manju od drugoga.

Primjerice, za sortiranje niza cijelih brojeva usporedna funkcija ima oblik:

int CmpInt(const void *p1, const void *p2) { int i1 = *((int *)p1); int i2 = *((int *)p2); if( i1 == i2) return 0; else if( i1 > i2) return 1; else return -1; /* i2 > i1 */ }

Najprije se s adresa p1 i p2 dobavlja cjelobrojne vrijednosti i1 i i2. Dobava vrijednosti se vrši tako da se najprije izvrši pretvorba void pokazivača u int*, a zatim se primijeni indirekcija pokazivača. Nakon toga se vrši usporedba vrijednosti i vraća dogovorena vrijednost.

Testiranje primjene funkcije qsort() je u programu sorti.c. U programu se vrši sortiranje niza cijelih brojeva.

/* Datoteka: sorti.c */ /* koristenje polimorfne qsort funkcije */ #include <stdio.h> #include <stdlib.h> int CmpInt(const void *p1, const void *p2) { ...... prethodna definicija ... } int main() { int i, A[] = {3,1,13,2,17}; int numel=sizeof(A)/sizeof(A[0]); int elsize = sizeof(A[0]); for(i=0; i <numel; i++) printf(" %d", A[i]); printf("\n Nakon sortiranja\n"); qsort(A, numel, elsize, CmpInt);

Page 143: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

143

for(i=0; i <numel; i++) printf(" %d", A[i]); printf("\n"); return 0; }

Nakon izvršenja dobije se ispis:

3 1 13 2 17 Nakon sortiranja 1 2 3 13 17

Zadatak: Napišite program sortf.c u kojem se vrši sortiranje niza realnih brojeva. Koristite qsort() funkciju. Definirajte prikladnu usporednu funkciju int FloatCmp(..).

Funkcija za polimorfno traženje elementa niza

Slijedi opis polimorfne funkcije search() kojom se određuje da li u nekom nizu postoji element zadane vrijednosti. Funkcija vraća pokazivač na traženi element, ako postoji , ili NULL ako u tom nizu ne postoji zadana vrijednost. Koristi se deklaracija funkcije slična funkciji qsort(), jedino se još dodaje argument tipa void pokazivača na varijablu koja sadrži vrijednost koju se traži u nizu A.

void * search(const void *x, /* pokazivač na zadanu vrijednost */ const void *A, /* pokazivač niza A*/ size_t n, /* broj elementa niza */ size_t elsize, /* veličina elementa u bajtima */ int (*pCmpF)( const void *, const void *)) /*funkcija */ { int indx; char *adr; for(indx = 0; indx < n; indx++) { adr= (char*)A +indx*elsize; /* adresa elementa niza */ if( (*pCmpF)((void *)adr, x) == 0) break; } if(indx == n) /* tada ni jedan element nema vrijednost x*/ return NULL; else return (void *) adr; /*vrati adresu elementa */ }

Testiranje primjene funkcije search() vrši se programom searchi.c:

/* Datoteka: searchi.c*/ #include <stdio.h> #include <stdlib.h> void *search(const void *x, const void *A, size_t n, size_t elsize, int (*pCmpF)( const void *, const void *)) {

Page 144: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

144

....... prema prethodnoj definiciji } int CmpInt(const void *p1, const void *p2) { ....... prema prethodnoj definiciji } int main() { int i, indx, x = 2; /* x- tražena vrijednost */ int A[] = {3,1,13,2,17, 7, 0, 11}; /* u nizu A*/ int numel=sizeof(A)/sizeof(A[0]); int elsize = sizeof(A[0]); int *pEl; for(i=0; i <numel; i++) printf(" %d", A[i]); pEl=(int *) search(&x, A, numel, elsize, CmpInt); printf("\n Element vrijednosti %d, na adresi %Fp\n", *pEl, pEl); printf("\n"); return 0; }

Nakon izvršenja dobije se ispis:

3 1 13 2 17 7 0 11 Element vrijednosti 2, na adresi 0022FF4C

U standardnoj biblioteci je implementirana funkcija bsearch() koja ima istu deklaraciju kao funkcija search(). Razlika ove dvije funkcije je u tome što je bsearch() funkcija specijalizirana i optimirana za slučaj da se traženje elementa niza provodi na prethodno sortiranom nizu. Kasnije ćemo pokazati kako su realizirane funkcije bsearch() i qsort().

10.10 Zaključak Pokazivači zauzimaju središnje mjesto u oblikovanju C programa. Pokazivač je varijabla

koja sadrži adresu. Ako je to adresa varijable, kaže se da pokazivač "pokazuje" na tu varijablu. U radu s pokazivačima koriste se dva specifična operatora: adresni operator (&) i operator

indirekcije(*). Adresni operator napisan ispred imena varijable vraća u izraz adresu varijable, a operator indirekcije *, postavljen ispred imena pokazivača, referira sadržaj varijable na koju pokazivač pokazuje.

Pokazivači i nizovi su u specijalnom odnosu. Ime niza, napisano bez uglatih zagrada predstavlja pokazivačku konstanu koja pokazuje na prvi element niza.

Indeksna notacija ima ekvivalentni oblik pokazivačke notacije. Uglate zagrade imaju karakter indeksnog operatora, jer kada se koriste iza imena pokazivača, uzrokuju da se tim pokazivačem može operirati kao s nizom.

Nizovi se prenose u funkcije na način da se u funkciju prenosi pokazivač na prvi element niza, odnosno adresa prvog elementa niza. Pošto funkcija zna adresu niza, u njoj se mogu

Page 145: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

145

koristiti naredbe koje mijenjaju sadržaj elemenata niza bilo u indeksnoj ili u pokazivačkoj notaciji.

Deklaracija niza kao parametra funkcije može se izvršiti u indeksnoj ili pokazivačkoj notaciji. Preporučuje se upotreba pokazivačke notacije jer sa tada poziv funkcije može vršiti sa statičkim nizovima i pokazivačima koji pokazuju na neki niz.

Funkcija ne raspolaže s podatkom o broju elemenata niza. Zadatak je programera da tu vrijednost, ukoliko je potrebna, predvidi kao parametar funkcije.

Korištenjem void pokazivača i pokazivača na funkcije mogu se realizirati polimorfne funkcije.

Ime funkcije je konstantni pokazivač na funkciju.

Page 146: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

146

11 Nizovi znakova - string

Naglasci: • ASCIIZ stringovi • standardne funkcije za rad sa stringovima • ulazno izlazne operacije sa stringovima • konverzije stringa • nizovi stringova • argumenti komandne linije operativnog sustava

Bit će pokazano kako se formiraju i obrađuju nizovi znakova. Od posebnog interesa su nizovi znakova koji imaju karakteristike stringa.

11.1 Definicija stringa String je naziv za memorijski objekt koji sadrži niz znakova, a posljednji znak u nizu mora

biti nulti znak ('\0'). Deklarira se kao niz znakova (pr. char str[10]) , ili kao pokazivač na znak (char *str), ali pod uvjetom da se pri inicijalizaciji niza i kasnije u radu s nizom uvijek vodi računa o tome da posljednji element niza mora biti jednak nuli. Zbog ove se karakteristike stringovi u C jeziku nazivaju ASCIIZ stringovi. Sastoje od niza ASCII znakova i nule (eng. Z - zero).

Duljina stringa je cjelobrojna vrijednost koja je jednaka broju znakova u stringu (bez nultog znaka). Indeks "nultog" znaka jednak je broju znakova u stringu, odnosno duljini stringa. Primjerice, string koji sadrži tekst: Hello, World!, u memoriji zauzima 14 bajta. Njegova duljina je 13, jer je indeks nultog znaka jednak 13.

0 1 2 3 4 5 6 7 8 9 10 11 12 13

H e l l o , W o r l d ! \0

Pogledajmo primjer u kojem se string tretira kao niz znakova.

/* Prvi C program – drugi put. */ #include <stdio.h> #include <string.h> int main() { char hello[14] = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\0' }; printf("%s\n", hello); printf("Duljina stringa je %d.\n", strlen(hello)); return 0; }

Page 147: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

147

Nakon izvršenja programa, dobije se poruka:

Hello, World! Duljina stringa je 13.

Ovaj program vrši istu funkciju kao i prvi C-program iz ove knjige, tiska poruku: Hello, World!. U programu je prvo definirana i inicijalizirana varijabla hello. Ona je tipa znakovnog niza od 14 elemenata. Inicijalizacijom se u prvih 13 elemenata upisuju znakovi (Hello World!), a posljednji element se inicira na nultu vrijednost. Ispis ove varijable se vrši pomoću printf() funkcije sa specifikatorom formata ispisa %s.

Duljina stringa je određena korištenjem standardne funkcije

size_t strlen(char *s);

Parametar funkcije je pokazivač na char, odnosno, pri pozivu funkcije argument je adresa početnog elementa stringa. Funkcija vraća vrijednost duljine stringa (size_t je sinonim za unsigned). Funkcija strlen() se može implementirati na sljedeći način:

unsigned strlen(const char *s) { unsigned i=0; while (s[i] != '\0') /* prekini petlju za s[i]==0, inače */ i++; /* inkrementiraj brojač znakova */ return i; /* i sadrži duljinu stringa */ }

ili pomoću pokazivačke aritmetike:

unsigned strlen(const char *s) { unsigned i = 0; while (*s++ != '\0') /* prekini petlju za *s==0, inače */ i++; /* inkrementiraj brojač i pokazivač*/ return i; /* i sadrži duljinu stringa */ }

String se može inicijalizirati i pomoću literalne konstante:

char hello[] = "Hello, World!"; /* kompilator rezervira mjesto u memoriji */

Elementi stringa se mogu mijenjati naredbom pridjele vrijednosti, primjerice naredbe:

hello[0] = 'h'; hello[6] = 'w';

mijenjaju sadržaj stringa u "hello world";

Ako se procjenjuje da će trebati više mjesta za string, nego se to navodi inicijalnim literalnim stringom, tada treba eksplicitno navesti dimenziju stringa. Primjerice, deklaracijom

char hello[50] = "Hello, World!";

Page 148: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

148

kompilator rezervira 50 mjesta u memoriji te inicira prvih 14 elemenata na "Hello, World!", zaključno s nultim znakom.

String je i svaki pokazivač koji se inicijalizira na adresu memorijskog objekta koji ima karakteristike stringa. Stoga, i sljedeći iskaz predstavlja deklaraciju stringa:

char *digits ="0123456789ABCDEF"; /* kompilator inicijalizira pokazivač */ /* na adresu od "0123456789ABCDEF"; */

Ovakva inicijalizacija je moguća jer kompilator interno literalni string tretira kao referencu, pa se njegova adresa pridjeljuje pokazivaču digits. Dozvoljeno je čak literalnu string konstantu koristiti kao referencu niza, primjerice

printf("%c", "0123456789ABCDEF"[n]);

ispisuje n-ti znak stringa "0123456789ABCDEF".

11.2 Standardne funkcije za rad sa stringovima U standardnoj biblioteci postoji niz funkcija za manipuliranje sa stringovima. One su

deklarirane u datoteci "string.h". Funkcija im je:

size_t strlen(const char *s) Vraća duljinu stringa s.

char *strcpy(char *s, const char *t) Kopira string t u string s, uključujući '\0'; vraća s.

char *strncpy(char *s, const char *t, size_t n) Kopira najviše n znakova stringa t u s; vraća s. Dopunja string s sa '\0' znakovima ako t ima manje od n znakova.

char *strcat(char *s, const char *t) Dodaje string t na kraj stringa s; vraća s.

char *strncat(char *s, const char *t, size_t n) Dodaje najviše n znakova stringa t na string s, i znak '\0'; vraća s.

int strcmp(const char *s, const char *t)

Uspoređuje string s sa stringom t, vraća <0 ako je s<t, 0 ako je s==t, ili >0 ako je s>t. Usporedba je leksikografska, prema ASCII "abecedi".

int strncmp(const char *s, const char *t, size_t n)

Uspoređuje najviše n znakova stringa s sa stringom t; vraća <0 ako je s<t, 0 ako je s==t, ili >0 ako je s>t.

char *strchr(const char *s, int c)

Vraća pokazivač na prvu pojavnost znaka c u stringu s, ili NULL znak ako c nije sadržan u stringu s.

Page 149: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

149

char *strrchr(const char *s, int c)

Vraća pokazivač na zadnju pojavnost znaka c u stringu s, ili NULL znak ako c nije sadržan u stringu s.

char *strstr(const char *s, const char *t)

Vraća pokazivač na prvu pojavu stringa t u stringu s, ili NULL ako string s ne sadrži string t. size_t strspn(const char *s, const char *t)

Vraća duljinu prefiksa stringa s koji sadrži znakove koji čine string t. size_t strcspn(const char *s, const char *t)

Vraća duljinu prefiksa stringa s koji sadrži znakove koji nisu prisutni u stringu t.

char *strpbrk(const char *s, const char *t) Vraća pokazivač na prvu pojavu bilo kojeg znaka iz string t u stringu s, ili NULL ako nije prisutan ni jedan znak iz string t u stringu s.

char *strerror(int n)

Vraća pokazivač na string kojeg interno generira kompilator za dojavu greške u nekim sistemskim operacijama. Argument je obično globalna varijabla errno, čiju vrijednost također postavlja kompilator pri sistemskim operacijama.

char *strtok(char *s, const char *sep) vrši razlaganje stringa s na niz leksema koji su razdvojeni znakovima-separatorima. Skup znakova-separatora se zadaje u stringu sep. Funkcija vraća pokazivač na leksem ili NULL ako nema leksema. Korištenje funkcije strtok() je specifično jer u stringu može biti više leksema, a ona vraća pokazivač na jedan leksem. Da bi se dobili sljedeći leksemi treba ponovo zvati istu funkciju, ali s prvim argumentom jednakim NULL. Primjerice, za string

char * s = "Prvi drugi,treci";

ako odaberemo znakove separatore: razmak, tab i zarez, tada sljedeći iskazi daju ispis tri leksema (Prvi drugi i treci):

char *leksem = strtoken(s, " ,\t"); /* dobavi prvi leksem */ while( leksem != NULL) { /* ukoliko postoji */ printf("", leksem); /* ispiši ga i */ lexem = strtok(NULL, " ,\t"); /* dobavi sljedeći leksem */ } /* pa ponovi postupak */

Sljedeća dva primjera pokazuju kako se mogu napisati standardne funkcije za kopiranje stringa (strcpy) i leksičku usporedbu dva stringa (strcmp).

Funkcija strcpy() kopira znakove stringa src u string dest, a vraća adresu od dest. 1. Verzija s indeksnim operatorom:

Page 150: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

150

char *strcpy( char *dst, const char *src) { int i; for (i = 0; src[i] != '\0'; i++) dst[i] = src[i]; dst[i] = '\0'; return dst; }

2. Verzija s pokazivačkom aritmetikom

char *strcpy(char *dest, const char *src) { char *d = dest; while(*d = *src) /* true dok src ne bude '\0'*/ { d++; src++; } return dest; }

Funkcija strcmp()služi usporedbi dva stringa s1 i s2. Deklarirana je s:

int strcmp(const char *s1, const char *s2)

Funkcija vraća vrijednost 0 ako je sadržaj oba stringa isti, negativnu vrijednost ako je s1 leksički manji od s2, ili pozitivnu vrijednost ako je s1 leksički veći od s2. Leksička usporedba se izvršava znak po znak, prema kodnoj vrijednosti ASCII standarda.

/*1. verzija*/ int strcmp( char *s1, char *s2) { int i = 0; while(s1[i] == s2[i] && s1[i] != '\0') i++; if (s1[i] < s2[i]) return -1; else if (s1[i] > s2[i]) return +1; else return 0; }

Najprije se u glavi petlje uspoređuje da li su znakovi s istim indeksom isti i da li je dostignut kraj niza (znak '\0'). Kad petlja završi, ako su oba znaka jednaka, to znači da su i svi prethodni znakovi jednaki. U tom slučaju funkcija vraća vrijednost 0. U suprotnom funkcija vraća 1 ili –1 ovisno o numeričkom kodu znakova koji se razlikuju.

/*2. verzija s inkrementiranjem pokazivača*/ int strcmp(char *s1, char *s2) { while(*s1 == *s2) { if(*s1 == '\0') return 0; s1++;

Page 151: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

151

s2++; } return *s1 - *s2; }

Verzije u kojima se koristi inkrementiranje pokazivačka često su kod starije generacije procesora rezultirale bržim izvršenjem programa. Kod novije generacije procesora to nije slučaj, pa se preporučuje korištenje indeksne notacije.

11.3 Ulazno-izlazne operacije sa stringovima Najjednostavniji način dobave i ispisa stringa je da se koriste printf() i scanf()

funkcije s oznakom formata %s. Primjerice, s

char line[100]; scanf("%s",str); printf("%s", str);

najprije se vrši unos stringa (korisnik otkuca niz znakova i <enter>). Zatim se vrši ispis tog stringa pomoću printf() funkcije. Funkcija scanf() nije pogodna za unos stringova u kojima ima bijelih znakova, jer i oni znače kraj unosa, stoga se za unos i ispis stringa češće koriste funkcije:

char *gets(char *str); int puts(char *str);

Funkcija gets() dobavlja liniju teksta sa standardnog ulaza (unos se prekida kada je na ulazu '\n') i sprema je u string str, kojeg zaključuje s nul znakom. Podrazumijeva se da je prethodno rezervirano dovoljno memorije za smještaj stringa str. Funkcija vraća pokazivač na taj string ili NULL ako nastupi greška ili EOF. Funkcija puts() ispisuje string str na standardni izlaz. Vraća pozitivnu vrijednost ili -1 (EOF) ako nastupi greška. Jedina razlika između funkcije printf("%s", str) i puts(str) je u tome da funkcija puts(str) uvijek na kraju ispisa dodaje i znak nove linije. Primjer: U programu str-io.c pokazana je upotreba raznih funkcija za rad sa stringovima. Program dobavlja liniju po liniju teksta sve dok se ne otkuca: "kraj". Nakon toga se ispisuje ukupno uneseni tekst. /* str-io.c */ #include <stdio.h> #include <ctype.h> #include <string.h> #define MAXCH 2000 int main( void) { char str[100]; /* string u kojeg se unosi jedna linija teksta */ char text[MAXCH]; /* string u kojeg se zapisuje ukupno uneseni tekst*/ /* iniciraj string text sa sljedecim tekstom */

Page 152: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

152

strcpy(text, "Unijeli ste tekst;\n"); puts("Otkucaj nekoliko linija teksta."); /* informiraj korisnika */ puts("Za kraj unosa otkuca: kraj"); /* kako se vrši unos */ while (gets(str) != NULL ) /* dobavljaj string */ { if(strcmp(str, "kraj") == 0) /* prekini ako je otkucan kraj */ break; /* prekini ako duljina texta premaši veličinu MAXCH */ if(strlen(str)+strlen(text) >= MAXCH) break; strcat(text, str); /* dopuni ukupan tekst sa str */ strcat(text, "\n"); /* i oznaci novi red */ } puts(text); /* ispisi tekst koji je unesen */ return 0; } Početno je string text inicijaliziran na način da je u njega kopiran string "Unijeli ste tekst;\n" pomoću funkcije strcpy(). Kasnije se taj string dopunjuje sa sadržajem stringa str koje unosi korisnik programa u petlji:

while (gets(str) != NULL )

U tijelu petlje prvo se ispituje sadržaj unesenog stringa. Ako je unesen string "kraj", petlja se prekida. To se ispituje iskazom:

if(strcmp(str, "kraj") == 0) /* prekini ako je otkucan kraj*/ break;

Dopuna stringa text vrši se iskazima:

strcat(text, str); /* dopuni ukupan tekst sa str*/ strcat(text, "\n"); /* i oznaci novi red */

Za string tekst je rezervirano MAXCH (2000) bajta, što je vjerojatno dovoljno za prihvat teksta. Ako se pokuša unijeti više znakova, unos se prekida iskazom:

if(strlen(str)+strlen(text) >= MAXCH) break;

Na kraju se ispisuje string text, koji sadrži ukupno otkucani tekst. Zadatak: Modificirajte prethodni program na način da se program prekine i u slučaju ako se bilo gdje unutar unesenog stringa nalazi riječ "kraj". U tu svrhu koristite funkciju strstr(). Zadatak: Modificirajte prethodni program na način da se na kraju programa ispiše koliko ukupno ima riječi u stringu tekst s više od tri slova. U tu svrhu koristite funkciju strtok() i strlen().

11.4 Korisnički definirane ulazne operacije sa stringovima Veliki nedostatak funkcija scanf() i gets() je u tome da se ne može ograničiti broj

unesenih znakova, pa treba koristiti stringove za koje je rezerviran veliki broj bajta. Zbog ovog ograničenja mnogi programeri radije sami definiraju funkciju za unos stringa u obliku:

Page 153: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

153

int getstring(char *str, int maxchar);

Parametri ove funkcije su string str i cijeli broj maxchar, kojim se zadaje maksimalno dozvoljeni broj znakova koji će biti spremljen u string. Funkcija vraća broj znakova ili 0 ako je samo pritisnut znak nove linije ili EOF ako je na početku linije otipkano Ctrl-Z. Funkcija getstring() se može implementirati na sljedeći način:

#include <stdio.h> int getstring(char *str, int maxchar) { int ch, nch = 0; /* početno je broj znakova = 0 */ --maxchar; /* osiguraj mjesto za '\0' */ while((ch = getchar()) != EOF) /* dobavljaj znak */ { if(ch == '\n') /* prekini unos na kraju linije */ break; /* prihvati unos samo ako je broj znakova < maxchar */ if(nch < maxchar) str[nchar++] = ch; } if(ch == EOF && nch == 0) return EOF; str[nch] = '\0'; /* zaključi string s nul znakom */ return nch; }

Testiranje ove funkcije provodi se programom getstr.c. U njemu korisnik unosi više linija teksta, a unos završava kada se otkuca Ctrl-Z.

#include <stdio.h> int getstring(char *, int); main() { char str[256]; while(getstring(str, 256) != EOF) printf("Otipkali ste \"%s\"\n", str); return 0; }

11.5 Pretvorba stringa u numeričku vrijednost Funkciju getstring()se može iskoristiti i za unos numeričkih vrijednosti, jer u

standardnoj biblioteci postoje funkcije

Page 154: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

154

int atoi(char *str); double atof(char *str);

koje vrše pretvorbu znakovnog zapisa u numeričku vrijednost. Ove funkcije su deklarirane u <stdlib.h>. Funkcija atoi() pretvara string u vrijednost tipa int a funkcija atof() pretvara string u vrijednost tipa double. Podrazumijeva se da string sadrži niz znakova koji se koriste za zapis numeričkih literala. U slučaju greške ove funkcije vraćaju nulu. Greška se uvijek javlja ako prvi znak nije znamenka ili znakovi + i -. Primjer: U programu str-cnv.c prikazano je kako se može vršiti unos numeričkih vrijednosti pomoću funkcija getstring(), atoi() i atof():

/* str-cnv.c */ #include <stdio.h> #include <stdlib.h> int getstring(char *, int); #define NCHARS 30 int main() { int i; double d; char str [NCHARS]; puts("Otipkajte cijeli broj"); getstring(str, NCHARS); i = atoi(str); printf("Otipkali ste %d\n", i); puts("Otipkajte realni broj"); getstring(str, NCHARS); d = atof(str); printf("Otipkali ste %lg\n", d); return 0; }

Drugi način da se iz stringa dobije numerička vrijednost je da se koristi funkcija sscanf().

int sscanf (char *str, char *format, ... );

Ova funkcija ima isto djelovanje kao funkcija scanf(). Razlika je u tome da sscanf() prima znakove iz stringa str, dok funkcija scanf() prima znakove sa standardnog ulaza. Primjer: pretvorba stringa, koji sadrži znakovni zapis cijelog broja, u numeričku vrijednost cjelobrojne varijable vrši se iskazom

sscanf( str, "%d", &i);

Ponekad će biti potrebno izvršiti pretvorbu numeričke vrijednosti u string. U tom slučaju se može koristiti standardna funkcija:

int sprintf(char *str, char *format, ... );

Page 155: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

155

koja ima isto djelovanje kao printf() funkcija, ali se ispis vrši u string, a ne na standardni izlaz.

11.6 Nizovi stringova Nizovi stringova se jednostavno deklariraju i inicijaliziraju kao nizovi pokazivača na

char. Primjerice, za rad s igračkim kartama može se koristiti dva niza stringova: jedan za boju, a drugi za lik karata. Deklariraju se na sljedeći način:

char *boja[] = {"Srce", "Tref", "Karo", "Pik "}; char *lik[] = {"As", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Dama", "Kralj" };

Pojedinom stringu se pristupa pomoću indeksa, primjerice boja[2] označava string "Karo". Pri deklaraciji se koristi pravilo da operator indirekcije ima niži prioritet od uglatih zagrada, tj. da je deklaracija char *boja[] ekvivalentna deklaraciji: char *( boja []), pa se iščitava: boja je niz pokazivača na char. Isto vrijedi i za upotrebu varijable lik. Primjerice, *lik[i] se odnosi na prvi znak i-tog stringa (tj. lik[i][0]).

Primjer: Dijeljenje karata

Program karte.c služit će da se po slučajnom uzorku izmiješaju igrače karte. Za miješanje karate koristi se proizvoljna metoda: Postoje 52 karte. Njih se registrira u nizu cijelih brojeva int karte[52]. Svaka karta[i] sadrži vrijednost iz intervala 0..51 koja označava kombinaciju boje i lika karte, prema pravilu :

oznaka boje: boja[karte[i] / 13]; /* string boja[0 do 3] */ oznaka lika: lik [karte[i] % 13]; /* string lik[0 do 12] */

jer ima 13 likova i 4 boje. To znači da ako se početni raspored inicijalizira s

for (i = 0; i < BROJKARATA; i++) karte[i] = i;

karte će biti složene po bojama, od asa prema kralju (prva će biti as-srce a posljednja kralj-pik). Za miješanje karata koristit će se standardna funkcija

int rand();

koja je deklarirana u <stdlib.h>. Ova funkcija pri svakom pozivu vraća cijeli broj iz intervala 0 do RAND_MAX (obično je to vrijednost 32767) koji se generira po slučajnom uzorku. Kasnije će biti pokazano kako je implementirana ova funkcija.

Miješanje karata se provodi na način da se položaj svake karte zamijeni s položajem koji se dobije po slučajnom zakonu:

for (i = 0; i < BROJKARATA; i++) { int k = rand() % BROJKARATA; swap (&karte[i], &karte[k]); }

/* Datoteka: karte.c - dijeljenje karata*/

Page 156: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

156

#include <stdio.h> #include <stdlib.h> char *boja[] = {"Srce", "Tref", "Karo", "Pik "}; char *lik[] = {"As", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Dama", "Kralj" }; #define BROJKARATA 52 void swap(int *x, int *y) { int t = *x; *x = *y; *y = t; } int main( void) { int i, karte[BROJKARATA]; /* ukupno 52 karte */ for (i = 0; i < BROJKARATA; i++) karte[i] = i; for (i = 0; i < BROJKARATA; i++) { int k = rand() % BROJKARATA; swap (&karte[i], &karte[k]); } for (i = 0; i < BROJKARATA; i++) printf("%s - %s\n", boja[karte[i]/13], lik[karte[i]%13]); return 0; }

Dobije se ispis:

Pik - 3 Srce - 9 Tref - 10 Srce - 10 Tref - 5 Tref - 8 Tref - Kralj . . . . . Pik - 7 Srce - 3 Tref - Dama Tref - Jack Tref - 6 Karo - 8

Page 157: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

157

11.7 Generator slučajnih brojeva Znanstvenici su pokazali da se može realizirati niz slučajnih cijelih brojeva pomoću tzv.

linearne kongruencijske sekvence;

xi = (a * xi-1 + b) % c u kojoj se vrijednost i-tog broja dobije iz prethodno izračunate vrijednosti (xi-1) i tri konsatante a,b,c. Vrijednost ovih konstanti se određuje iz uvjeta da brojevi iz ove sekvence imaju jednaku vjerojatnost pojavljivanja, odnosno da imaju uniformnu razdiobu gustoće vjerojatnosti. U ANSI/ISO standardnoj biblioteci implementiran je generator s konstantama a=1103515245, b=12345 i c= 232, pomoću jedne globalne varijable seed i dvije funkcije rand() i srand().

static unsigned int seed = 1; int rand(void) { /* vraća pseudo-slučajni broj iz intervala 0..32767 */ seed = seed * 1103515245 + 12345; return seed >> 16; } void srand(unsigned int start) { /* postavlja početnu vrijednost za rand() */ seed = start; }

Slučajni broj generira funkcija rand() iskazom:

seed = seed * 1103515245 + 12345;

Uočite da se ne vrši operacija mod 232, jer maksimalna veličina unsigned long je 232-1, pa nije potrebno tražiti ostatak dijeljenja s tim brojem. Funkcija rand() vraća vrijednost koja je sadržana u donjih 16 bita (seed>>16), jer se pokazalo da to daje najbolji statistički rezultat (time je maksimalna veličina broja svedena na 32767). Postoje i kvalitetniji algoritmi za generator slučajnih brojeva, ali ovaj način je odabran kao dobar kompromis između kvalitete i brzine generiranja slučajnih brojeva.

Početno, pri pokretanju programa, globalna varijabla seed ima vrijednost 1, a bilo bi poželjno da to bude uvijek druga vrijednost. U tu svrhu se koristi funkcija srand(), pomoću koje se početna vrijednost varijable seed postavlja na vrijednost argumenta ove funkcije. Postavlja se pitanje kako osigurati da seed pri pokretanju programa uvijek ima drugačiju vrijednost. Programeri obično koriste činjenicu da će startanje programa biti u nekom jedinstvenom vremenu. Za očitanje vremena na raspolaganju je funkcija time() koja je deklarirana u <time.h>:

time_t time(time_t *tp)

Tip time_t je typedef za cjelobrojnu vrijednost . U većini implementacija funkcija time() vraća vrijednost koja je jednaka vremenu u sekundama koje je proteklo od 01.10.1970. godine. Ista se vrijednost postavlja i u dereferencirani pokazivač tp, ukoliko on nije NULL. Sada se inicijalizacija generatora slučajnih brojeva može provesti sljedećim iskazom:

Page 158: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

158

srand((unsigned int)time(NULL));

Zadatak: Uvrstite prethodni iskaz u program karte.c (također, dodajte direktivu #include<time.h>). Pokrenite program nekoliko puta i provjerite da li se pri svakom izvršenju programa dobije drugačija podjela karata.

11.8 Argumenti komandne linije operativnog sustava Funkcija main() također može imati argumente. Njoj argumente prosljeđuje operativni

sustav s komandne linije. Primjerice, pri pozivu kompilatora (c:>cl karte.c ) iz komandne linije se kao argument prosljeđuje string koji sadrži ime datoteke koja se kompilira. Opći oblik deklaracije funkcije main(), koja prima argumente s komandne linije, glasi:

int main( int argc, char *argv[])

Parametri su: argc sadrži broj argumenata. Prvi argument (indeks nula) je uvijek ime samog programa, dakle broj argumenata je uvijek veći ili jednak 1. argv je niz pokazivača na char, svaki pokazuje na početni znak stringova koji su u komandnoj liniji odvojeni razmakom.

Primjer: Programom cmdline.c ispituje se sadržaj komandne linije

/* Datoteka: cmdline.c */ #include <stdio.h> int main( int argc, char *argv[]) { int i; printf("Ime programa je: %s\n", argv[0]); if (argc > 1) for (i = 1; i < argc; i++) printf("%d. argument: %s\n", i, argv[i]); return 0; }

Ako se otkuca:

c:> cmdline Hello World

dobije su ispis:

Ime programa: C:\CMDLINE.EXE 1. argument: Hello 2. argument: World

jer je:

argc = 3 argv[0] = "C:\CMDLINE.EXE" argv[1] = "Hello" argv[2] = "World"

Primjer: Program cmdsum.c računa sumu dva realna broja koji se unose u komandnoj liniji iza imena izvršnog programa. Primjerice, ako se u komandnoj liniji otkuca:

Page 159: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

159

C:>cmdsum 6.5 4e-2

program treba dati ispis

Suma: 6.500000 + 0.040000 = 6.540000

/* Datoteka: cmdsum.c */ #include <stdio.h> #include <stdlib.h> int main( int argc, char *argv[]) { float x,y; if (argc > 2) /* unose se dva argumenta */ { x = atof(argv[1]); y = atof(argv[2]); printf ("Suma: %f + %f = %f\n", x, y, x+y); } else printf("Otkucaj: cmdsum broj1 broj2 \n"); return 0; }

Page 160: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

160

12 Dinamičko alociranje memorije

Naglasci: • slobodna memorija i alociranje memorije • malloc(), calloc(), realloc() i free() • dinamički nizovi • dinamičke matrice • brzi pristup memoriji

Kada operativni sustav učita program, koji je formiran C-kompilatorom, on tom programu dodijeli dio radne memorije koji čine tri cjeline: memorija izvršnog kôda programa, memorija za statičke podatke (globalne i statičke varijable te literalne konstante), te memorija nazvana stog (eng. stack) koja se koristi za privremeni smještaj automatskih varijabli (lokalne varijable i formalni argumenti funkcija). Ostatak memorije se naziva slobodna memorija ili "heap".

Operativni

sustav

Korisnički program:

strojni kod

Statički

podaci

Stog

automatske varijable

Slobodna memorija

"heap"

Slika 12.1 Raspodjela memorije između operativnog sustava i korisničkog programa

Nadzor nad korištenjem slobodne memorije vrši operativni sustav. U samom C-jeziku nije implementirana mogućnost direktnog raspolaganja slobodnom memorijom, ali se, uz pomoć pokazivača i funkcija biblioteke: malloc(), calloc(), realloc() i free(), može na indirektan način koristiti slobodna memorija. Postupak kojim se dobiva na upotrebu slobodna memorija naziva se dinamičko alociranje memorije. Pojam alociranje memorije asocira na činjenicu da se dio slobodne memorije dodjeljuje korisničkom programu, dakle apstraktno se mijenja njegova lokacija. Alociranje memorije se vrši tijekom izvršenja programa, pa se kaže da je taj proces dinamički. Postupak kojim se alocirana memorija vraća na raspolaganje operativnom sustavu naziva se oslobađanje ili dealociranje memorije.

U ovom poglavlju biti će opisano kako se vrši dinamičko alociranje memorije i kako ta programska tehnika omogućuje efikasno korištenje memorijskih resursa računala.

12.1 Funkcije za dinamičko alociranje memorije Najprije će biti opisane funkcije za dinamičko alociranje memorije. One su deklarirane u datoteci <stdlib.h>.

void *malloc(size_t n) void *calloc(size_t n, size_t elsize)

Funkcijom malloc() alocira se n bajta slobodne memorije. Ako je alociranje uspješno funkcija vraća pokazivač na tu memoriju, u suprotnom vraća NULL pokazivač. Primjerice, naredbom

double *dp = malloc(10 * sizeof(double));

Page 161: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

161

dobije se pokazivač dp, koji pokazuje na niz od 10 elemenata tipa double. U deklaraciji funkcije malloc() označeno je da ona vraća je void *. To omogućuje da se adresa, koju vraća funkcija malloc(), može pridijelili pokazivaču bilo kojeg tipa. Funkcija calloc(n, elsize) je ekvivalentna malloc(n * elsize), uz dodatni uvjet da calloc() inicijalizira sve bitove alocirane memorije na vrijednost nula. Za dealociranje memorije poziva se funkcija:

void free(void *p)

Funkcija free() prima kao argument pokazivač p. Uz pretpostavku da p pokazuje na memoriju koja je prethodno alocirana fukcijom malloc(), calloc() ili realloc(), ova funkcija dealocira (ili oslobađa) tu memoriju. Promjenu veličine već alocirane memorije vrši se pozivom funkcije:

void *realloc(void *ptr, size_t newsize)

Funkcija realloc() vrši promjenu veličine prethodno alocirane memorije, koja je pridijeljena pokazivaču ptr, na veličinu newsize. Funkcija realloc() vraća pokazivač na tu memoriju. Vrijednost toga pokazivača može biti ista kao i vrijednost od ptr, ako memorijski alokator može prilagoditi veličinu zahtijevanog području slobodne memorije veličini newsize. Ukoliko se to ne može ostvariti funkcija realloc() alocira novo područje memorije pa u njega kopira i zatim oslobađa dio memorije na koju pokazuje ptr. Ukoliko se ne može izvršiti alokacija memorije funkcija realloc() vraća NULL. Napomena: poziv realloc(p, 0) je ekvivalentan pozivu free(p), a poziv realloc(0, n) je ekivalentan pozivu malloc(n).

Dobra je programerska praksa da se uvijek pri pozivu funkcije za alociranje memorije provjeri da li ona vraća NULL. Ako se to dogodi u većini je slučajeva najbolje odmah prekinuti program. U tu svrhu može se koristiti standardna funkcija exit() kojom se prekida korisnički program, a operativnom se sustavu prosljeđuje argument ove funkcije. Primjerice,

p = malloc(n); if(p == NULL) { printf("out of memory\n"); exit(1); } /* … koristi pokazivač p */

Napomena: Prije uvođenja ANSI/ISO standarda prototip za malloc() je kod mnogih kompilatora bio deklariran s: char *malloc(size_t n). Kod ovakvih kompilatora potrebno je izvršiti eksplicitnu pretvorbu tipa vrijednosti koju vraća malloc(), primjerice

double *p; p = (double *)malloc(n);

Ako se ne izvrši eksplicitna pretvorba tipa kompilator dojavljuje grešku o nekompatibilnosti tipova. Primjer: Dinamičko alociranje niza.

Page 162: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

162

U programu allocarr.c alocira se niz od maxsize=5 cjelobrojnih elemenata. Korisnik zatim unosi niz brojeva. Ako broj unesenih brojeva premaši maxsize, tada se povećava alocirana memorija za dodatnih 5 elemenata. Unos se prekida kada korisnk otkuca slovo. Na kraju se ispisuje niz i dealocira memorija.

/*Datoteka: allocarr.c*/ #include <stdio.h> #include <stdlib.h> void izlaz(char *string) { printf("%s", string); exit(1); } int main( void ) { int *pa ; /* pokazivač na niz cijelih brojeva */ int maxsize = 5; /* početna maksimalna veličina niza */ int num = 0; /* početno je u nizu 0 elemenata */ int j,data; /* * Početno alociraj memoriju za maxsize = 5 cijelih brojeva. * Koristi funkciju malloc koji vraća pokazivač pa. * Izvrši provjeru NULL vrijednosti pokazivača */ pa = malloc(maxsize * sizeof( int )); if(pa == NULL ) izlaz("Nema slobodne memorije za malloc.\n") ; /* * Učitaj proizvoljan broj cijelih brojeva u niz pa[] * Ako broj premaši maxsize: * povećaj allocirani niz za 5 elemenata pomoću funkcije realloc * Kraj unosa je ako se umjesto broja pritisne neko slovo */ while(scanf("%d", &data) == 1) { pa[num++] = data; /* upisi podatak u memoriju */ if(num >= maxsize) /* ako je memorija popunjena */ { /* povećaj alociranu memoriju */ maxsize += 5; /* za dodatnih 5 elemenata */ pa = realloc( pa, maxsize * sizeof(int)); if(pa== NULL) izlaz("Nema slobodne memorije za realloc.\n") ; } } /* Ispiši podatke iz dinamički alociranog niza */ for(j = 0; j < num; j ++ ) printf("%d \n", pa[j]) ; /* Konačno vrati memoriju na heap koristeći free() */ free( pa ) ; return 0 ; }

Page 163: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

163

Primjer: Pokazana je implementacija funkcije char * NumToString(int n), koja pretvara cijeli broj n u string i vraća pokazivač na taj string.

char *NumToString( int n) { char buf[100], *ptr; sprintf( buf, "%d", n); ptr = malloc( strlen( buf) + 1); strcpy( ptr, buf); return ptr; }

Primjer primjene funkcije NumToString:

char *s; s = NumToString(56); printf("%s\n", s); free(s); /* !!! oslobodi memoriju kad više ne trebaš s */

Napomena: Ako se u nekoj funkciji alocira memorija i pridijeli pokazivaču koji je lokalna varijabla, nakon izlaza iz funkcije taj pokazivač više ne postoji (zašto?). Alocirana memorija će i dalje postojati ukoliko nije prije završetka funkcije eksplicitno dealocirana funkcijom free(). Može se dogoditi da ta memorija bude "izgubljena" ako je se ne dealocira, ili ako se iz funkcije ne vrati pokazivač na tu memoriju (kao u slučaju funkcije NumToString). Primjer: Funkciji char * strdup(char *s) namjena je da stvori kopiju nekog stringa s, i vrati pokazivač na novoformirani string.

char *strdup( char * s) { char *buf, *ptr; int n = strlen(s); /* alociraj memoriju n+1 bajta za kopiju stringa s*/ buf = malloc(n* sizeof(char)+1); /* kopiraj string u tu memoriju */ if(buf != NULL) strcpy( buf, s); /* vrati pokazivač na string */ return buf; }

Ova funkcija nije specificirana u standardnoj biblioteci C jezika, ali je implementirana u biblioteci Visual C i gcc kompilatora.

12.2 Kako se vrši alociranje memorije Da bi se bolje shvatio proces alociranja memorije, bit će ilustrirano stanje slobodnje

memorije u slučaju kada se vrši višestruko alociranje/dealociranje memorije. Sivom bojom označeno je područje slobodne memorije:

Page 164: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

164

Neka najprije treba alocirati memoriju za string "Ivo". To se vrši iskazom:

char *str1 = malloc(4);

Alocirana su 4 bajta, jer pored znakova 'I', 'v' i 'o', treba spremiti i znak '\0'. Ako se uspješno izvrši alokacija memorije, vrijedit će prikaz: ? ? ? ?

Upitnici označavaju da je sadržaj memorije, na koji str1 pokazuje, nedefiniran. Alocirana će memorija biti inicijalizirana tek kada se izvrši funkcija:

strcpy(str1, "Ivo");

Sada alocirana memorija ima definirani sadržaj: I v o -

(crtica označava nul-znak) Ako se nadalje izvrši alociranje memorije za string "Ivona",

str2 = malloc(6); strcpy(str2, "Ivona");

vrijedi prikaz: I v o - I v o n a -

Između dva alocirana bloka nacrtano je manje područje slobodne memorije kako bi se istaklo da memorijski alokator ne slaže alocirane blokove neposredno jedan do drugog. Zapravo, memorijski alokator u alocirani sadržaj ispred svakog bloka upisuje i informacije o tom bloku, primjerice veličinu samog bloka. Realnu situaciju je bolje prikazati na sljedeći način: 4 I v o - 6 I v o n a -

str1 str2

Kada korisnik oslobodi memoriju na koju pokazuje str1, naredbom

free(str1);

stanje slobodne memorije je takovo da postoji dio slobodne memorije ispred stringa "Ivona". 6 I v O n a -

Page 165: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

165

Kaže se da je slobodna memorija fragmentirana. Pri višestrukim pozivima malloc/free može doći do višestruke fragmentacije, čime se "gubi" jedan dio slobodne memorije. Zapamtite:

• O stanju slobodne memorije vodi računa posebni proces – memorijski alokator. • Nakon što se alocira memorija pomoću funkcije malloc(), treba je inicijalizirati. • Ako se želi inicijalizirati sadržaj memorije na vrijednost nula, koristi se funkcija

calloc(). • Ne smije se pretpostaviti da se sukcesivnim pozivima funkcije malloc() dobiva

kontinuirani memorijski blok.

12.3 Alociranje višedimenzionalnih nizova Korištenje pokazivača i funkcija za dinamičko alociranja memorije omogućuje stvaranje

dinamičkih struktura podataka koje mogu imati promjenljivu veličinu tijekom izvršenja programa. Nema posebnih pravila za stvaranje dinamičkih struktura podataka. U ovom i u narednim poglavljima bit će pokazani različiti primjeri koji su korisni u programiranju i koji se mogu koristiti kao obrazac za "dinamičko struktuiranje podataka" u C jeziku.

Najprije će biti opisano kako se dinamički alociraju višedimenzionalni nizovi i to nizovi stringova i matrice promjenljivih dimenzija.

Dinamički nizovi stringova Prije je pokazano da se u C jeziku pokazivači tipa char * mogu tretirati kao stringovi, uz uvjet da pokazuju na niz znakova koji završava s nultim znakom. To omogućuje da se niz stringova deklarira iskazom:

#define N 100 char *txt[N];

Ovaj niz stringova je statičan jer se njime može registrirati konačan broj stringova txt[i]. Uočite da je N konstanta.

Niz stringova se može realizirati i dinamičkim alociranjem niza koji će sadržavati N pokazivača tipa char *. To se postiže iskazima:

int N = 100; /* veličina niza je varijabla */ char **txt; /* txt pokazuje na niz pokazivača */ txt = calloc(N, sizeof(char*)); /* alociraj za N pokazivača */

U oba slučaja txt[i] predstavlja string. Razlika je pak od prethodne definicije niza stringova u tome što se sada veličina niza može mijenjati tijekom izvršenja programa. Početno su svi elementi niza jednaki NULL, što znači da niz nije inicijaliziran. Inicijalizacija niza se postiže tako da se pokazivačima txt[i] pridijeli adresa nekog stringa. Primjerice, nakon sljedećih operacija:

txt[0]= strdup("Hello") txt[1]= strdup("World!") txt[2]= strdup("je prvi C program")

prva tri elementa niza txt[i] predstavljaju stringove, a ostali elementi niza su i dalje neinicijalizirani. Stanje u memoriji se može ilustrirati slikom 12.2.

Page 166: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

166

Slika 12.2 Prikaz korištenja memorije za dinamički niz stringova

Nakon upotrebe, potrebno je osloboditi memoriju koju zauzima niz stringova. To se postiže tako da se najprije dealocira memorija koju zauzimaju stringovi.

for(i=0; i<N; i++) { if(txt[i] != NULL) /* oslobodi samo alocirane stringove */ free(txt[i]); }

a zatim se dealocira memorija koju zauzima niz pokazivača txt;

free(txt);

Prikazana struktura je zgodna za unošenje proizvoljnog broja linija teksta, i može biti temeljna struktura u editoru teksta. U ovu strukturu je lako unositi tekst , brisati tekst i povećavati broj linija teksta.

Primjer: U programu alloctxt.c korisnik unosi proizvoljan broj linija teksta. Početno se alocira memorija za 5 linija teksta. Ako korisnik unese više linija, tada se pomoću funkcije realloc() povećava potrebna memorija za dodatnih 5 linija. Unos završava kada se otkuca prazna linija. Nakon toga se vrši sortiranje teksta pomoću funkcije qsort(). Na kraju programa ispisuje se sortirani tekst i dealocira memorija.

/* Datoteka: alloctxt.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> int CmpString( const void *pstr1, const void *pstr2 ); char *strdup( char * s); void memerror(); int main( void ) { char **txt; /* pokazivač na pokazivač stringa */ int maxsize = 5; /* početna maksimalna veličina niza */ int numstrings = 0; /* početno je u nizu 0 stringova */ char str[256]={"\0"};

Page 167: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

167

int i; /* * Početno alociraj memoriju za maxsize=5 , * Koristi malloc koji vraća pokazivač u txt. * Izvrši provjeru NULL vrijednosti pokazivača */ txt = malloc(maxsize * sizeof(char *)); if(txt == NULL ) memerror() ; /* * Učitaj proizvoljan broj stringova u niz txt[]: * Prethodno alociraj memoriju sa svaki uneseni string * Ako broj stringova premaši maxsize: * povećaj allocirani niz za 5 elemenata pomoću funkcije realloc * Kraj unosa je ako se unese prazna linija. */ while(gets(str) != NULL) { char *s = strdup( str) ; if(s== NULL) memerror(); if(strlen(s)==0) break; txt[numstrings++] = s; if(numstrings >= maxsize) { maxsize += 5; txt = realloc( txt, maxsize * sizeof( int )); if(txt== NULL) memerror(); } } /* sortiraj niz stringova leksikografski */ if(numstrings > 1) qsort((void *)txt, numstrings, sizeof(char*), CmpString); /* Ispiši podatke iz dinamički alociranog niza * i vrati memoriju na heap koristeći free() * za svaki pojedinačni string */ for(i = 0; i < numstrings; i ++ ) { puts(txt[i]) ; free( txt[i] ); } free(txt); return 0 ; } int CmpString( const void *pstr1, const void *pstr2 ) { /* Ova usporedna funkcija se koristi u funkciji qsort() * Prema dogovoru, u usporednu funkciju se šalju pokazivači * na element niza, u ovom slučaju * pokazivac na string, odnosno char **.

Page 168: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

168

* Za usporedbu stringova koristi se funkcija strcmp(). * Pošto argument funkcije strcmp mora biti string (char *) * to znači da joj se mora poslati *pstr1 i *pstr2, * odnosno sadržaj argumenata pstr1 i pstr2, * koji su pokazivaci tipa char** */ return strcmp( *(char **) pstr1, *(char **)pstr2 ); } void memerror() { /* funkcija za dojavu pogreške */ printf("%s", "Greska pri alociranju memorije"); exit(1); }

Dinamičke matrice Matrica je skup istovrsnih elemenata koji se obilježavaju s dva indeksa - mat[i][j]. Prvi

indeks predstavlja redak, a drugi indeks predstavlja stupac matrice. Matrica se može programski realizirati kao dinamička struktura. Primjerice, matrica imena mat, koja će sadržavati elemente tipa double, se sljedećim postupkom:

Slika 12.3 Prikaz korištenja memorije za dinamičke matrice

1. Identifikator matrice se deklarira kao pokazivač na pokazivač na double, a dvije varijable koje označavaju broj redaka i stupaca matrice se iniciraju na neku početnu vrijednost.

int brojredaka = 5, brojstupaca=10; double **mat;

2. Alocira se memorija za niz pokazivača na retke matrice (mat[i])

mat = malloc( brojredaka * sizeof(double *));

3. Zatim se alocira memorija za svaki pojedini redak. To zauzeće je određeno brojem stupaca matrice:

for(k = 0; k < brojredaka; k++) mat[i] = malloc( brojstupaca * sizeof(double));

4. Ovime je postupak formiranja matrice završen. Nakon toga se mogu raditi operacije s matricom. Primjerice, iskaz

for(i = 0; i < brojredaka; i++) for(j = 0; j < brojstupaca; j++) mat[i][j] = 0;

Page 169: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

169

postavlja sve elemente matrice na vrijednost nula. 5. Nakon završetka rada s matricom potrebno je osloboditi alociranu memoriju. To se postiže tako da se najprije dealocira memorija koju zauzimaju elementi matrice;

for(k=0; k < brojredaka; k++) free(mat[k]);

a zatim se dealocira memorija koju zauzima niz pokazivača retka;

free(mat);

Iako ovaj postupak izgleda dosta kompliciran, on se često koristi jer se njime omogućuje rad s matricama čije se dimenzije mogu mijenjati tijekom izvršenja programa. Također, pogodno je pisati funkcije s ovakvim matricama, jer se mogu primijeniti na matrice proizvoljnih dimenzija. Primjerice, funkcija:

void NulMat(double **mat, int brojredaka, int brojstupaca) { for(int i = 0; i < brojredaka; i++) for(int j = 0; j < brojstupaca; j++) mat[i][j] = 0; }

može poslužiti da se postavi vrijednost elemenata bilo koje matrice na vrijednost 0. Ovo nije moguće kod korištenja statičkih nizova je se tada u definiciji funkcije, u parametru koji označava dvodimenzionalni niz, uvijek mora označiti broj kolona. Funkcija PrintMat() može poslužiti za ispis matrice u proizvoljnom formatu:

void PrintMat(double **mat, int brojredaka, int brojstupaca, char *format) { int i,j; for( i = 0; i < brojredaka; i++) { for(j = 0; j < brojstupaca; j++) printf (format, mat[i][j]); printf("\n") ; } }

Sam postupak alociranja i dealociranja matrice se može formalizirati s dvije funkcije

double **AlocirajMatricu (int brojredaka,int brojstupaca) { int k; double **mat = malloc( brojredaka * sizeof(double *)); if(mat == NULL) return NULL; for(k = 0; k < brojredaka; k++) mat[k] = malloc( brojstupaca * sizeof(double)); return mat; } void DealocirajMatricu(double **mat, int brojredaka)

Page 170: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

170

{ int k; for(k=0; k < brojredaka; k++) free(mat[k]); free(mat); }

Primjer: U programu allocmat.c formira se matrica s 3x4 elementa, ispunja slučajnim brojem i ispisuje vrijednost elemenata matrice. Zatim se ta matrica dealocira i ponovo alocira s 2x2 elementa.

/* Datoteka: allocmat.c */ #include <stdio.h> #include <stdlib.h> #include <math.h> double **AlocirajMatricu (int brojredaka,int brojstupaca); void DealocirajMatricu(double **mat, int brojredaka); void PrintMat(double **mat, int brojredaka, int brojstupaca, char *format); int main( void ) { int i,j; double **mat; /* matrica */ int rd= 3; /* broj redaka */ int st= 4; /* broj stupaca */ /* formiraj matricu sa 3x4 elementa*/ mat = AlocirajMatricu(rd, st); if(mat == NULL) exit(1); /* ispuni matricu sa slučajnim vrijednostima */ for(i = 0; i < rd; i++) for(j = 0; j < st; j++) mat[i][j] = (double)rand(); /* ispisi vrijednosti */ PrintMat(mat, rd, st, "%12.2lf"); DealocirajMatricu(mat, rd); /* sada formiraj drugu matricu sa 2x2 elementa*/ rd = 2; st = 2; mat = AlocirajMatricu(rd, st); if(mat == NULL) exit(1); for(i = 0; i < rd; i++) for(j = 0; j < st; j++) mat[i][j] = (double)rand(); /* ispisi vrijednosti */ PrintMat(mat, rd, st, "%12.2lf"); DealocirajMatricu(mat, rd); return 0 ;

Page 171: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

171

}

/* definiraj potrebne funkcije */ Dobije se ispis: 41.00 18467.00 6334.00 26500.00 19169.00 15724.00 11478.00 29358.00 26962.00 24464.00 5705.00 28145.00 23281.00 16827.00 9961.00 491.00

12.4 Standardne funkcije za brzi pristup memoriji U standardnoj biblioteci je definirano nekoliko funkcija koje su optimirane za brzi pristup memoriji.

void *memcpy(void *d, const void *s, size_t n) Kopira n znakova s adrese s na adresu d, i vraća d.

void *memmove(void *d, const void *s, size_t n) Ima isto djelovanje kao memcpy, osim što vrijedi i za preklapajuća memorijska područja.

int memcmp(const void *s, const void *t, size_t n) uspoređuje prvih n znakova s i t; vraća rezultat kao strcmp.

void *memchr(const void *m, int c, size_t n) vraća pokazivač na prvu pojavu znaka c u memorijskom području m, ili NULL ako znak nije prisutan među prvih n znakova.

void *memset(void *m, int c, size_t n) postavlja znak c u prvih n bajta od m , vraća m. Primjer: prethodnu funkciju NulMat() može se napisati pomoću memset() funkcije:

void NulMat(double **mat, int brojredaka, int brojstupaca) { for(int i = 0; i < brojredaka; i++) memset(mat[i], 0, brojstupaca*sizeof(double) }

Page 172: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

172

13 Korisnički definirane strukture podataka

Naglasci: • korisnički definirane strukture podataka • članovi strukture • prijenos struktura u funkcije • strukture i funkcije za očitanje vremena • unija podataka • bit polja • pobrojani tipovi

U C-jeziku je implementiran mehanizam za definiranje korisničkih tipova podataka. To su:

1. Struktura - tip koji označava uređen skup varijabli, koje mogu biti različitog tipa 2. Unija - tip kojim se jednom memorijskom objektu pridjeljuje skup tipova 3. Pobrojani tip (ili enumeracija) - tip definiran skupom imenovanih cjelobrojnih

konstanti

13.1 Struktura (struct) Struktura je skup od jedne ili više varijabli, koje mogu biti različitog tipa, grupiranih

zajedno pod jednim imenom radi lakšeg rukovanja. Varijable koje čine strukturu nazivaju se članovi ili polja strukture. Član strukture može biti bilo koji tip koji je dozvoljen u C jeziku (prosti tip, pokazivački tip, niz ili prethodno definirani korisnički tip).

Za definiranje strukture koristi se ključna riječ struct. Tako nastaje složeni strukturni tip

podataka - struktura - pomoću koje se mogu deklarirati varijable odnosno memorijski objekti strukturnog tipa.

U radu sa strukturnim tipovima potrebno je znati: 1. Kako se deklarira struktura i njezini članovi, 2. Kako se deklariraju varijable strukturnog tipa, 3. Kako se operira sa strukturnim varijablama, 4. Kako se može izvršiti inicijaliziranje početne vrijednosti varijable strukturnog tipa.

Strukturni tip se definira prema sljedećem sintaktičkom pravilu:

struct oznaka_strukture { tip1 oznaka_člana1; tip2 oznaka_člana2; .... };

Page 173: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

173

struct oznaka_strukture lista_varijabli;

oznaka_varijable.oznaka_člana

Primjerice, zapisom

struct _tocka { int x; int y; };

deklarirana je struktura imena _tocka, koja ima dva člana: x i y. Pomoću ove strukture zgodno je deklarirati varijable koje će označavati položaj točke u pravokutnom koordinatnom sustavu. Na slici 13.1 prikazan je pravokutnik kojeg određuju dvije točke T1 i T2. Deklarira ih se iskazom

struct _tocka T1, T2;

a vrijednost koordinata se postavlja u sljedećoj sekvenci:

T1.x = 2; T1.y = 1; T2.x = 6; T2.y = 4;

Slika13. 1. Označavanje koordinata točke i pravokutnika

Ukoliko se želi izračunati površinu pravokutnika može se pisati:

int povrsina; ..... povrsina = (T2.x – T1.x)*(T2.y-T2.x) .......

Na ovaj način je dobiven programski zapis kojim se poboljšava apstrakcija. Dalje se razina apstrakcije može povećati na način da se deklarira struktura _pravokutnik koja ima dva člana koji predstavljaju točke dijagonala:

struct _pravokutnik

Page 174: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

174

{ struct _tocka p1; struct _tocka p2; }

Dakle, članovi strukture mogu biti prethodno definirani strukturni tipovi. Sada se prijašnji programski segment može zapisati sljedećom sekvencom:

int povrsina; struct _pravokutnik pravokutnik; ..... pravokutnik.p1.x = 2; pravokutnik.p1.y = 1; pravokutnik.p2.x = 6; pravokutnik.p2.y = 4; ...... povrsina = ( pravokutnik.p2.x – pravokutnik.p1.x) *( pravokutnik.p2.y - pravokutnik.p2.x)

Uočite da se za pristup članovima strukture _tocka, koja je član strukture _pravokutnik, pristupa tako da se dva puta koristi točka-operator.

Potrebno je navesti još neka sintaktička pravila za rad sa strukturama. 1. Može se istovremeno izvršiti deklariranje strukturnih varijabli i same strukture, primjerice

struct _tocka {int x; int y;} T1,T2;

Ako kasnije neće biti potrebno deklarirati nove varijable ovog tipa, može se izostaviti oznaka strukture, tj.

struct {int x; int y;} T1,T2;

2. Pomoću typedef može se definirati sinonim za strukturu, primjerice

typedef struct tocka _tocka; typedef struct _pravokutnik { _tocka p1; _tocka p2 } pravokutnik_t;

Sada _tocka i pravokutnik_t predstavljaju oznake tipova pomoću kojih se može deklarirati varijable (bez navođenja ključne riječi struct), primjerice

_tocka T1, T2; pravokutnik_t pravokutnik;

3. Inicijaliziranje početnih vrijednosti se može izvršiti na sličan način kao kod nizova, tako da se pripadajuća lista početnih vrijednosti navede unutar vitičastih zagrada.

struct _tocka T1 = {2,1}, T2 = {6,4}; struct _pravokutnik pravokutnik = { {2,1}, {6,4} };

Page 175: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

175

Ako se struktura inicijalizira djelomično, elementi koji nisu inicijalizirani se postavljaju na vrijednost nula.

Strukture i nizovi

Elementi strukture mogu biti nizovi. Također, može se formirati nizove struktura. Primjerice, struktura za vođenje evidencije o ocjenama pojedinog studenta može imati oblik:

typedef struct _studentinfo { char ime[30]; int ocjena; } studentinfo_t;

Uz pretpostavku da nastavu pohađa 30 studenata, za vođenje evidencije o studentima može se definirati niz:

studentinfo_t student[30];

Pojedinom članu strukture, koji je element niza, pristupa se na način da se točka operator piše iza uglatih zagrada, primjerice:

student[0].ocjena = 2; student[1].ocjena = 5;

Struktura kao argument funkcije

Strukture se, za razliku od nizova, tretiraju kao "tipovi prve klase". To znači da se može pridijeliti vrijednost jedne strukturne varijable drugoj, da se vrijednost strukturne varijable može prenositi kao argument funkcije i da funkcija može vratiti vrijednost strukturne varijable. Program struct.c demonstrira upotrebu strukturnih varijabli u argumenatima funkcije.

/* Datoteka: struct.c */ /* Primjer definiraja strukture i koristenja */ /* strukture kao argumenta funkcije */ #include <stdio.h> #include <string.h> typedef struct _student { char ime[30]; int ocjena; }studentinfo_t; void display( studentinfo_t st ); int main() { studentinfo_t student[30]; int i=0; strcpy( student[0].ime, "Marko Matic" ); student[0].ocjena = 4; strcpy( student[1].ime, "Marko Katic" ); student[1].ocjena = 2; strcpy( student[2].ime, "Ivo Runjanin" ); student[2].ocjena = 1;

Page 176: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

176

strcpy( student[3].ime, "" ); student[3].ocjena = 0; while (student[i].ocjena != 0 ) display( student[i++]); return 0; } void display(studentinfo_t st) { printf( "Ime: %s ", st.ime ); printf( "\tocjena: %d\n", st.ocjena ); }

Rezultat izvršenja programa je:

Ime: Marko Matic ocjena: 4 Ime: Marko Katic ocjena: 2 Ime: Ivo Runjanin ocjena: 1

U programu se prvo inicijalizira prva tri elementa niza koji su tipa studentinfo_t. Četvrtom elementu se vrijednost člana ocjena postavlja na vrijednost nula. Ova nulta ocjena će kasnije služiti kao oznaka elementa niza, do kojeg su uneseni potpuni podaci. Ispis se vrši pomoću funkcije display(studentinfo_t).

U prethodnom programu strukturna varijabla se prenosi u funkciju po vrijednosti. Ovaj način prijenosa strukture u funkciju nije preporučljiv jer se za prijenos po vrijednosti na stog mora kopirati cijeli sadržaj strukture. To zahtijeva veliku količinu memorije i procesorskog vremena (u ovom slučaju veličina strukture je 34 bajta). Mnogo bolji način prijenosa strukture u funkciju je da se koristi pokazivač na strukturu, a da se zatim u funkciji elementima strukture pristupa indirekcijom.

Pokazivači na strukturne tipove i operator ->

Neka su deklarirani varijabla i pokazivač tipa studentinfo_t:

studentinfo_t St, *pSt;

i neka je varijabla St inicijalizirana slijedećim iskazom:

St.ime = strcpy("Ivo Boban"); St.ocjena = 1;

Ako se pokazivač pSt inicijalizira na adresu varijable St, tj.

pSt = &St;

tada se može pristupiti varijabli St i pomoću dereferenciranog pokazivača. Primjerice, ako se želi promijeniti ocjenu na vrijednost 2 i ispisati novo stanje, može se koristiti iskaze:

(*pSt).ocjena = 2; printf("Ime: %s, ocjena: %d", (*pSt).ime, (*pSt).ocjena)

Uočite da se zbog primjene točka operatora dereferencirani pokazivač mora napisati u zagradama, jer bi inače točka operator imao prioritet. Ovakav način označavanja indirekcije je dosta kompliciran, stoga je u C jeziku definiran operator ->, pomoću kojeg se prethodni iskazi zapisuju u obliku:

Page 177: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

177

pSt->ocjena = 2; printf("Ime: %s, ocjena: %d", pSt->ime, pSt->ocjena)

Vrijedi ekvivalentni zapis

(*pStudent).ocjena ⇔ pStudent->ocjena

Operator -> označava pristup elementu strukture pomoću pokazivača i može ga se zvati operatorom indirekcije pokazivača strukture.

Primjer: U programu structp.c pokazano je kako se prethodni program može napisati mnogo efikasnije korištenjem pokazivača. U ovom primjeru pokazivači će se koristiti i kao članove strukture i pomoću njih će se vršiti prijenos strukturne varijable u funkciju.

/* Datoteka: structp.c * Primjer definiraja strukture pomocu pokazivačkih članova i * korištenje pokazivača strukture kao argumenta funkcije */ #include <stdio.h> #include <stdlib.h> /* def. NULL */ typedef struct _student { char *ime; int ocjena; }studentinfo_t; void display( studentinfo_t *pSt ); int main() { int i=0; studentinfo_t *p; studentinfo_t student[30] = { { "Marko Matic", 4 }, { "Marko Katic", 2 }, { "Ivo Runjanin", 1 }, { NULL, 0} }; p=&student[0]; while (p->ime != NULL ) display( p++); return 0; } void display(studentinfo_t *pS) { printf( "Ime: %s ", pS->ime ); printf( "\tocjena: %d\n", pS->ocjena ); }

Page 178: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

178

Prvo što se treba uočiti u ovom programu je način kako je deklarirana struktura _student . U njoj sada nije rezerviran fiksni broj mjesta za član ime, već ime sada predstavlja pokazivač na char (string).

typedef struct _student { char *ime; int ocjena; }studentinfo_t;

Niz student je inicijaliziran pri samoj deklaraciji sa:

studentinfo_t student[30] = { { "Marko Matic", 4 }, { "Marko Katic", 2 }, { "Ivo Runjanin", 1 }, { NULL, 0} };

Ovime se postiže ušteda memorije jer se za string ime rezervira točno onoliko mjesta koliko je upisano inicijalizacijom (plus nula!). Pomoćnim pokazivačem p, kojeg se početno inicira na adresu nultog elementa niza student, pretražuje se niz sve dok se ne dođe do elementa kojem član ime ima vrijednost NULL pokazivača. Dok to nije ispunjeno vrši se ispis sadržaja niza pomoću funkcije display(), kojoj se kao argument prenosi pokazivač na tip studentinfo_t. Unutar funkcije display() elementima strukture se pristupa indirekcijom pokazivača strukture (->).

Na kraju, da se zaključiti, da prijenos strukture u funkciju u pravilu treba vršiti pomoću pokazivača. Isto vrijedi za slučaj kada funkcija vraća vrijednost strukture. I u tom slučaju je bolje raditi s pokazivačima na strukturu, jer se štedi memorija i vrijeme izvršenja programa. Prenošenje strukture po vrijednosti se može tolerirati samo u slučajevima kada struktura ima malo zauzeće memorije.

Memorijska slika strukturnih tipova

U tablici 13.1 dana je usporedba karakteristika nizova i struktura. Važno je uočiti da članovi struktura u memoriji nisu nužno poredani neposredno jedan do drugog. Razlog tome je činjenica da je dozvoljeno da se članovi strukture smještaju u memoriju na način za koji se ocjenjuje da će dati najbrže izvršenje programa (većina procesora brže pristupa parnim adresama, nego neparnim adresama).

karakteristika niz struktura strukturalni sadržaj kolekcija elemenata istog tipa kolekcija imenovanih članova koji

mogu biti različitog tipa

pristup elementima složenog tipa

elementima se pristupa pomoću indeksa niza

članovima se pristupa pomoću imena

raspored elemenata u memoriji računala

u nizu jedan do drugog u nizu, ali ne nužno jedan do drugog

Tablica 13.1. Usporedba nizova i struktura

Kod kompilatora Microsoft Visual C može se posebnom direktivom (#pragma pack(1)) zadati da se članovi strukture "pakiraju" u memoriju jedan do drugog. To pokazuje program pack.c.

Page 179: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

179

/* Datoteka: pack.c ***********************************/ /* Primjer memorijski pakirane i nepakirane strukture */ #include <stdio.h> struct s1 { char c; short i; double d; } v1; #pragma pack(1) /* forsiraj pakiranu strukturu */ struct s2 { char c; short i; double d; } v2; int main( void) { printf("Minimalno zauzece memorije: %d bajta\n", sizeof(char)+sizeof(short)+ sizeof(double)); printf("Zauzece memorije nepakiranom strukturom: %d bajta\n", sizeof(struct s1)); printf("Zauzece memorije pakiranom strukturom: %d bajta\n", sizeof(struct s2)); printf("\nAdresa elementa : nepakirano pakirano\n"); printf("Adresa od c : %p %p\n", &v1.c, &v2.c); printf("Adresa od i : %p %p\n", &v1.i, &v2.i); printf("Adresa od d : %p %p\n", &v1.d, &v2.d); return 0; }

Nakon izvršenja programa dobije se poruka:

Minimalno zauzece memorije: 11 bajta Zauzece memorije nepakiranom strukturom: 16 bajta Zauzece memorije pakiranom strukturom: 11 bajta Adresa elementa : nepakirano pakirano Adresa od c : 00406B90 00406BA0 Adresa od i : 00406B92 00406BA1 Adresa od d : 00406B98 00406BA3

Uočite da je zauzeće memorije pakirane strukture minimalno i jednako zbroju veličine zauzeća memorije pojedinog člana, dok kod nepakirane strukture ostaje neiskorišten jedan bajt između char i short, te dva bajta između short i double.

Ne smije se pretpostaviti veličina zauzeća memorije na temelju zauzeća memorije pojedinog člana strukture. Za određivanje veličine memorije koju zauzima strukturni tip uvijek treba koristiti operator sizeof.

Page 180: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

180

13.2 Union – zajednički memorijski objekt za različite tipova podataka S ključnom riječi union definira se korisnički tip podataka pomoću kojeg se nekom

memorijskom objektu može pristupiti s različitim tipskim pristupom. Deklaracija unije sliči deklaraciji strukture, primjerice,

union skalar { char c; int i; double d; };

međutim, njeno značenje je bitno drukčije. Ako se s unijom skalar deklarira objekt imena obj, tj.

union skalar obj;

tom se objektu može pristupiti na više načina, jer obj može sadržavati vrijednost tipa char, int ili double. Dozvoljeno je pisati:

obj.c = 'A'; obj.i = 1237; obj.d = 457.87

Sve ove vrijednosti se upisuju u istu memorijsku lokaciju. Veličina zauzeća memorije je određena elementom unije koji zauzima najveći prostor memorije.

U programiranju se često unija koristi unutar neke strukture kojoj posebni element služi za označavanje tipa vrijednosti koji trenutno sadrži unija. Primjerice,

#define char_type 0 #define int_type 1 #define double_type 2 struct variant { int var_type; /* oznaka tipa vrijednosti unije skalar */ union skalar var; }; struct variant obj; /* pored vrijednosti, zabilježimo i tip vrijednosti*/ obj.var.c = 'A'; obj.var_type = char_type; obj.var.i =1237; obj.var_type = int_type; obj.var.d =457.87 obj.var_type = double_type; .......... /* uniju koristimo tako da se najprije provjeri tip vrijednosti */ switch(obj.val_type) { case char_type: printf("%c", obj.var.c); break; case int_type: printf("%d", obj.var.i); break; case double_type: printf("%f", obj.var.d); break;

Page 181: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

181

}

13.3 Bit-polja Unutar strukture ili unije može se specificirati veličinu cjelobrojnih članova u bitovima .

To se izvodi tako da se iza člana strukture navede dvotočka i broj bitova koliko taj član zauzima. Primjerice

struct bitfield1 { int i3b : 3; unsigned int i1b : 1; signed int i7b : 7; };

U ovoj deklaraciji je definirano da u strukturi bitfield1 član i3b je cijeli broj od 3-bita, član i1b je 1-bitni kardinalni broj , a član i7b je 7-bitni cijeli broj. (uočite da 1-bitni član može imati samo dvije vrijednosti 0 i 1)

Ovakve strukture se koriste u cilju uštede memorijskog prostora, posebno u slučaju kad se većina članova tretira kao logička vrijednost. Članovima strukture se pristupa kao da su normalni cijeli brojevi, a kompilator vodi računa o tome da se maskiraju nepostojeći bitovi.

struct bitfield1 a,b; a.i3b=3; b.i7b=65;

Manipuliranje s ovakvim strukturama je potpuno pod kontrolom kompilatora. Zbog toga se ne može koristiti pokazivače na članove strukture jer oni nisu direktno adresibilni. Iz istog razloga nije moguće koristiti nizove bitnih-polja.

Kada se želi kontrolirati kako se slažu bit-polja unutar jedne riječi, na raspolaganju su dva mehanizma. Prvi je da se umetnu bezimeni članovi koji će predstavljati "prazne bitove". Drugi je mehanizam da veličina bezimenog polja može biti 0. To je poruka kompilatoru da se na tom mjestu završi pakiranje u jednu riječ, i da se od tog mjesta članovi strukture pakiraju u novu riječ. Primjerice

struct bitfield2 { int i3b : 3; unsigned int i1b : 1; signed int i7b : 7; int : 2; int i2b: 2; int : 0; int i4b : 4, i5b : 5; };

opisuje strukturu koja se pakira u dvije riječi. Prva sadrži redom 3-, 1-, i 7-bitna polja, zatim 2-bitnu prazninu, te 2-bitno polje i2b, a druga riječ sadrži 4-bitna i 5-bitna polja i4b i i5b.

Page 182: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

182

13.4 Pobrojanji tip (enum) Treća klasa korisnički definiranih tipova C jezika je tzv. pobrojani tip (eng. enumeration).

Deklarira se pomoću ključne riječi enum. Služi definiranju integralnog cjelobrojnog tipa kojemu se skup vrijednosti označava simboličkim (imenovanim ) konstantama u sljedećoj notaciji:

pobrojani_tip: enum ime_tipa { lista_definicije_ konstanti };

definicija_ konstanti:

ime ime = konstanta .

Primjerice, deklaracijom enum dani_t {Nedjelja, Ponedjeljak, Utorak, Srijeda, Cetvrtak, Petak, Subota}; definira se korisnički pobrojani tip dani_t kojem se vrijednost označava simboličkim konstantama: Nedjelja, Ponedjeljak, Utorak, Srijeda, Cetvrtak, Petak, Subota. Pri kompiliranju programa ovim se konstantama pridjeljuje numerička vrijednost prema pravilu da prvo ime u listi ima numeričku vrijednost 0, drugo ime ima vrijednost 1, treće ime ima vrijednost 2 itd. Pomoću pobrojanog tipa mogu se deklarirati varijable, prema pravilu

deklaracija_varijable: enum ime_tipa lista_varijabli; primjerice,

enum dani_t danas, sutra; ....... danas = Srijeda; sutra = Cetvrtak; ........

Vrijednost se neke ili svih simboličkih konstanti može inicijalizirati već pri samoj deklaraciji pobrojanog tipa. Primjerice,

enum karte_t {AS = 1, JACK = 11, DAMA, KRALJ};

Neinicijalizirane konstante imaju vrijednost za jedan veću od vrijednosti prethodne konstante, tako DAMA ima vrijednost 12, a KRALJ ima vrijednost 13.

Pobrojane tipove ne treba smatrati posebno “čvrstim” tipovima jer ih se može tretirati ravnopravno s cjelobrojnim tipom. Primjerice, dozvoljeno je pisati:

int i = Subota; enum karte_t c = 10; c++; /* c postaje jack (11) */

Ponekad se enum koristi samo za definiranje simboličkih konstanti (poput #define direktive). Umjesto korištenja leksičkih direktiva:

#define ONE 1 #define TWO 2 #define THREE 3

može se koristiti enum deklaracija:

Page 183: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

183

enum threenum {ONE=1, TWO, THREE};

13.5 Strukture i funkcije za očitanje vremena U datoteci <time.h> definirano je nekoliko funkcija i struktura imena tm, za očitanje i manipuliranje vremena i datuma. Dosada je za očitanje vremena korištena funkcija

time_t time(time_t *tp);

koja vraća vrijednost time_t tipa, tj. kardinalni broj koji predstavlja trenutno vrijeme (obično je to broj sekundi od 1.1.1970.). Parametar tp, ako nije NULL, također prihvaća trenutno vrijeme u *tp.

Da bi se olakšalo pretvorbu ovog vremena u stvarno vrijeme i datum, definirana je struktura tm sa sljedećim članovima:

struct tm /* opisuje vrijeme i datum */ { int tm_sec; /* sekunde 0..61 */ int tm_min; /* minute 0..59 */ int tm_hour; /* sat 0..23 */ int tm_mday; /* dan 1..31 */ int tm_mon; /* mjesec 0..11 */ int tm_year; /* broj godina nakon 1900 */ int tm_wday; /* dan u sedmici 0..6 */ int tm_yday; /* dan u godini 0..365 */ int tm_isdst; /* da li je dan promjene sata 0..1 */ };

Napomena: ako dan promjene sata nije implementiran tada tm_isdst ima negativnu vrijednost.

Broj sekundi može biti veći od 59 u slučaju prestupnog vremena. Mjeseci su kodiranu tako da 0 označava siječanj, 1 veljaču itd. Dani u sedmici su kodirani tako da 0 označava nedjelju, 1 ponedjeljak itd. Stvarna godina se dobije tako da se članu tm_year doda vrijednost 1900 (primjerice u godini 2002. godini član tm_year sadrži vrijednost 102).

localtime, gmtime

Pretvorbu vremena iz formata time_t u struct tm vrši se funkcijom localtime(), kada se želi dobiti lokalno vrijeme, ili funkcijom gmtime() za dobiti univerzalno vrijeme u nultom meridijanu.

struct tm *localtime(const time_t *t); struct tm *gmtime(const time_t *t);

Obje funkcije primaju adresu varijable koja sadrži vrijeme u formatu time_t, a vraćaju pokazivač na statičku strukturu tipa tm (sadržaj se obnavlja pri svakom pozivu ovih funkcija) .

ctime, asctime

Ako se želi dobiti zapis vremena u obliku stringa, mogu se koristiti funkcije

char *ctime(const time_t *t); char *asctime(const struct tm *tp);

Page 184: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

184

Funkcija ctime() za argument koristi adresu varijable koja sadrži vrijeme u formatu time_t, a funkcija asctime()za argument koristi pokazivač na strukturu tm. Obje funkcije vraćaju pokazivač statičkog stringa koji sadrži zapis vremena u standardnom formatu. Primjerice, sekvenca naredbi

time_t t = time(NULL); char *s = ctime(&t); puts(s);

generira ispis:

Sat May 11 14:21:20 2002

Uočite da je rezultat poziva ctime(&t) ekvivalentan pozivu asctime(localtime(&t)) . Standardna verzija je prilagođena američkim standardima. Ako se želi napisati vrijeme u formatu

11.05.2002 14:21

tada se može koristiti sljedeće iskaze:

/* ispisuje datum i vrijeme u formatu 11.05.2002 14:21 */ time_t t = time(NULL); struct tm *p = localtime(&t); printf("%.2d.%.2d.%.2d %2d:%.2d\n", p->tm_mday, p->tm_mon + 1, p->tm_year +1900, p->tm_hour, p->tm_min);

strftime

Funkcija strftime() se koristi za formatirani ispis vremena. Format se zadaje kao kod printf() funkcije. Prototip funkcije strftime()glasi:

size_t strftime(char *buf, size_t bufsize, const char *fmt, const struct tm *tp);

Prvi argument je string str u koji se vrši formatirani zapis. Drugi argument (bufsize) ograničava broj znakova stringa. Treći parametar je string u kojem se zapisuje format ispisa nizom specifikatora oblika %x (kao kod printf() funkcije). Posljednji argument je pokazivač strukture tm. Funkcija vraća broj znakova u stringu ili 0 ako nije moguće generirati formatirani string. Specifikatori formata su

%a kratica od tri slova za ime dana u sedmici (eng. Sun, Mon, Tue,..) %A puno ime dana u sedmici (eng...) %b kratica od tri slova za ime mjeseca (eng. Jan, Feb, Mar,...) %B puno ime mjeseca (eng....) %c kompletni zapis vremena i datuma %d dan u mjesecu (1..31) %H sat u formatu (1..24) %I sat u formatu (1..12) %j dan u godini (1..365) %m mjesec u godini (1..12) %M minute %p AM/PM (eng.) string koji označava jutro ili popodne %S sekunde %U broj za sedmicu u godini (1..52) - 1 određen prvom nedjeljom

Page 185: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

185

%w broj za dan u sedmici (0-nedjelja) %W broj za sedmicu u godini (1..52) - 1 određen prvim ponedjeljkom %x kompletni zapis datuma %X kompletni zapis vremena %y zadnje dvije znamenke godine %Y godina u formatu s 4 znamenke %Z ime vremenske zone (ako postoji ) %% znak %

Primjer: U programu vrijeme.c demonstrira se korištenje funkcija za datum i vrijeme.

/* Datoteka: vrijeme.c */ /* Prikazuje datum i vrijeme u tri formata*/ #include <stdio.h> #include <time.h> int main() { time_t vrijeme = time(NULL); struct tm *ptr; char datum_str[20]; /* ispisuje datum i vrijeme u standardnom formatu */ puts(ctime(&vrijeme)); /* ispisuje datum i vrijeme pomoću strftime funkcije */ strftime(datum_str, sizeof(datum_str), "%d.%m.%y %H:%M\n", localtime(&vrijeme)); puts(datum_str); /* ispisuje datum i vrijeme u proizvoljnom formatu */ ptr = localtime(&vrijeme); printf("%.2d.%.2d.%.2d %2d:%.2d\n", ptr->tm_mday, ptr->tm_mon+1, ptr->tm_year +1900, ptr->tm_hour, ptr->tm_min); return 0; }

Dobije se ispis:

Mon May 13 20:13:06 2002 13.05.02 20:13 13.05.2002 20:13

Za obradu vremena se koriste i sljedeće funkcije:

mktime time_t mktime(struct tm *tp) Funkcija mktime() pretvara zapisa iz strukture tm u time_t format. Korisna je u tzv. kalendarskim proračunima. Kada je potrebno dodati nekom datumu n dana, tada se može upisati

Page 186: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

186

datum u tm strukturu, povećati član tm_mday za n, zatim pozivom mktime() se dobije time_t vrijednost koja odgovara novom datumu.

difftime

double difftime(time_t t1, time_t t2)

Funkcija difftime() vraća realnu vrijednost koja je jednaka razlici vremena t1 i t1 u sekundama.

clock

clock_t clock(void);

Funkcija clock() služi za preciznije mjerenje vremena nego je to moguće sa prethodnim funkcijama. Ona vraća vrijednost procesorskog mjerača vremena, koji starta na početku programa, u jedinicama koje su znatno manje od sekunde (nekoliko milisekundi). Koliko je tih jedinica u jednoj sekundi određeno je konstantom CLOCKS_PER_SEC. To znači da izraz:

(double)clock()/CLOCKS_PER_SEC

daje vrijednost koja je jednaka vremenu (u sekundama) od startanja programa. Primjer: U programu brzina.c ispituje se vremenska rezolucija funkcije clock(), tj. minimalno vrijeme koje se njome može mjeriti. Također, pomoću funkcije clock() mjeri se koliko je potrebno vremena za izvršenje sinusne funkcije.

/* Datoteka: brzina.c * Program određuje vremensku rezoluciju funkcije clock() * i mjeri brinu izvršenja sinus funkcije */ #include <stdio.h> #include <math.h> #include <time.h> int main() { double start, stop; double n ; double rezolucija; start =(double)clock()/CLOCKS_PER_SEC; do { stop=(double)clock()/CLOCKS_PER_SEC; } while (stop == start); rezolucija = stop-start; printf("Rezolucija CLOCK-a je %g sekundi\n" , rezolucija); start =(double)clock()/CLOCKS_PER_SEC; stop = start + 10*rezolucija; do { n += 1.0; sin(n);

Page 187: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

187

} while (stop > (double)clock()/CLOCKS_PER_SEC); printf("Funkcija sin se izvršava %g sekundi\n" , 10*rezolucija/n); return 0; }

Dobije se ispis:

Rezolucija CLOCK-a je 0.015 sekundi Funkcija sin se izvrsava u 2.3543e-007 sekundi

Rezolucija funkcije clock() je 15 ms. U drugoj petlji, u kojoj se računa funkcija sin(), odabrano je da se ona ponavlja za vrijeme koje je 10 puta veće od rezolucije clock()-a. Na taj način mjerenje vremena izvršenja sinus funkcije vrši se s greškom koja je manja ili jednaka 10%.

Page 188: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

188

14 Leksički pretprocesor

Naglasci: • leksičke direktive • makro-supstitucije • leksički string-operatori • direktive za uvjetno kompiliranje

Pojam "pretprocesor'' se koristi za oznaku prve faze obrade izvornog koda. Kod nekih C kompilatora pretprocesor je izveden kao zasebni program. Pretprocesor ne analizira sintaksu zapisanog koda, već koristeći leksičke direktive, vrši leksičku obradu izvornog kôda na tri načina:

1. Umeće u izvorni kôd datoteke koje su navedene u direktivi #include 2. Vrši supstituciju teksta prema direktivi #define 3. Vrši selekciju kôda, koji će se kompilirati, pomoću direktiva: #if, #elif, #else i

#endif

14.1 Direktiva #include Koriste se dvije varijante direktive #include:

#include <ime_datoteke> #include "ime_datoteke"

pomoću kojih se vrši umetanje neke datoteke u izvorni kôd, i to na mjestu gdje ja zapisana direktiva. Ako je ime_datoteke zapisano unutar navodnih znakova, traženje datoteke počinje u direktoriju gdje se nalazi izvorni kod, a ako nije tamo pronađena, ili ako je ime zapisano unutar < >, traženje datoteke se vrši u direktoriju u kojem su smještene datoteke s deklaracijama standardnih funkcija.

Ime datoteke može sadržavati potpuni ili djelomični opis staze do datoteke. Zapis ovisi o pravilima operativnog sustava. Primjerice, na Unix-u može biti oblik

#include "/usr/src/include/classZ.h" #include "../include/classZ.h"

ili na Windowsima

#include "C:\src\include\classZ.h" #include "..\include\classZ.h"

14.2 Direktiva #define za makro-supstitucije Leksička supstitucija teksta se definira pomoću direktive #define na dva načina. Prvi

način zapisa, koji se vrši prema pravilu:

Page 189: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

189

#define identifikator supstitucijski-tekst je već korišten za definiranje simboličkih konstante ili dijelova izvornog koda, primjerice

#define EPSILON 1.0e-6 #define PRINT_LINE printf("----------\n"); #define LONG_SUBSTITION if(a>b){ printf("a>b"); } \ else { prinf("a<=b"); }

Značenje ove direktive je da se u izvornom kodu, na mjestima gdje se je identifikator, umetne supstitucijski-tekst. Supstitucijski tekst se zapisuje u jednoj ili više linija. Znak nove linije označava kraj zapisa, ali ako je posljednji znak obrnuta kosa crta tada supstitucijskom tekstu pripada i zapis u narednoj liniji.

Direktiva za supstituciju teksta se često naziva makro supstitucija ili još kraće – makro.

Drugi način zapisa direktive #define omogućuje da se uz identifikator navede jedan ili

više makro argumenata, prema pravilu:

#define identifikator(argument, ... , argument) supstitucijski-tekst-s-argumentima

tako da supstitucijski tekst može biti različit za različita pozivanja makroa. Primjerice, makroi

#define KVADRAT(x) x*x #define POSTOTAK(x,y) x*100.0/y

se u izvornom kodu, ovisno o argumentima, supstituiraju na sljedeći način:

poziv makroa rezultira kodom y = KVADRAT(z); y = z*z; y = POSTOTAK(a,b); y = a*100.0/b; y = KVADRAT(a+b); y = a+b*a+b; y = POSTOTAK(a+b,c+d); y = a+b*100.0/c+d;

Dva zadnja primjera pokazuju najčešću grešku kod primjene makroa. Dobiveni kod ne odgovara namjeri da se dobije kvadrat vrijednosti (a/b) jer se zbog prioriteta operatora izraz (a+b*a+b) tretira kao (a+(b*a)+b), a ne kako je bila intencija programera, tj. kao ((a+b)*(a+b)). Zbog toga se preporučuje da se argumenti makroa u supstitucijskom tekstu uvijek pišu u zagradama, primjerice:

#define KVADRAT(x) ((x)*(x)) #define POSTOTAK(x,y) ((x)*100.0/(y))

Tada se ekspanzija makroa uvijek vrši s istim značajem, primjerice

poziv makroa rezultira kodom Y = KVADRAT(a+b) y = ((a+b)*(a+b)) Y = POSTOTAK(a+b,c+d) y = ((a+b)*100.0/(c+d))

Page 190: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

190

U definiciji makro naredbe prvi argument mora biti napisan neposredno iza zagrada, bez razmaka.

Makro argumente u supstitucijskom tekstu treba uvijek pisati unutar zagrada. Time se osigurava da će se ekspanzija makroa izvršiti s istim efektom za bilo koji oblik argumenata. Također, poželjno je i da se cijeli supstitucijski tekst zapiše u zagradama.

Poziv makroa izgleda kao poziv funkcije, ali nema značaj poziva funkcije (ne vrši se prijenos parametara funkcije), već se radi o supstituciji teksta prije procesa kompiliranja.

Simboli unutar supstitucijskog teksta mogu biti prethodno definirani makroi, primjerice

#define JEDAN 1 #define DVA (JEDAN +1) #define TRI (DVA +1) ....

Ekspanzija koju pretprocesor radi s izrazom JEDAN+DVA+TRI je 1+(1+1)+((1+1)+1), što daje vrijednost 6. Ovaj proračun se provodi tijekom kompiliranja.

14.3 String operatori # i ## Ekspanzija makro argumenata se ne vrši ako je argument u supstitucijskom tekstu zapisan u

navodnim znakovima, odnosno unutar stringa. Ponekad je potrebno da se neki argument u ekspanziji makroa pojavljuje literalno kao string. To se postiže tako da se u supstitucujskom tekstu nepesredno ispred imena argumenta napiše znak # (tzv. string operator). U ekspanziji makroa se tada taj argument pojavljuje zapisan unutar navodnih znakova. Primjerice, za definiciju

#define PRINT(x) printf(#x "=%g", x)

vrši se ekspanzija PRINT(max) u printf("max" "=%g", max), a pošto se dva uzastopna navodnika poništavaju, efekt je da se kompilira naredba printf("max=%g", max). Zadatak: Provjerite ispis sljedećeg programa:

/* program fun.c */ #include <stdio.h> #include <math.h> #define PRINT_FUN(fun, x) printf(#fun " = %g\n", fun ((x))) int main() { PRINT_FUN(sin, 3); PRINT_FUN(cos, 3); return 0; }

Ispis je:

Page 191: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

191

sin = 0.14112 cos = -0.989992

Operator ## djeluje na dva susjedna simbola na način da preprocesor izbaci taj operator i sva prazna mjesta, pa se dobije jedan novi simbol s identifikatorom sastavljenim od ta dva susjedna simbola. Primjerice, za definiciju

#define NIZ(ime, tip, n) tip ime ## _array_ ## tip[N]

vrši se ekspanzija

NIZ(x, int, 100) int x_array_int [100]

NIZ(vektor, double, N) double vektor_array_double [N]

Zadatak: Provjerite ispis sljedećeg programa:

/* program strop.c */ #include <stdio.h> #define N 10 #define PRINT(x) printf(#x "=%d\n", x) #define NIZ(ime, tip, n) tip ime ## _array [n] int main() { int i, y=7; NIZ(x, int, N); for(i=0; i<N;i++) x_array[i] = 10; PRINT(y); PRINT(x_array[y]); return 0; }

Ispis je:

y=7 x_array[y]=10

14.4 Direktiva #undef Ponekad je potrebno redefinirati značaj nekog identifikatora. U tu svrhu koristi se direktiva

#undef:

#undef identifikator Ovom se direktivom poništava prethodni značaj identifikatora. Primjerice,

#define SIMBOL_X VALUE_FOR_X .... .... /* područje gdje se koristi SIMBOL_X */ ....

Page 192: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

192

#undef SIMBOL_X .... .... /* ovdje se ne može koristiti SIMBOL_X */ .... #define SIMBOL_X NEW_VALUE_FOR_X .... .... /* ovdje ponovo može koristiti SIMBOL_X */ .... /* ali s nekim drugim značenjem */

Ako se pokuša redefinirati neki simbol bez prethodne #undef direktive, C kompilator će dojaviti grešku poput:

"Attempt to redefine macro SIMBOL_X"

14.5 Direktive za uvjetno kompiliranje Moguće je pomoću posebnih direktiva kontrolirati i sam proces pretprocesiranja. U tu svrhu

se koriste direktive: #if, #ifdef, #ifndef, #elif, #else i #endif. Iza direktiva #if i #elif (else-if) mogu se koristiti prosti izrazi koji rezultiraju

cjelobrojnom vrijednošću (ne smiju se koristiti sizeof i cast operatori te enum konstante). Ako je vrijednost tih izraza različita od nule, tada se u proces kompiliranja uključuje tekst koji slijedi iza ovih direktiva sve do slijedeće direktive (#elif, #else ili #endif).

U sljedećem primjeru pokazano je kako se pomoću ovih direktiva određuje koja će datoteka biti uključena u proces kompiliranja, ovisno o tipu operativnog sustava:

#if SISTEM == LINUX #define HDR "linx.h" #elif SISTEM == MSDOS #define HDR "msdos.h" #elif SISTEM == WIN32 #define HDR "win32.h" #else #define HDR "default.h" #endif #include HDR

Pretpostavljeno je da je prethodno definirana vrijednost simbola: LINUX, WIN32, MSDOS i SISTEM. Direktiva #elif ima značenje else if. Iza #if i #elif može se koristiti izraz

defined(identifikator)

koji daje 1 ako je identifikator prethodno registriran kao neki simbol (uključujući i makro simbole), inače je 0. Primjerice, da bi se osiguralo da se sadržaj neke datoteke može uključiti u izvorni kod samo jedan put, često se koriste direktive po sljedećem obrascu :

/* datoteka header.h */ #if !defined(HEADER_H) #define HEADER_H

Page 193: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

193

/* ovdje se zapisuje sadržaj od header.h */ #endif

Pri prvom uključenju ove datoteke definira se simbol HEADER_H i sadržaj datoteke se uljučuje u izvorni kod. Ako se ova datoteka uključi po drugi put, neće se koristiti njen sadržaj jer tada postoji definiran simbol HEADER_H.

Direktive #ifdef i #ifndef su specijalizirane za ispitivanje da li je neki identifikator definiran (ili nije definiran). Prethodni se primjer može zapisati i u obliku:

#ifndef _HEADER_H #define _HEADER_H /* ovdje se zapisuje sadržaj od header.h */ #endif

#ifdef identifikator je ekvivalentno #if defined(identifikator). #ifndef identifikator je ekvivalentno #if !defined(identifikator).

Iza svake #if, #ifdef ili #ifndef direktive mora biti #endif direktiva.

Page 194: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

194

15 Rad s datotekama i tokovima

Naglasci: • ulazno-izlazni tokovi • standardni tokovi • tekstualne i binarne datoteke • otvaranje i zatvaranje datotečnih tokova • formatirani tokovi • binarni tokovi • sekvencijalni i proizvoljni pristup datotekama • kopiranje, brisanje i promjena imena datoteka

15.1 Ulazno-izlazni tokovi U standardnoj je biblioteci implementiran niz funkcija koje na jedinstven način tretiraju sve

ulazno izlazne operacije: unos s tipkovnice, ispis na ekran te čitanje i pisanje informacija koje se pohranjuju na magnetskim i optičkim medijima. Komuniciranje s uređajima koji obavljaju ove operacije vrši se sekvencijalno bajt po bajt, a programski mehanizam kojim se vrši ovakvi prijenos informacije naziva se tok (eng. stream).

U jednom se programu može raditi s više tokova. Svakom toku se pridjeljuje jedna struktura podataka imena FILE, koja je definirana u <stdio.h>. Temeljna namjena te strukture je da služi kao memorijski ulazno/izlazni međuspremnik (eng. I/O buffer) pri prijenosu podataka. Važno je znati da se pri izlaznim operacijama podaci ne šalju direktno vanjskim uređajima, već se najprije upisuju o ovaj međuspremnik. Kada se on ispuni, tada se sadržaj cijelog međuspremnika šalje vanjskom uređaju. Na ovaj način se smanjuje broj pristupa disku i znatno ubrzava rad s datotekama. Sličnu namjenu ovaj međuspremnik ima i pri ulaznim operacijama.

Tokovi se dijele u četiri grupe: • standardni ulaz (vrši dobavu znakove tipkovnice) • standardni izlaz (vrši ispis na ekran) • standardna dojava greške (obično se vrši ispis na ekran) • datotečni tok (vrši čitanje ili pisanje podataka u datoteku)

Standardni ulaz, standardni izlaz i standardni tok dojave greške se samoinicijaliziraju pri pokretanju programa, a njihov pokazivač na strukturu FILE je u globalnim varijablama:

FILE *stdin; /* pokazivač toka standardnog ulaza */ FILE *stdout; /* pokazivač toka standardnog izlaza */ FILE *stderr; /* pokazivač toka dojave greške */

Ovi pokazivači su deklarirani u datoteci <stdio.h>. Iniciranje pokazivača datotečnih tokova mora obaviti sam programer. Kako se to radi bit će objašnjeno kasnije.

Page 195: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

195

Programer ne mora voditi računa o detaljima kako se izvršava ulazno/izlazni prijenos podataka. Ono što on mora znati je pokazivač toka, s kojim se komunicira, i funkcije pomoću kojih se ta komunikacija realizira. Pokazivača toka (FILE *) se mora navesti kao argument svake funkcije s kojom se vrše ulazno/izlazne operacije. Primjerice, za formatirani ispis podataka koristi se funkcija fprintf kojoj prototip glasi:

int fprintf(FILE *pTok, const char *format, ...);

gdje je pTok pokazivač toka, a tri točkice označavaju da se funkcija može koristiti s promjenjljivim brojem argumenata. Formatirani ispis se vrši prema obrascu koji se zapisuje u stringu format, na isti način kako se zapisuje format ispisa u printf()funkciji. Primjerice, za ispis stringa "Hello World" na standardnom izlazu može se koristiti naredba:

fprintf(stdout, "Hello World");

koja ima isti učinak kao naredba:

printf("Hello World");

Zapravo, printf() funkcija je interno realizirana kao fprintf() funkcija kojoj je pokazivač toka jednak stdout.

15.2 Binarne i tekstualne datoteke Informacije se u datoteke zapisuju u kodiranom obliku. Temeljna su dva načina kodiranog

zapisa: binarni i tekstualni (ili formatirani). Kaže se da je zapis izvršen u binarnu datoteku kada se informacije na disk zapisuju u istom binarnom obliku kako su kodirane u memoriji računala. U tekstualne datoteke se zapis vrši formatirano pomoću slijeda ASCII znakova, na isti način kako se vrši tekstualni ispis na video monitoru, primjerice printf() funkcijom. Sadržaj tekstualnih datoteka se može pregledati bilo kojim editorom teksta, dok sadržaj binarnih datoteka obično može razumjeti samo program koji ih je formirao.

Treba imati na umu da kada se u tekstualnu datoteku formatirano upisuje C-string, tada se ne zapisuje završni znak '\0'. Uobičajeno se zapis u tekstualne datoteke vrši u redovima teksta, na način da se za oznaku kraja linije koristi znak '\n'. Takovi zapis zovemo linija. Poželjno je da se ne unose linije koje sadrže više od 256 znakova, jer se time osigurava da će tekstualna datoteka biti ispravno očitana s gotovo svim programima koji manipuliraju s tekstualnim zapisom. Potrebno je napomenuti da se na MS-DOS računalima tekstualne datoteke zapisuju tako da se svaki znak '\n' (CR) pretvara u dva znaka "\r\n" (CR-LF), a kada se vrši očitavanje s diska tada se ""\r\n" prevodi u jedan znak '\n'. Ova se operacija obavlja na razini operativnog sustava. Programer o njoj ne mora voditi računa ukoliko koristi funkcije koje su u C jeziku predviđene za rad s tekstualnim datotekama.. Iznimka je slučaj kada se datoteka, koja je zapisana u tekst modu, tretira kao binarna datoteka. Na UNIX sustavima se ne vrši ova pretvorba.

Prema ANSI/ISO standardu u datoteka <stdio.h> sadrži prototipove funkcija za rad s datotekama. Neke od ovih funkcija predviđene su za rad s binarnim datotekama, a neke za rad s tekstualnim datotekama. Prije nego se opiše te funkcije najprije će biti pokazano kako se pristupa datotekama.

15.3 Pristup datotekama Svaka datoteka ima ime. Ime datoteke je spremljeno na disku kao tekstualni zapis u

posebnoj sekciji kataloga diska. Uz ime su zabilježeni i podaci o datoteci: vrijeme kada je

Page 196: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

196

spremljena, broj bajta koje datoteka zauzima, mjesto na disku gdje je spremljen sadržaj datoteke i atributi pristupa datoteci (read, write, hidden).

Da bi se moglo koristiti neku datoteku potrebno je od operativnog sustava zatražiti dozvolu pristupa toj datoteci. Taj proces se zove otvaranje datoteke. Isto tako se za kreiranje nove datoteke mora zatražiti dozvola od operativnog sustava. Tu funkciju obavlja standardna funkcija fopen(). Ona pored komunikacije s operativnim sustavom kreira datotečni tok koji sadrži memorijski međuspremnik za efikasno čitanje ili spremanje podataka na disk.

Prototip funkcije fopen() je:

FILE *fopen(const char *staza, const char *mod);

staza je string koji sadrži ime datoteke, s potpunim opisom staze direktorija, primjerice string :

char *filename = "c:\\data\\list.txt"; bi koristili na Windows računalima za otvoriti datoteku imena list.txt koja se nalazi na disku c: u direktoriju imena data. Napomenimo da se u imenu datoteke ne smiju koristiti znakovi : /, \, :, *, ?, ", <, > i |. Ako se zapiše samo ime datoteke, podrazumijeva se da se datoteka nalazi u tekućem direktoriju.

mod je string koji se opisuje način otvaranja datoteke. Zapisuje s jednim ili više znakova: r, w,

a i +, čije značenje je dano u tablici 15.1.

mod značenje "r" Otvori datoteku za čitanje (eng. read). Ako datoteka ne postoji fopen() vraća NULL.

"w" Otvori datoteku za pisanje (eng. write). Ako ne postoji datoteka zadanog imena kreira se nova datoteka. Ako postoji datoteka zadanog imena njen se sadržaj briše i kreira prazna datoteka (novi podaci će se zapisivati počevši od početka datoteke).

"a" Otvori datoteku za dopunu sadržaja (eng. append) . Ako ne postoji datoteka zadanog imena kreira se nova datoteka. Ako postoji datoteka zadanog imena, novi podaci će se dodavati na kraj datoteke.

"r+" Otvori datoteku za čitanje i pisanje . Ako ne postoji datoteka zadanog imena kreira se nova datoteka. Ako postoji datoteka zadanog imena njen se sadržaj briše i kreira prazna datoteka (novi podaci će se zapisivati od početka datoteke)..

"w+" Isto kao r+

"a+" Otvori datoteku za čitanje i dopunu . Ako ne postoji datoteka zadanog imena kreira se nova datoteka. Ako postoji datoteka zadanog imena u nju se vrši upis na kraju datoteke..

"b" Ako se iza slova w, r ili a još zapiše slovo 'b' to označava da se datoteku treba otvoriti u binarnom modu, inače se datoteka otvara u tekstualnom modu.

Tablica 15.1. Značaj znakova u stringu mod funkcije fopen()

Funkcija fopen() vraća pokazivač toka (FILE *). U slučaju greške vrijednost toga pokazivača je NULL. Najčešći uzrok greške pri otvaranju datoteke je:

• Neispravan zapis imena datoteke. • Neispravan zapis direktorija ili oznake diska. • Ne postoji direktorij zadana imena • Zahtjev da se otvori nepostojeća datoteka u modu čitanja – "r".

Page 197: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

197

Dobra je praksa da se uvijek provjeri da li je datoteka otvorena bez greške. Primjerice, za otvoriti datoteku imena "hello.txt" u modu pisanja, pogodan je slijed iskaza

FILE * fp; fp = fopen("hello.txt", "w"); if( fp == NULL) printf("Greska pri otvaranju datoteke");

Kada je datoteka otvorena, koristi je se kao tok. Primjerice, iskazima

fprintf( fp, "Hello World!\n"); fprintf( fp, "%s\n" "Hello World drugi put!");

u prethodno otvorenoj datoteci "hello.txt" biti će zapisane dvije linije teksta:

Hello World! Hello World drugi put;

Kada se završi rad s datotekom, treba zatvoriti datoteku. Što je to zatvaranje datoteke? Zatvaranje datoteke je postupak koji je nužno provesti kako bi svi podaci iz računala, koji se jednim dijelom nalaze u međuspremniku strukture FILE, bili spremljeni na disk, te da bi se ispravno zapisao podatak o veličini datoteke. Zatvaranje datoteke se vrši funkcijom fclose() čiji je prototip:

int fclose(FILE *fp);

Funkcija fclose() prima argument koji je pokazivač toka prethodno otvorene datoteke, a vraća vrijednost nula ako je proces zatvaranja uspješan ili EOF ako pri zatvaranju nastane pogreška. Ukoliko se ne zatvori datoteka pomoću ove funkcije, ona će biti prisilno zatvorena po završetku programa. Ipak se preporučuje da se uvijek zatvori datoteka čim se s njome završi rad, jer se time štede resursi operativnog sustava i osigurava od mogućeg gubitka podataka (primjerice, pri resetiranju računala dok je program aktivan, pri nestanku električnog napajanja, ili ako nastupi blokada programa).

U nekim će programima biti potrebno da datoteke budu otvorene cijelo vrijeme. U tom slučaju je zgodno koristiti funkciju fflush() kojom se forsira pražnjenje datotečnog međuspremnika i ažurira stanje datoteke na disku, bez zatvaranja datoteke. Prototip funkcije fflush() je

int fflush(FILE *fp);

funkcija prima argument koji je pokazivač toka prethodno otvorene datoteke, a vraća vrijednost nula ako je proces pražnjenja međuspremnika uspješan ili EOF ako pri zapisu podataka iz međuspremnika nastane greška. Funkcija fflush(stdin) s također često koristi za odstranjivanje viška znakova iz standardnog ulaza.

15.4 Formatirano pisanje podataka u datoteku Formatirano pisanje se vrši pomoći fprintf() funkcije. Pokažimo to sljedećim

programom:

/* Datoteka: txtfile-write.c */ /* Demonstrira se upis u tekstualnu datotke */

Page 198: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

198

/* 5 realnih brojeva, koje unosi korisnik */ #include <stdlib.h> #include <stdio.h> int main() { FILE *fp; float data[5]; int i; char filename[20]; puts("Otipkaj 5 realnih brojeva"); for (i = 0; i < 5; i++) scanf("%f", &data[i]); /* Dobavi ime datoteke, ali prethodno */ /* isprazni moguci višak znakova iz međuspremnika ulaza */ fflush(stdin); puts("Otipkaj ime datoteka:"); gets(filename); if ( (fp = fopen(filename, "w")) == NULL) { fprintf(stderr, "Greska pri otvaranju datoteke %s.", filename); exit(1); } /*Ispisi vrijednosti u datoteku i na standardni izlaz */ for (i = 0; i < 5; i++) { fprintf(fp, "\ndata[%d] = %f", i, data[i]); fprintf(stdout, "\ndata[%d] = %f", i, data[i]); } fclose(fp); printf("\nSada procitaj datoteku: %s, nekim editorom", filename); return(0); }

Izlaz iz programa je:

Otipkaj 5 realnih brojeva 3.14159 9.99 1.50 3. 1000.01 Otipkaj ime datoteke brojevi.txt data[0] = 3.141590 data[1] = 9.990000 data[2] = 1.500000 data[3] = 3.000000

Page 199: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

199

data[4] = 1000.010 Sada procitaj datoteku: brojevi.txt, nekim editorom

15.5 Formatirano čitanje podataka iz datoteke Za formatirano čitanje sadržaja datoteke koristi se fscanf() funkcija, koja je poopćeni

oblik scanf() funkcije za dobavu podataka iz ulaznih tokova. Prototip fscanf() funkcije je:

int fscanf(FILE *fp, const char *fmt, ...);

Parametar fp je pokazivač ulaznog toka, koji može biti stdout ili datotečni tok koji se dobije kada se datoteka otvori s atributom "r", "r+" ili "w+". String fmt služi za specifikaciju formata po kojem se učitava vrijednost varijabli, čije adrese se koriste kao argumenti funkcije. Tri točke označavaju proizvoljan broj argumenata, uz uvjet da svakom argumentu mora pripadati po jedan specifikator formata u stringu fmt. Primjer: Program txtfile-read.c čita sadržaj datoteke "input.txt", a zatim ga ispisuje na standardni izlaz. Prethodno je potrebno nekim editorom teksta formirati datoteka imena "input.txt", sa sljedećim sadržajem:

6.8 5.89 67 1.099010 67.001

/* Datoteka: txtfile-read.c */ /* Demonstrira se upis u tekstualnu datotke */ /* 5 realnih brojeva, koje unosu korisnik */ #include <stdio.h> int main() { float f1, f2, f3, f4, f5; FILE *fp; if ( (fp = fopen("INPUT.TXT", "r")) == NULL) { fprintf(stderr, "Greska pri otvaranju datoteke.\n"); exit(1); } fscanf(fp, "%f %f %f %f %f", &f1, &f2, &f3, &f4, &f5); fprintf(stdout, "Vrijednosti u datoteci su:\n"); fprintf(stdout, "%f, %f, %f, %f, %f\n.", f1, f2, f3, f4, f5); fclose(fp); return(0); }

Ispis je: Vrijednosti u datoteci su: 6.800000, 5.890000, 67.000000, 1.099010, 67.000999

Page 200: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

200

Uočite da je posljednji broj u datoteci 67.0001 pročitan kao 67.000999. Očito je da se u pretvorbi koja se vrši pri formatiranom unosu gubi na točnosti.

Funkcija fscanf() je pogodna za formatirani unos brojeva, ali nije pogodna za unos stringova i znakova. To je pokazano u prethodnim poglavljima, kada je analizirana upotreba funkcije scanf().

Dalje će biti opisane funkcije koje omogućuju dobavu znakova i linija teksta.

15.6 Znakovni ulaz/izlaz U pristupu datotekama pojam znakovni ulaz/izlaz se koristi za transfer pojedinačnog znaka

ili jedne linije (linija je nula ili više znakova zaključenih s znakom nove linije). Znakovni ulaz/izlaz se uglavnom koristi s tekstualnim datotekama.

Znakovni ulaz – getc, ungetc, fgetc, fgets

Za dobavu znakova koriste se funkcije getc() i fgetc(), a za dobavu linije koristi se funkcija fgets(). Deklarirani su u <stdio.h>. Prototip funkcija za dobavu znaka je:

int getc(FILE *fp); int fgetc(FILE *fp);

Obje funkcije obavljaju s tokom fp iste operaciju kao funkcija fgetchar() sa standardnim ulazom, na način da vraćaju trenutni ulazni znak, ili EOF ako je greška ili kraj datoteke. Prototip funkcije za dobavu linije je:

char *fgets(char *str, int n, FILE *fp);

Parametar str je pokazivač gdje će biti prihvaćen string, n je maksimalni broj znakova koji string prihvaća (uključujući i nul znak), a fp je pokazivač toka. Znakovi se uzimaju iz toka sve do pojave znaka nove linije ili dok se ne prenese n-1 znakova (fgets() postavlja n-ti znak '\0'). Ako je transfer uspješan fgets() vraća pokazivač str, a ako je transfer neuspješan ili ako je detektiran EOF vraća NULL. Ukoliko greška ili EOF nastupi nakon transfera prvog znaka, str više nije pogodan za upotrebu jer nije ispravno zaključen s nul znaka. Obično se ova funkcija koristi za dobavu linije teksta. Preporuka je da se uvijek alocira dovoljno memorije za string, kako be se učitala čitava linija.

Ponekad je pri dobavi znaka potrebno vratiti taj znak u tok. U tu svrhu se može koristiti funkcija

int ungetc(int c , FILE *fp);

ungetc() je korisna pri leksičkoj analizi. Primjerice, ako se iz toka dobavlja niz znamenki može ih se pridijeliti nekom broju sve dok se ne pojavi znak koji nije znamenka. Tada je zgodno vratiti taj znak u tok kako bi bio na raspolaganju u nekoj drugoj operaciji. Primjerice, sljedeći iskazi rezultiraju dobavom cijelog broja iz tekstualnog ulaznog toka:

int n = 0; int c; while((c = getc(fp)) >= '0' && c <= '9') n = 10 * n + (c - '0'); ungetc(c, fp); /* nije znamenka – vrati znak u ulazni tok */

Page 201: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

201

printf("%d", n);

Ovim se mehanizmom ne smije vraćati više znakova u tok. Garantira se uspješno vraćanje samo jednog znaka. Ukoliko se ne može izvršiti ova operacija funkcija ungetc() vraća EOF, a ako je operacija uspješna funkcija ungetc() vraća znak c.

Znakovni izlaz – putc, fputs

Za znakovni izlaz se mogu koristiti dvije funkcije; putc() i fputs(). Funkcija putc() je ekvivalentna funkciju putchar() kada se komunicira sa standardnim izlazom. Prototip te funkcije je:

int putc(int ch, FILE *fp);

Funkcija koristi dva argumenta; ch je znak koji se zapisuje, a fp je pokazivač izlaznog toka. Iako je ch deklariran kao int uzima se samo donji bajt. Funkcija vraća znak koji je zapisan u tok ili EOF ako nastupi pogreška. Za zapis stringa u izlazni tok koristi se funkcija fputs(), kojoj prototip glasi:

int fputs(char *str, FILE *fp);

Koriste se dva argumenta: str je pokazivač na string, a fp je pokazivač izlaznog toka. Ova funkcija zapisuje string bez zaključnog nul znaka. Ako je transfer uspješan vraća pozitivnu vrijednost, a ako je transfer neuspješan vraća EOF. Za ispis stringa na standardni izlaz do sada je korištena funkcija

int puts(char *str);

koja je uvije dodavala znak nove linije. To je različito od djelovanja fputs(str, stdout), koja ne dodaje znak nove linije.

Znakovni prijenos kod binarnih datoteka

Za direktan upis u binarne datoteke, bez ikakvog posrednog formatiranja, također se mogu koristiti funkcije putc() i fgetc() ali se tada ne vrijedi da se vrijednost EOF (-1) koristi za dojavu greške, jer je kod binarnih datoteka to regularni simbol.

Za detektiranje kraja datoteke predviđena je posebna funkcija

int feof(FILE *fp);

koja vraća vrijednost različitu od nule ako je dosegnut kraj datoteke. Nakon toga više nije moguće čitanje iz datoteke.

Primjer: kopiranje datoteka

Kopiranje sadržaja jedne datoteke u drugu datoteku provodi se prema sljedećem algoritmu: 1. Otvori izvornu datoteku u binarnom modu (tj. datoteku iz koje se kopira) 2. Otvori odredišnu datoteku u binarnom modu (tj. datoteku u koju se kopira) 3. Učitaj znak iz izvorne datoteke 4. Ako funkcija feof() vrati vrijednost različitu od nule (nije dosegnut kraj datoteke)

tada upiši znak u odredišnu datoteku i vrati se na korak 3. 5. Ako funkcija feof() vrati 0 to znači da je dosegnut kraj datoteke. U tom slučaju

zatvori obje datoteke. Ovaj algoritam je implementiran u funkciji

Page 202: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

202

int kopiraj_datoteke( char *ime_izvora, char *ime_odredista ); Funkcija prima dva argumenta koji označavaju imena izvorne i odredišne datoteke. Vraća 0 ako nije izvršeno kopiranje ili 1 ako je kopiranje uspješno. Implementacija funkcije je:

int kopiraj_datoteke( char *ime_izvora, char *ime_odredista ) { FILE *fpi, *fpo; /* Otvori datoteku za čitanje u binarnom modu */ if (fpi = fopen( ime_izvora, "rb" ) == NULL ) return 0; /* Otvori datoteku za pisanje u binarnom modu */ if ( fpo = fopen( ime_odredista, "wb" ) == NULL ) { fclose ( fpi ); return 0; } /* Učitaj 1 bajt iz fpi. Ako nije eof, upiši bajt u fpo */ while (1) { char c = fgetc( fpi ); if ( !feof( fpi ) ) fputc( c, fpo ); else break; } fclose ( fpi); fclose ( fpo); return 1; }

Testiranje ove funkcije se provodi programom kopiraj.c u kojem se ime izvorne i odredišne datoteke dobavlja s komandne linije.

/* Datoteka: kopiraj.c * kopira datoteku ime1 u datoteku ime2, komandom * c:> kopiraj ime1 ime2 */ #include <stdio.h> int kopiraj_datoteke( char *ime_izvora, char *ime_odredista ); int main(int argc, char **argv) { char *ime1, *ime2; if (argc <3) /* moraju biti dva argumenta komandne linije */ { printf("Uputstvo: kopiraj ime1 ime2\n"); return 1; } ime1 = argv[1]; ime1 = argv[2]; if( kopiraj_datoteke(ime1, ime1 ) )

Page 203: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

203

puts("Kopiranje zavrseno uspjesno\n"); else puts("Kopiranje neuspjesno!\n"); return(0); }

U radu s binarnim datotekama za detekciju kraja datoteke isključivo se koristi funkcija feof(), dok se u radu s tekstualnim datotekama kraj datoteke može detektirati i kada funkcije vrate EOF (obično je to vrijednost -1).

15.7 Direktni ulaz/izlaz za memorijske objekte Najefikasniji i najbrži način pohrane bilo kojeg složenog memorijskog objekta je da se

zapisuje u binarne datoteke. Na taj način datoteka sadrži sliku memorijskog objekta, pa se isti može na najbrži mogući način prenijeti iz datoteke u memoriju.

Za ovakvi tip ulazno izlaznih operacija predviđene su dvije funkcije: fwrite() i fread().

fwrite

Funkcija fwrite() služi za zapis u datoteku proizvoljnog broja bajta s neke memorijske lokacije. Prototip funkcije je:

int fwrite(void *buf, int size, int count, FILE *fp);

Argument buf je pokazivač na memorijsku lokaciju s koje se podaci zapisuju u tok fp. Argument size označava veličinu u bajtima pojedinog elementa koji se upisuje, a argument count označava ukupni broj takovih elemenata koji se zapisuju. Primjerice, ako je potrebno zapisati 100 elemenata niza cijelih brojeva, tada je size jednak 4 (sizeof int), a count je jednako 100. Dakle, ukupno se zapisuje 400 bajta. Funkcija vraća vrijednost koja je jednaka broju elemenata koji su uspješno zapisani. Ako je ta vrijednost različita od count, to znači da je nastala pogreška. Uobičajeno je da se pri svakom transferu vrši ispitivanje ispravnog transfera iskazom:

if( (fwrite(buf, size, count, fp)) != count) fprintf(stderr, "Error writing to file.");

Primjeri korištenja funkcije fwrite(): 1. zapis skalarne varijable x, koja je tipa float, vrši se sa:

fwrite(&x, sizeof(float), 1, fp);

2. zapis niza od 50 elemenata strukturnog tipa, primjerice

struct tocka {int x, int y;} niz[50];

vrši se iskazom:

fwrite(niz, sizeof(struct tocka), 50, fp); ili fwrite(niz, sizeof(niz), 1, fp);

U drugom slučaju čitav se niz tretira kao jedan element, a učinak je isti kao u prvom iskazu.

Page 204: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

204

fread

Funkcija fread() služi za učitavanje proizvoljnog broja bajta na neku memorijsku lokaciju. Prototip funkcije je:

int fread(void *buf, int size, int count, FILE *fp);

Argument buf je pokazivač na memorijsku lokaciju u koju se upisuju podaci iz toka fp. Argument size označava veličinu u bajtima pojedinog elementa koji se učitava, a argument count označava ukupni broj takovih elemenata koji se učitavaju u memoriju. Funkcija vraća vrijednost koja je jednaka broju elemenata koji su uspješno učitani. Ako je ta vrijednost različita od count, to znači da je nastala greška ili je dosegnut kraj datoteke. Primjer: U programu binio.c niz od 10 cijelih brojeva prvo se zapisuje u binarnu datoteku imena "podaci", a zatim se taj niz ponovo učitava u binarnom obliku.

/*Datoteka: binio.c */ #include <stdlib.h> #include <stdio.h> #define SIZE 10

void prekini(char *s) { fprintf(stderr, s);

exit(1); } int main() { int i, niz1[SIZE], niz2[SIZE]; FILE *fp; for (i = 0; i < SIZE; i++) niz1[i] = 7 * i; /* otvori datoteku za pisanje u binarnom modu*/ if ( (fp = fopen("podaci", "wb")) == NULL) prekini("Greška pri otvaranju datoteke"); /* Spremi niz1 u datoteku */ if (fwrite(niz1, sizeof(int), SIZE, fp) != SIZE) prekini("Greška pri pisanju u datoteku"); /* Zatvori datoteku */ fclose(fp); /* Ponovo otvori datoteku za čitanje*/ if ( (fp = fopen("podaci", "rb")) == NULL) prekini("Greška pri otvaranju datoteke"); /* Čitaj iz datoteke u niz2 */ if (fread(niz2, sizeof(int), SIZE, fp) != SIZE) prekini("Greška pri citanju datoteke"); fclose(fp); /* Sada ispisi oba niza i provjeri da li su jednaka*/

Page 205: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

205

for (i = 0; i < SIZE; i++) printf("%d\t%d\n", niz1[i], niz2[i]); return(0); }

Dobije se ispis:

0 0 7 7 14 14 21 21 28 28 35 35 42 42 49 49 56 56 63 63

Prednost zapisa u binarnom modu u odnosu na formatirani zapis nije samu u efikasnom korištenju resursa i brzini rada. U binarnom modu nema gubitka informacije (smanjenja točnosti numeričkih zapisa) koje se javljaju u formatiranom zapisu.

Nije preporučljivo direktno zapisivanje u datoteku struktura koji sadrže pokazivačke članove, jer pri ponovnom učitavanju takovih struktura pokazivači neće sadržavati ispravne adrese.

Funkcije rewind() i ftell()

U prethodnom programu bilo je potrebno dva puta otvoriti i zatvoriti datoteku istog imena. U oba slučaja se čitanje/pisanje vršilo od početka datoteke. U slučaju kada se s nekom datotekom vrši čitanje i pisanje ona se može otvoriti u modu "w+b". Da bi ovi procesi startali od početka datoteke tada je potrebno koristiti funkciju rewind() kojom se mjesto pristupa datoteci postavlja na početak datoteke. Prototip ove funkcije glasi:

void rewind(FILE *fp);

Uvijek se može odrediti mjesto na kojem će biti izvršen slijedeći pristup datoteci. To se postiže funkcijom ftell() kojoj je prototip:

long ftell(FILE *fp);

Funkcija ftell() vraća cjelobrojnu vrijednost koja odgovara poziciji (u bajtima) slijedećeg pristupa datoteci. U slučaju pogreške funkcija vraća vrijednost -1L. Primjer: Program binio1.c ima isti učinak kao i program binio.c, ali se koristi mod "w+b" i funkcija rewind(). Također, demonstrira se upotreba funkcije ftell().

/*Datoteka: binio1.c */ #include <stdlib.h> #include <stdio.h> #define SIZE 10

Page 206: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

206

void prekini(char *s) { fprintf(stderr, s); exit(1); } int main() { int i, niz1[SIZE], niz2[SIZE]; FILE *fp; for (i = 0; i < SIZE; i++) niz1[i] = 7 * i; /* Otvori datoteku za čitanje i pisanje*/ if ( (fp = fopen("podaci", "w+b")) == NULL) prekini("Greška pri otvaranju datoteke"); /* Spremi niz1 u datoteku */ if (fwrite(niz1, sizeof(int), SIZE, fp) != SIZE) prekini("Greška pri pisanju u datoteku"); /* pomoću ftell() izvijesti o broju bajta u datoteci */ printf("U datoteci je zapisano %d bajta\n", ftell(fp)); /* vrati poziciju pristupa datoteci na pocetak */ rewind(fp); /* Čitaj iz datoteke u niz2 */ if (fread(niz2, sizeof(int), SIZE, fp) != SIZE) prekini("Greška pri citanju datoteke"); fclose(fp); /* Sada ispisi oba niza i provjeri da li su jednaka*/ for (i = 0; i < SIZE; i++) printf("%d\t%d\n", niz1[i], niz2[i]); return(0); }

15.8 Sekvencijani i proizvoljni pristup datotekama Sekvencijalni pristup datoteci označava operacije s datotekama u kojima se čitanje ili

pisanje uvijek vrši na kraju datoteke. Proizvoljni ili slučajni pristup datoteci (random access) označava operacije s datotekama u kojima se čitanje ili pisanje može usmjeriti na proizvoljno mjesto u datoteci.

Svakoj se otvorenoj datoteci dodjeljuje jedan pozicijski indikator koji označava poziciju (u bajtima) od početka datoteke na kojoj će biti izvršeno čitanje ili pisanje. U svim dosadašnjim primjerima korišten je sekvencijalni pristup datoteci. U tom slučaju, nakon otvaranja datoteke pozicijski indikator ima vrijednost 0, a kada se datoteka zatvori pozicijski indikator ima vrijednost koja je jednaka broju bajta koji su zapisani u datoteci, ukoliko je posljednja operacija bila pisanje u datoteku.

Proizvoljni pristup datoteci ima smisla samo kod binarnih datoteka, kod kojih se čitanje i pisanje vrši kontrolirano bajt po bajt. On se ostvaruje pomoću funkcije fseek().

fseek

Funkcija fseek() služi za proizvoljno postavljanje pozicijskog indikatora datoteke. Deklarirana je u datoteci <stdio.h> prototipom:

Page 207: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

207

int fseek(FILE *fp, long pomak, int seek_start);

Prvi argument je pokazivač toka. Drugi argument određuje pomak pozicijskog indikatora, a treći argument određuje od koje startne pozicije se vrši pomak pozicijskog indikatora. Ova startna pozicija se određuje pomoću tri konstante koje su definirane u <stdio.h>, a njihov značaj je opisan u tablici:

Konstanta Vrijednost Značaj vrijednosti seek_start

SEEK_SET 0 Pomak se vrši od početka datoteke prema kraju datoteke.

SEEK_CUR 1 Pomak se vrši od trenutne pozicije prema kraju datoteke.

SEEK_END 2 Pomak se vrši od kraja datoteke prema početku datoteke.

Funkcija fseek() vraća vrijednost 0 ako je operacija uspješna, a ako je operacija neuspješna vraća vrijednost različitu od nule. Uočite:

fseek(fp,0, SEEK_SET)) je jednako rewind(fp). Veličinu datoteke u bajtima se može dobiti naredbama:

fseek(fp, 0, SEEK_END); size = ftell(fp);

Primjer: U programu seek.c generira se datoteka "random.dat" s 50 slučajnih cijelih brojeva. Zatim se po proizvoljnom redoslijedu čita vrijednosti iz te datoteke. Redoslijed bira korisnik tako da unosi indeks elementa. Program završava kada korisnik unese negativnu vrijednost.

/* Program fseek.c */ #include <stdlib.h> #include <stdio.h> #define MAX 50 int main() { FILE *fp; int data, i, niz[MAX]; long pomak; /* Inicijaliziraj niz od 50 elemenata po slucajnom uzorku */ for (i = 0; i < MAX; i++) niz[i] = rand(); /* Otvori binarnu datoteku RANDOM.DAT za čitanje i pisanje. */ if ( (fp = fopen("RANDOM.DAT", "w+b")) == NULL) { fprintf(stderr, "\nGreska pri otvaranje datoteke"); exit(1); } /* upiši niz */ if ( (fwrite(niz, sizeof(int), MAX, fp)) != MAX) { fprintf(stderr, "\nGreska pisanja u datoteku"); exit(1);

Page 208: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

208

} /* Pitaj korisnika koji element zeli ucitati, */ /* zavrsi ako se unese negativna vrijednost */ while (1) { printf("\nIzaberi element datoteke: 0-%d ili -1 za kraj:", MAX-1); scanf("%ld", &pomak); if (pomak < 0) break; else if (pomak > MAX-1) continue; /* Postavi pozicijski indikator datoteke */ if ( (fseek(fp, (pomak*sizeof(int)), SEEK_SET)) != 0) { fprintf(stderr, "\nGreska fseek()."); exit(1); } /* Zatim učitaj element i prikazi njegovu vrijednost. */ fread(&data, sizeof(int), 1, fp); printf("\nElement %ld ima vrijednost %d.", pomak, data); } /* zatvori datoteku */ fclose(fp); return(0); }

15.9 Funkcije za održavanje datoteka Temeljne operacije za održavanje datoteka su brisanje datoteka, promjena imena datoteka i kopiranje datoteka. Prije je definirana funkciju za kopiranje datoteke. Za brisanje datoteke koristi se funkcija remove(), a za promjenu imena datoteke koristi se funkcija rename(). Ove funkcije su deklarirane u datoteci <stdio.h>.

remove

Prototipe funkcije remove(), kojom se briše datoteka glasi:

int remove( const char *imedatoteke);

Funkcija kao argument prima pokazivač stringa koji sadrži ime datoteke (uključujući i stazu) koju treba izbrisati. Operacija se može izvesti samo ako ta datoteka nije otvorena. Funkcija vraća vrijednost 0 ako je operacija brisanja uspješna, a ako je operacija neuspješna vraća vrijednost -1. Razlog neuspješnog brisanja može biti kada datoteka ne postoji ili kada je spremljena s atributom read-only.

rename

Prototipe funkcije rename(), kojom se mijenja ime datoteke glasi:

int rename( const char *ime, const char *novo_ime );

Funkcija prima dva argumenta – pokazivače na string - prvi je ime datoteke, a drugi novo ime za datoteku. Operacija se može izvesti samo ako ta datoteka nije otvorena. Funkcija vraća

Page 209: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

209

vrijednost 0 ako je operacija uspješna, a ako je operacija neuspješna vraća vrijednost -1. Razlog neuspješne operacije može biti:

• ne postoji datoteka ime • već postoji datoteka s imenom novo_ime • zadano je novo_ime s drugom oznakom diska

Privremene datoteke

Ponekad je potrebno formirati tzv. privremenu datoteku koja će poslužiti za smještaj podataka koji se vrši samo za vrijeme izvršenja programa. Tada nije bitno kako se datoteka zove, jer se nju treba izbrisati prije nego završi program. Za formiranje imena privremene datoteke može se koristiti funkcija tmpname(), koja je deklarirana u <stdio.h>. Prototip joj je:

char *tmpnam(char *s);

Argument funkcije je pokazivač stringa koji pokazuje na memoriju koja je dovoljna za smještaj imena datoteke. Ako je taj pokazivač jednak NULL tada funkcija tmpname() koristi vlastiti statički spremnik u kojem generira neko ime. Funkcija tada vraća pokazivač na taj spremnik. Način formiranja imena privremene datoteke je određen na način koji osigurava da se u jednom programu ne mogu pojaviti dva ista imena za privremenu datoteku. Primjer: u programu tmpname.c demonstrira se korištenje tzv. privremenih datoteka

/* Datoteka: tmpname.c * Formiranje privremenih datoteka */ #include <stdio.h> int main() { char ime[80]; FILE *tmp; /* Formiraj privremenu datorteku */ tmpnam(ime); tmp = fopen(ime, "w"); if(tmp != NULL) { /* koristi datoteku */ printf("Formirana datoteka imena: %s\n", ime); /* nakon koristenja zatvori datoteku **/ fclose(tmp); /* i izbrisi je s diska */ remove(ime); } }

Dobije se ispis:

Formirana datoteka imena: \s3a4.

Page 210: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

210

16 Apstraktni tipovi podataka - ADT

Naglasci: • Apstraktni dinamički tip podataka -ADT • Model, specifikacija, implementacija i aplikacija ADT-a • ADT STACK za rad sa stogom podataka • Proračun aritmetičkog izraza postfiksne notacije • ADT QUEUE za rad s redom a čekanje

16.1 Koncept apstraktnog dinamičkog tipa podataka Pomoću struktura, pokazivača i funkcija mogu se realizirati apstraktni dinamički tipovi podataka (ADT – eng. abstract data type). Mi smo, na neki način, već do sada koristili apstraktne tipove. Primjerice, int, char ili float apstraktno označavaju karakteristike nekog memorijskog objekta i operacije koje se mogu izvršavati s tim objektom. Pošto smo se na te tipove navikli, oni su u našem mentalnom sklopu postali "konkretni" primitivni tipovi C jezika. Sada će ideja tipa biti proširena i na druge objekte apstrakcije, tako da apstraktni tip predstavlja oznaku za skup objekata koji se ponašaju u skladu s definiranim operacijama. Na slici 16.1 prikazan je konceptualni model za rad s apstraktnim tipom podataka. Čini ga:

1. Model

Prvi stupanj definiranja ADT-a je izrada modela podataka i operacija kojima se opisuje objekt apstrakcije, neovisno o načinu kako će biti implementiran u C jeziku. Model se opisuje algoritamskim zapisima i matematičkom aksiomatikom operacija.

2. Specifikacija

Na temelju tog modela izrađuje se specifikacija u C jeziku koja mora sadržavati: • identifikator tipa kojim se označava ADT,

• prototip funkcija kojima se realizira model operacija s apstraktnim objektom,

• uz funkcije treba jasno dokumentirati koji su uvjeti za primjenu funkcije (eng. precondition) i stanje objekta nakon djelovanja funkcije (eng. postcondition).

Specifikacija se zapisuje u "*.h" datoteci. Ona predstavlja sučelje prema aplikaciji.

3. Implementacija

Na temelju specifikacije vrši se implementacija modela, odnosno definiranje potrebnih C funkcija i struktura podataka. Implementacija se zapisuje u jednoj ili više datoteka kao samostalni modul koji se može kompilirati neovisno od programa u kojem se koristi.

4. Aplikacija

Korisnik upotrebljava ADT modul na temelju opisa koji je dan specifikacijom, i ne zanima ga kako je izvršena programska implementacija modela.

Page 211: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

211

Objekt apstrakcije

Izrada programa Izvršni program

ADT - model podataka i operacija kojima se opisuje objekt apstrakcije

Sučelje s aplikacijom: - oznaka tipa ADT-a - specifikacija funkcija i uvjeta za primjenu ADT-a

Implementacija funkcija i struktura podataka potrebnih za realizaciju ADT-a

Slika 16.1. Konceptualni model ADT-a

Kako se realizira apstraktni tip bit će najprije pokazano na primjeru apstraktnog objekta brojača. Primjer programske realizacije objekta brojača već je prije opisan u lekciji o modularnom programiranju. Tada je model rada brojača bio sljedeći - stanje objekta brojača opisivale su dvije statičke varijable: count (koja pokazuje izbroj) i mod (koja određuje modul brojača), a operacije s brojačem bile su reset_counter(), incr_count(), get_count(), get_modul(). Nedostatak te - statičke - realizacije brojača je u tome što u jednom programu omogućuje postojanje samo jednog apstraktnog objekta brojača. Sada će biti pokazano kako se programski može omogućiti višestruka pojavnost objekta brojača.

Stanje apstraktnog objekta brojača bit će opisano strukturom _counter, pomoću koje se definira i tip COUNTER, koji označava pokazivač na ovu strukturu;

struct _counter { int count; int mod; }; typedef struct _counter *COUNTER;

Zapis velikim slovima je izvršen iz razloga da podsjeti kako se radi o pokazivačkom tipu. Ime COUNTER se dalje koristi kao oznaku tipa ADT-a brojača. Pojavnost (instancu) objekta tipa COUNTER, određuju dvije funkcije: new_counter(), koja vrši dinamičko alociranje objekta brojača i inicira varijable tipa COUNTER, te delete_counter(), koja dealocira objekt brojača.

Pošto je model brojača poznat, sada slijedi opis specifikacije ADT-a COUNTER (u datoteci

"counter-adt.h"). Uz svaki prototip funkcije u komentaru su opisani: namjena funkcije, argumenti i vrijednost koju funkcija vraća, uvjeti koji moraju biti zadovoljeni za primjenu funkcije (PRE) i stanje nakon primjene funkcije (POST).

Page 212: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

212

/* Datoteka: counter-adt.h Specifikacija ADT brojača po modulu: mod */ typedef struct _counter *COUNTER; COUNTER new_counter(int mod); /* Funkcija: alocira i inicijalizira novi objekt tipa COUNTER * vraca pokazivač tipa COUNTER * Argumenti: mod je modul brojača * POST: brojač na nuli, a modul brojaca na vrijednost mod. * Ako je mod<=1, modul se postavlja na vrijednost INT_MAX */ void delete_counter(COUNTER pc); /* Funkcija: dealocira objekt ADT brojaca * PRE: pc != NULL * POST: pc==NULL */ void reset_counter(COUNTER pc, int mod); /* Funkcija: resetira stanje brojača * PRE: pc != NULL * POST: brojač na nuli, a modul brojača ima vrijednost mod. * Ako je mod<=1, modul se postavlja na vrijednost INT_MAX */ int incr_count(COUNTER pc); /* Funkcija: inkrementira brojac u intervalu 0..mod-1 * PRE: pc != NULL * POST: vrijednost brojača inkrementirana (u intervalu 0..mod-1) */ int get_count(COUNTER pc); /* Funkcija: vraca trenutnu vrijednost brojača * PRE: pc != NULL */ int get_modul(COUNTER pc); /* Funkcija: vraca vrijednost modula brojača * PRE: pc != NULL */

Na temelju specifikacije vrši se implementacija modula ADT-a. Uočimo da se u specifikaciji funkcija pojavljuje preduvjet PRE: pc != NULL. Ovaj će preduvjet biti uvijek ispunjen ako se pri inicijalizaciji objekta brojača ispita vrijednost pokazivača na objekt, primjerice

COUNTER pc = new_counter(0); /* inicijalizirara brojač pc s mod=INT_MAX*/ if(pc == NULL) exit(1); /* ako je p==NULL prekini program */

Tjekom razvoja modula poželjno je provjeravati ovaj preduvjet u svakoj funkciji. U C jeziku, prema ANSI standardu, u datoteci <assert.h> definirana je makro naredba:

assert( uvjet )

Page 213: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

213

kojom se može provjeravati da li je neki uvjet ispunjen. Ako uvjet nije ispunjen, prekida se program i izvještava u kojoj datoteci i u kojem retku izvornog koda je došlo do greške. Pošto ovo ispitivanje usporava program, predviđeno je da se ova provjera može isključiti. Ako ne želimo da se vrši ova provjera tada se u komandnoj liniji kompilatora treba definirati simbol NDEBUG, primjerice:

c:> cl /D"NDEBUG" ime_datoteke.c

ili u izvornom kodu ispred direktive #include <assert.h> treba definirati simbol NDEBUG, tj.

#define NDEBUG 1 #include<assert.h>

U implementaciji brojača koristit će se makro naredba assert(pc != NULL), i to u svim funkcijama koje kao argument imaju pokazivač pc. Slijedi opis implementacije:

/* Datoteka: counter-adt.c * Implementacija ADT brojača po modulu mod */ #include <limits.h> /* zbog definicija INT_MAX*/ #include <stdlib.h> /* zbog definicija malloc i free*/ #include <assert.h> #include "counter-adt.h" typedef struct _counter { int count; int mod; } counter; /* typedef struct _counter *COUNTER; definiran u counter-adt.h */ COUNTER new_counter(int mod) { COUNTER pc = malloc(sizeof(counter)); if(pc != NULL) { if(mod <= 1) mod = INT_MAX; pc->mod = mod; pc->count=0; } return pc; } void delete_counter(COUNTER pc) { assert(pc != NULL); free(pc); } void reset_counter(COUNTER pc, int mod) { assert(pc != NULL); if(mod <= 1) mod = INT_MAX; pc->mod = mod; pc->count=0; } int incr_count(COUNTER pc) { assert(pc != NULL); pc->count++; if(pc->count >= pc->mod) pc->count = 0; return pc->count;

Page 214: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

214

} int get_count(COUNTER pc) { assert(pc != NULL); return pc->count; } int get_modul(COUNTER pc) { assert(pc != NULL); return pc->mod; }

Modul ADT-a se može testirati programom testcount-adt.c u kojem se inicijaliziraju dva neovisna objekta brojača pc1 i pc2. Prvi na mod=5, a drugi na mod=INT_MAX.

/* Datoteka: testcount-adt.c */ #include <stdio.h> #include "counter-adt.h" int main(void) { int i; COUNTER pc1 = new_counter(5); COUNTER pc2 = new_counter(0); if(pc1 == NULL || pc2 == NULL) exit(1); printf("brojac(mod=%d), brojac(mod=%d)\n", get_modul(pc1),get_modul(pc2)); for(i=0; i<=10; i++) { incr_count(pc1); incr_count(pc2); printf("%d\t\t %d\n", get_count(pc1),get_count(pc2)); } printf("itd........\n"); delete_counter(pc1); delete_counter(pc2); return 0; }

Nakon izvršenja ovog programa dobije se ispis:

brojac(mod=5), brojac(mod=2147483647) 1 1 2 2 3 3 4 4 0 5 1 6 2 7 3 8 4 9 0 10 1 11 itd........

Page 215: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

215

Na kraju razmatranja važno je uočiti da u specifikaciji (counter-adt.h) nije navedena struktura podataka koja služi za implementaciji objekta brojača. Deklariran je samo pokazivač na "neku" strukturu - COUNTER. Kako izgleda ta struktura važno je implementatoru ADT-a, a ne onome tko ga koristi. Na ovaj način se ostvaruje princip enkapsulacije – skrivanja detalja implementacije od korisnika modula. Početnicima ovaj princip nema posebno značenje, ali iskusnim programerima on znači jednu od temeljnih paradigmi modernog programiranja. Enkapsulacija olakšava timski rad i doradu modula ADT-a, bez obzira u kojoj će aplikaciji biti primijenjen.

16.2 Stog i STACK ADT Stog je naziv za kolekciju podataka kojoj se pristupa po principu LIFO – last in first out.

Primjerice, kada slažemo tanjure tada stavljamo jedan tanjur poviše drugog – tu operaciju zovemo push(), a kada uzimamo tanjur tada uvijek uzimamo onaj tanjur kojeg smo posljednjeg stavili u stog – tu operaciju nazivamo pop(). Pored ove dvije temeljne operacije, obično se u pristupu stogu koriste još dvije operacije: top() – vraća vrijednost elementa koji je na vrhu stoga i empty() – vraća 1 ako je stog prazan, inače vraća 0.

Stog se može realizirati pomoću ADT STACK, koji ima sljedeću specifikaciju: #ifndef STACK_ADT #define STACK_ADT typedef int stackElemT; typedef struct stack *STACK; STACK stack_new(void); /* alocira memoriju za stog */ /* vraća pokazivač na stog */ void stack_free(STACK S); /* dealocira memoriju koju zauzima stog */ int stack_empty(STACK S); /* vraca 1 ako je stog prazan */ unsigned stack_count(STACK S); /* vraca broj elemenata na stogu */ stackElemT Top(STACK S); /* dobavlja vrijednost elementa na vrha stoga */ stackElemT Pop(STACK S); /* dobavlja vrijednost elementa na vrha stoga */ /* i odstranjuje ga sa stoga */ /* PRE: stog postoji */ /* POST: na stogu je jedan element manje */ void Push(STACK S, stackElemT x); /* postavlja element na vrh stoga */ /* PRE: stog postoji */ /* POST: na stogu je jedan element vise */ #endif

Slika 16.2. Stog - operacije

Page 216: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

216

Implementacija se može izvršiti na više načina. Sada će biti opisana implementacija u kojoj se za spremanje elemenata stoga koristi podatkovna struktura tipa dinamičkog niza, a u poglavlju 18 bit će pokazana implementacija pomoću strukture podataka tipa vezane liste.

Implementacija ADT STACK pomoću dinamičkog niza je opisana u datoteci "stack-arr.c".

/* Datoteka: stack-arr.c: * Implementacija ADT STACK pomoću niza */ #include <stdlib.h> #include "stack.h" #define STACK_GROW 10U #define STACK_SIZE 100U /* typedef int stackElemT; definirano in stack.h*/ /* typedef struct stack *STACK; definirano in stack.h*/ struct stack { stackElemT *A; unsigned top; /* indeks poviše stvarnog vrha stoga*/ unsigned size; /* veličina niza*/ }; static void stack_error(char *s) { printf("\nGreska: %s\n", s); exit(1); } STACK stack_new(void) { STACK S = malloc(sizeof(struct stack)); if(S != NULL) { S->size = STACK_SIZE; S -> top = 0; S->A = malloc(sizeof(stackElemT) * S->size); if(S->A == NULL) {free(S); S=NULL;} } return S; } void stack_free(STACK S) { if(S->A != NULL) free(S->A); if(S != NULL) free(S); } int stack_empty(STACK S) { return (S->top <= 0); } stackElemT stack_pop(STACK S) { if(stack_empty(S)) stack_error("Stog prazan"); return S->A[--(S->top)]; } void stack_push(STACK S, stackElemT x) { if (S->top >= S->size) { S->size += STACK_GROW;

Page 217: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

217

S->A = realloc(S->A, sizeof(stackElemT) * S->size); if(S->A == NULL) stack_error("Nema slobodne memorije"); } S->A[(S->top)++] = x; } stackElemT stack_top(STACK S) { if(stack_empty(S)) stack_error("Stog prazan"); return S->A[S->top-1]; }

Testiranje ADT STACK provodi se programom stack-test.c.

/* Datoteka: stack-test.c */ #include <stdio.h> #include <stdlib.h> #include "stack-arr.c" void upute(void) /* Upute za korisnika */ { printf("Otipkaj:\n" "1 - push - gurni vrijednost na stog\n" "2 - pop - skini vrijednost sa stoga\n" "0 - kraj programa\n"); } int main(void) { int izbor, val; STACK stog = stack_new(); upute(); printf("? "); scanf("%d", &izbor); while (izbor != 0) { switch (izbor) { case 1: /* push */ printf("Unesi integer: "); scanf("%d", &val); stack_push(stog, val); break; case 2: /* pop */ if (!stack_empty(stog)) printf("Podignuta je vrijednost %d.\n", stack_pop(stog)); else printf("Stog je prazan.\n"); break; default: printf("Pogresan odabir opcije. Ponovi!\n\n"); upute(); break; } printf("? "); scanf("%d", &izbor); }

Page 218: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

218

printf("\nStog:"); while (!stack_empty(stog)) printf(" %d", stack_pop(stog)); stack_free(stog); return 0; }

16.3 Primjena stoga za proračun izraza postfiksne notacije Korištenjem programski simuliranog stoga jednostavno se provodi računanje matematičkih

izraza u postfiksnoj notaciji. Postfiksna notacija izraza se piše tako da se najprije napišu operandi, a iza njih operator koji na njih djeluje, primjerice

infiksna notacija izraza postfiksna notacija izraza A + B * C A B C * + (A + B) * C A B + C * (a + b)/(c – d) a b + c d - / a * b / c a b * C /

Ovaj tip notacije se naziva i obrnuta poljska notacije, prema autoru Lukasiewiczu. Svojstva postfiksne notacije su:

1. Svaka formula se može napisati bez zagrada.

2. Infiksni operatori moraju uvažavati pravila prioriteta što nije potrebno kod postfiksne notacije.

3. Za proračun postfiksne notacije prikladna je upotreba stoga. Pretvorba infiksne u postfiksnu notaciju se izvodi slijedećim algoritmom:

1. Kompletno ispiši zagrade između svih operanada, tako da zagrade potpuno odrede redoslijed izvršenja operacija.

2. Pomakni svaki operator na mjesto desne zagrade

3. Odstrani zagrade Pretvorba izraza (8+2*5)/(1+3*2-4), prema gornjem pravilu, je sljedeća:

prema 1. ( ( 8 + ( 2 * 5 ) ) / ( 1 + ( ( 3 * 2 ) - 4 ) ) ) prema 2. i 3. ( ( 8 + ( 2 * 5 ) ) ( 1 + ( ( 3 * 2 ) - 4 ) ) / ( 8 ( 2 * 5 ) + 1 ( ( 3 * 2 ) - 4 ) + / 8 2 5 * + 1 ( ( 3 * 2 ) 4 - + / 8 2 5 * + 1 3 2 * 4 - + / daje notaciju: 8 2 5 * + 1 3 2 * + 4 - /

Page 219: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

219

Za izračun postfiksnog izraza, koji ima n simbola, vrijedi algoritam: 1. Neka je k =1 indeks prvog simbola 2. Dok je k <= n ponavljaj

Ako je k-ti simbol operand, stavi ga na stog. Ako je k-ti simbol operator, dobavi dvije vrijednosti sa stoga (najprije drugi, pa prvi operand), izvrši naznačenu operaciju i rezultat vrati na stog. Uvećaj k za 1

3. Algoritam završava s rezultatom na stogu. Primjer: Izraz zapisan infiksnoj notaciji:

(8+2*5) / (1+3*2-4).

ima postfiks notaciju:

8 2 5 * + 1 3 2 * + 4 - /

Proračun ovog izraza pomoću stoga ilustriran je na slici 16.3: Neobrađeni ulazni niz Operacija Sadržaj stoga 1 8 2 5 * + 1 3 2 * + 4 - / push 8 8 2 2 5 * + 1 3 2 * + 4 - / push 2 8 2 3 5 * + 1 3 2 * + 4 - / push 5 8 2 5 4 * + 1 3 2 * + 4 - / pop(b), pop(a),push (a*b) 8 10 5 + 1 3 2 * + 4 - / pop(b), pop(a),push(a+b) 18 6 1 3 2 * + 4 - / push 1 18 1 7 3 2 * + 4 - / push 3 18 1 3 8 2 * + 4 - / push 2 18 1 3 2 9 * + 4 - / pop(b), pop(a),push (a*b) 18 1 6 10 + 4 - / pop(b), pop(a),push(a+b) 18 7 11 4 - / push 4 18 7 4 12 - / pop(b), pop(a),push(a-b) 18 3 13 / pop(b), pop(a),push(a/b) 6

Slika 16.3. Korištenje stoga za proračun izraza koji je zapisan u postfiksnoj notaciji

Proračun izraza pomoću postfiksne notacije je jedan od uobičajenih načina kako interpreteri izračunavaju izraze zapisane u višim programskim jezicima - najprije se vrši pretvorba infiksnog zapisa izraza u postfiksni zapis, a zatim se proračun izraza vrši pomoću stoga. U ovom slučaju ne koristi se stog kojim upravlja procesor već se rad stoga simulira programski.

Primjer: Datoteka "polish.c" sadrži jednostavni interpreter aritmetičkih izraza. Izraz treba zapisati u komandnoj liniji unutar navodnika, primjerice

c:> polish "8 2 5 * + 1 3 2 * + 4 - /"

U izrazu se smiju koristiti cijeli brojevi i operatori zbrajanja, oduzimanja, množenja i dijeljenja. Kada se program izvrši dobije se ispis:

Rezultat: 8 2 5 * + 1 3 2 * + 4 - / = 6

Page 220: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

220

/* Datoteka polish.c: * Proracun izraza postfiksne notacije */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <ctype.h> #include "stack.h" #include "stack-arr.c" #define Pop() stack_pop(stack) #define Top() stack_top(stack) #define Push(x) stack_push(stack, (x)) main(int argc, char *argv[]) { char *str; int i, len, tmp; STACK stack; if (argc < 2) { printf("Za proracun (30/(5-2))*10 otkucaj:\n"); printf("c:> polish \"30 5 2 - / 10 *\"\n"); exit(1); } str = argv[1]; len = strlen(str); stack = stack_new(); for (i = 0; i < len; i++) { if (str[i] == '+') Push(Pop()+ Pop()); if (str[i] == '*') Push(Pop()* Pop()); if (str[i] == '-') { tmp = Pop(); Push(Pop()- tmp); } if (str[i] == '/') { int tmp = Pop(); if (tmp==0) {printf("Djeljenje s nulom\n"); exit(1);} Push(Pop() / tmp); } if (isdigit(str[i])) /* konverzija niza znamenki u broj */ { Push(0); do { Push(10*Pop() + (int)str[i]-'0'); i++; } while (isdigit(str[i])); i--; } } printf("Rezultat: %s = %d \n", str, Pop( )); stack_free(stack); }

Page 221: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

221

16.4 Red i QUEUE ADT Red (eng. queue) je struktura koja podsjeća na red za čekanje. Iz reda izlazi onaj koji je prvi

u red ušao. Ovaj princip pristupa podacima se naziva FIFO – first in first out. Temeljne su operacije:

ADT QUEUE

get(Q )

put( Q , el)

empty( Q )

full( Q )

- dobavi element iz reda Q.

- stavi element el u red Q.

- vraća 1 ako je red Q prazan, inače vraća 0.

- vraća 1 ako je red Q popunjen, inače vraća 0.

Ovakvi se redovi mogu realizirati kao ADT QUEUE prema sljedećoj specifikaciji:

/* Datoteka: queue.h */ #ifndef _QUEUE_ADT #define _QUEUE_ADT typedef int queueElemT; typedef struct queue *QUEUE; QUEUE queue_new(void); /* formira novi objekt tipa QUEUE */ void queue_free(QUEUE Q); /* dealocira objekt tipa QUEUE */ int queue_empty(QUEUE Q); /* vraća 1 ako je red prazan */ int queue_full(QUEUE Q); /* vraća 1 ako je red popunjen */ void queue_put(QUEUE Q, queueElemT el); /* stavlja element u red */ queueElemT queue_get(QUEUE Q); /* vraća element iz reda */ void queue_print(QUEUE Q); #endif

Implementacija se može provesti na više načina. Za smještaj elemenata reda najčešće se koristi niz ili linearna lista. Najprije ćemo upoznati implementaciju pomoću niza, i to implementaciju koja koristi tzv. cirkularni spremnik. On se realizira pomoću niza, kojem dva indeksa: back i front, označavaju mjesta unosa (back) i dobave (front) iz reda. Niz je veličine QUEUEARRAYSIZE. Operacije put() i get() se mogu ilustrirati na sljedeći način:

Page 222: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

222

Početno je red prazan Front = back;

- - - - - - Front=back (red prazan)

Nakon operacija put() povećava se indeks back za 1

put('a'); put('b')

a b - - - - front back

Operacija get() dobavlja element kojem je indeks jednak front, a zatim se front povećava za jedan.

x = get() /* x sadrži vrijednost a */

a b - - - - front back

Što napraviti kada, nakon višestrukog unosa, back postane jednak krajnjem indeksu niza?

put('c'); put('d'); put('e');

- b c d E - front back

Ideja je da se back ponovo postavi na početak niza (takovi spremnik se nazivaju cirkularni spremnik). Time se maksimalno iskorištava prostor spremnika za smještaj elemenata reda.

put('f')

- b C d e f back front (red popunjen)

Red popunjen ako je: (back+1) % QUEUEARRAYSIZE == front

U datoteci "queue_arr.c" realiziran je ADT QUEUE pomoću cirkularnog spremnika.

/* Datoteka: queue-arr.c * QUEUE realiziran kao cirkularni spremnik */ #include <stdio.h> #include <stdlib.h> #include <assert.h> #include "queue.h" #define QUEUESIZE 100 /* maksimalni broj elemenata */ #define QUEUEARRAYSIZE (QUEUESIZE +1) /* veličina niza */ /* typedef int queueElemT; */ /* typedef struct queue *QUEUE; definirani u queue.h*/ struct queue { queueElemT A[QUEUEARRAYSIZE]; int front; int back; }; QUEUE queue_new(void) { QUEUE Q = malloc(sizeof(struct queue)); if(Q != NULL) Q->front = Q->back = 0; return Q; } void queue_free(QUEUE Q) { assert(Q != NULL); if(Q != NULL) free(Q);

Page 223: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

223

} int queue_empty(QUEUE Q) { assert(Q != NULL); return (Q->front == Q->back); } int queue_full(QUEUE Q) { assert(Q != NULL); return ((Q->back + 1) % QUEUEARRAYSIZE == Q->front); } void queue_put(QUEUE Q, queueElemT x) { assert(Q != NULL); Q->A[Q->back] = x; Q->back = (Q->back + 1) % QUEUEARRAYSIZE; } queueElemT queue_get(QUEUE Q) { queueElemT x; assert(Q != NULL); x = Q->A[Q->front]; Q->front = (Q->front +1) % QUEUEARRAYSIZE; return x; } void queue_print(QUEUE Q) { int i; assert(Q != NULL); printf("Red: "); for(i = Q->front % QUEUEARRAYSIZE; i < Q->back; i=(i+1)% QUEUEARRAYSIZE ) printf("%d, ", Q->A[i]); printf("\n"); }

Testiranje ADT QUEUE provodi se programom queue-test.c.

/* Datoteka: queue-test.c */ #include <stdio.h> #include <stdlib.h> #include <assert.h> #include "queue.h" #include "queue-arr.c" void upute(void) { printf ("Izbornik:\n" " 1 Umetni broj u Red\n" " 2 Odstrani broj iz Reda\n" " 0 Kraj\n"); } int main()

Page 224: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

224

{ int izbor, elem; QUEUE Q = queue_new(); upute(); printf("? "); scanf("%d", &izbor); while (izbor != 0) { switch(izbor) { case 1: printf("Otkucaj broj: "); scanf("\n%d", &elem); if (!queue_full(Q)) { queue_put(Q, elem); printf("%d ubacen u red.\n", elem); } queue_print(Q); break; case 2: if (!queue_empty(Q)) { elem = queue_get(Q); printf("%d odstranjen iz reda.\n", elem); } queue_print(Q); break; default: printf("Pogresan Izbor.\n\n"); upute(); break; } printf("? "); scanf("%d", &izbor); } return 0; }

16.5 Zaključak Opisana je metoda programiranja, pomoću koje se sustavno analizira, specificira i implementira programske objekte kao apstraktne dinamičke tipove podataka – ADT. Izrada specifikacije operacija s apstraktnim objektima sve više postaje temeljni element programiranja. To osigurava da se maksimalna pažnja posveti onome što treba programirati. Neovisnost specifikacije od implementacije ADT, osigurava fleksibilan i pouzdan razvoj programa. U specifikaciji ADT-a dva su temeljna elementa: ime ADT-a i operacije koje se mogu izvršiti. To je karakteristka tipova, pa se s ADT-om kreiraju novi apstrakni tipovi podataka. Rad s ADT predstavlja objetno temeljeno programiranje.

Page 225: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

225

17 Rekurzija i složenost algoritama

Naglasci: • rekurzija • podijeli pa vladaj • kompleksnost algoritama • binarno pretraživanje niza • sortiranje

17.1 Rekurzivne funkcije U programiranju i matematici često se koriste rekurzivne definicije funkcija. Direktna

rekurzija nastaje kada se u definiciji funkcije poziva ta ista funkcija, a indirektna rekurzija nastaje kada jedna funkcija poziva drugu funkciju, a ova ponovo poziva funkciju iz koje je pozvana.

Definicija rekurzivne funkcije u pravilu se sastoji od dva dijela: temeljnog slučaja i pravila rekurzije. Primjerice, u matematici se se može rekurzivno definirati funkcija n! (n-faktorijela) na sljedeći način:

Definicija n! (n-faktorijela): 1. Temeljni slučaj: 0! = 1 za n=0 2. Pravilo rekurzije: n! = n * (n-1)! za n>0

Sve vrijednosti od n! se mogu izračunati pomoću gornjeg rekurzivnog pravila, tj.

1! = 1 * 0! = 1 * 1 = 1 2! = 2 * 1! = 2 * 1 * 0! = 2 * 1 * 1 = 2 3! = 3 * 2! = 3 * 2 * 1! = 3* 2 * 1 * 0! = 3 * 2 * 1 * 1 = 6 4! = ....

Uočite da rekurzivno pravilo znači ponavljanje nekog odnosa, a temeljni slučaj označava prostu radnju nakon koje prestaje rekurzija.

Programski se funkcija n! realizira kao funkcija fact(), koja prima argument tipa int i vraća vrijednost tipa int. Koristeći prethodnu matematičku definiciju , funkcija fact() se implementira na sljedeći način: int fact(int n)

{ if (n == 0) return 1; else return fact(n-1) * n; }

Uočite da se u tijelu funkcije fact() poziva ta ista funkcija.

Kako se izvršava ova funkcija? Da bi to shvatili, potrebno je znati kako se na razini strojnog koda vrši poziv funkcije. Većina kompilatora to vrši na sljedeći način:

Page 226: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

226

1. Pri pozivu funkcije najprije se argumenti funkcije postavljaju u dio memorije koji je predviđen za lokalne varijable. Ta memorija se naziva izvršni stog, jer se podacima pristupa s pus() i pop() opearacijama. Vrijednost, koja je posljednja stavljena na stog, predstavlja vrh stoga.

2. Zatim se izvršava kôd tijela pozvane funkcije. U njoj se argumenti funkcije tretiraju kao lokalne varijable čija je vrijednost na stogu.

3. Povrat iz funkcije se vrši tako da se stanje izvršnog stoga vrati na stanje prije poziva funkcije, a povratna se vrijednost postavlja u registar procesora, tzv. povratni registar.

4. Izvršenje programa se nastavlja naredbom u pozivnoj funkciji koja slijedi iza pozvane funkcije.

Na slici 1 prikazano je izvršenje funkcije fact(4) i stanje izvršnog stoga.

izvršenje funkcije fact(4)

stanje stoga koji se koristi za prijenos argumenata funkcije

fact(4)= 4 * fact(3)=

3 * fact(2) 2 * fact(1) 1 * fact(0) return 1 return 1*1 return 2*1 return 3*2

return 4*6 => 24

... 4 ... 4 3 ... 4 3 2 ... 4 3 2 1 ... 4 3 2 1 0 ... 4 3 2 1 ... 4 3 2 ... 4 3 ... 4 ...

Slika 17.1. Redoslijed izvršenja rekurzivne funkcije fact(4)

Poziv funkcije fact(4) počinje tako da se argument vrijednosti 4 stavlja na vrh izvršnog stoga, a zatim se izvršavaju naredbe iz tijela funkcije. Pošto je argument različit od nule izvršava se naredba return fact(n-1) * n;. Da bi se ona mogla izvršiti, najprije se vrši poziv funkcije fact(n-1). Zbog toga se na izvršni stog stavlja vrijednost argumenta 3 i poziva funkcija fact(3). Ovaj proces se ponavlja sve dok argument funkcije ne postane jednak nuli. Nakon toga se u tijelu funkcije izvršava naredba return 1;. To znači da se odstranjuje vrijednost s vrha stoga (0), a u povratni registar se upisuje vrijednost 1. Program nastavlja izvršenje naredbom koja slijedi iza pozivne funkcije, a to je zapravo onaj dio naredbe return fact(n-1) * n; u kojoj se vrši množenje povratne vrijednosti od fact(n-1) i argumenta n, čija se vrijednost nalazi na vrhu stoga (u ovom slučaju to je vrijednost 1). Zatim se u povratni registar stavlja vrijednost 1*1 i skida argument s vrha stoga, pa na vrhu stoga ostaje vrijednost 2. Program se ponovo vraća na izvršenje u naredbu return 1 * 2;. U povratni registar se sada upisuje vrijednost 2, na vrhu stoga ostaje vrijednost 3 i izvršenje se vraća u naredbu return 2 * 3; jer je iz nje vršen poziv fact(3). Nakon toga se izvršava naredba return 6 * 4;. Ovo je posljednja naredba koja će se izvršiti. Nakon nje je vrh stoga prazan, a izvršenje programa se vraća na naredbu iz pozivne funkcije koja slijedi iza poziva fact(4). U povratnom registru je vrijednost 24, koja predstavlja vrijednost koju vraća fact(4).

Moglo bi se slikovito reći da se rekurzivne funkcije izvršavaju tako da se najprije višestrukim pozivom funkcije vrši "traženje" temeljnog slučaja, pri čemu se pamte sva stanja procesa, a zatim se problem rješava "izvlačenjem" iz rekurzije.

Kod funkcija koje imaju veliki broj rekurzivnih poziva može doći do značajnog ispunjenja izvršnog stoga. Kod MSDOS operativnog sustava može se maksimalno koristiti 64Kbajta za

Page 227: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

227

stog, pa treba biti oprezan pri korištenju rekurzivnih funkcija. Kod WIN32 sustava za stog je predviđeno koristiti do 1Mbajta memorije.

Zadatak: Napišite funkciju

unsigned suma( unsigned n);

kojoj je argument kardinalni broj n, a funkcija vraća vrijednost koja je jednaka sumi svih kardinalnih brojeva 0,1,2...,n. Koristite rekurzivnu definiciju:

1. trivijalni slučaj: ako je n=0, suma(n) = 0 2. rekurzivno pravilo: ako je n>0, suma(n) = suma(n-1)+n

17.2 Matematička indukcija Rekurzija se koristi i pri dokazivanju teorema indukcijom. Princip matematičke indukcija

se koristi kod problema čija se zakonitost može označiti cijelim brojem n, kao S(n). Definira se na sljedeći način.

Da bi dokazali da vrijedi zakonitost S(n), za bilo koju vrijednost n:

1. Dokaži da zakonitost S(n) vrijedi u trivijalnom slučaju za n=0 2. Zatim dokaži da vrijedi S(n), ako se pretpostavi da vrijedi S(n-1).

Primjer: Suma od n prirodnih brojeva se može izračunati prema izrazu:

1 + 2 + 3 + … + n = n (n +1) / 2 Dokaz: 1. Trivijalni slučaj: za n = 1, suma je jednaka 1

Pošto je 1(1+1)/2) = 1 dokazano je da vrijedi trivijalni slučaj. 2. Pretpostavimo da vrijedi za 1 + … +( n -1), pa ispitajmo da li vrijedi za 1 + … +( n -1) + n ? Pošto je

1 + … +( n -1) + n = (n -1)( n -1+1) / 2 + n = n (n +1) / 2 dokaz je izvršen. Zadatak: Dokažite da ova formula vrijedi i za proračun sume svih kardinalnih brojeva (0,1,2,..) koji su manji ili jednaki n. Zadatak: Napišite funkciju za proračun sume svih kardinalnih brojeva koji su manji ili jednaki n, koristeći prethodno izvedenu formulu.

17.3 Kule Hanoja Čovjek nije sposoban razmišljati i rješavati probleme na rekurzivan način. U programiranju

se pak rekurziju može koristiti u mnogo slučajeva, posebno kada je njome prirodno definiran

Page 228: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

228

neki problem. Jedan od najpoznatijih rekurzivnih problema u kompjuterskoj literaturi je bez sumnje rješenje inteligentne igre koja se naziva Kule Hanoja. Problem je predstavljen na slici 2.

Slika 17.2. Kule Hanoia

Postoje tri štapa označena s A, B i C. Na prvom štapu su nataknuti cilindrični diskovi

promjenljive veličine, koji imaju rupu u sredini. Zadatak je premjestiti sve diskove s štapa A na štap B u redoslijedu kako se nalaze na štapu A. Pri prebacivanju diskova treba poštovati sljedeće pravila:

Odjednom se smije pomicati samo jedan disk. Ne smije se stavljati veći disk povrh manjeg diska. Može se koristiti štap C za privremeni smještaj diskova, ali uz poštovanje prethodna dva pravila.

Problem: pomakni N diskova sa štapa A na štap B, može se riješiti rekurzivnim postupkom. Temeljni slučaj i pravilo rekurzije su: Temeljni slučaj - Najjednostavniji slučaj kojeg svatko može riješiti je kada kula sadrži samo jedan disk. Tada je rješenje jednostavno; prebaci se taj disk na ciljni štap B. Rekurzivno pravilo - Ako kula sadrži N diskova, pomicanje diskova se može izvesti u tri koraka

1. Pomakni gornjih N-1 diskova sa štapa A na pomoćni štap C. 2. Preostali donji disk s štapa A pomakni na ciljni štap B. 3. Zatim pomakni kulu od N-1 diskova s pomoćnog štapa C na ciljni štap B.

Teško je na prvi pogled prihvatiti da ovo rekurzivno pravilo poštuje pravilo da se uvijek pomiče samo jedan disk, ali ako se prisjetimo da se rekurzivni problemi počinju rješavati tek kad je pronađen temeljni slučaj, u kojem se pomiče samo jedan disk, i da su prije toga zapamćena sva moguća stanja procesa, onda je jasno da se uvijek pomiče samo jedan disk.

Kako napisati funkciju pomakni_kulu() koja izvršava gornje pravilo. Potrebni argumente funkcije su: broj diskova koje treba pomaknuti, ime početnog štapa , ime ciljnog štapa, ime pomoćnog štapa.

void pomakni_kulu(int n, char A, char B, char C);

Također, potrebno je definirati funkciju kojom će se na prikladan način označiti prebacivanje jednog diska. Nju se može odmah definirati u obliku:

Page 229: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

229

void pomakni_disk(char sa_kule, char na_kulu) { printf("%c -> %c\n", sa_kule, na_kulu); }

Korištenjem ove funkcije i pravila rekurzije, funkciju pomakni_kulu() se može napisati na sljedeći način:

void pomakni_kulu(int n, char A, char B, char C) { if (n == 1) { /* temeljni slučaj */ pomakni_disk(A, B); } else { pomakni_kulu (n - 1, A, C, B); /* 1. pravilo */ pomakni_disk (A, B); /* 2. pravilo */ pomakni_kulu (n - 1, C, B, A); /* 3. pravilo */ } }

Ili još jednostavnije:

void pomakni_kulu(int n, char A, char B, char C) { if (n > 0) { pomakni_kulu (n - 1, A, C, B); pomakni_disk (A, B); pomakni_kulu (n - 1, C, B, A); } }

jer se u slučaju kada je n=1 u funkciji pomakni_kulu(0, ....) ne izvršava ništa, pa se u tom slučaju izvršava funkcija pomakni_disk(A,B), što je pravilo temeljnog slučaja. Za testiranje funkcije pomakni_kulu(), koristi se program hanoi.c:

/* Datoteka: hanoi.c */ #include <stdio.h> void pomakni_kulu(int n, char A, char B, char C); void pomakni_disk(char sa_kule, char na_kulu) int main() { int n = 3; /* za slučaj 3 diska*/ pomakni_kulu(n, 'A','B','C'); return 0; }

Nakon izvršenja ovog programa dobije se izvještaj o pomaku diskova oblika:

A -> B A -> C B -> C A -> B C -> A

Page 230: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

230

C -> B A -> B

U ovom primjeru je očito da se pomoću rekurzije dobije fascinantno jednostavno rješenje problema. Teško da postoji neka druga metoda kojom bi se ovaj problem mogao riješiti na jednako efikasan način. Zadatak: Provjerite izvršenje programa za slučaj da broj diskova iznosi: 2, 3, 4 i 5. Pokazat će se da broj operacija iznosi 2n-1, što se može i logično zaključiti, jer se povećanjem broja diskova za jedan udvostručuje broj rekurzivnih poziva funkcije pomakni_kulu(), a u temeljnom slučaju se vrši samo jedna operacija. Procijenite koliko bi trajalo izvršenje programa pod uvjetom da izvršenje jedne operacije traje 1us i da se koristi 64 diska. Da li izvršenje tog program traje dulje od životog vijeka čovjeka?

17.4 Metoda - podijeli pa vladaj (Divide and Conquer) U analizi programskih metoda često se spominje metoda "podijeli pa vladaj". Kod nje se

rekurzija nameće kao prirodni način rješenja problema. Opći princip metode je da se problem logično podijeli u više manjih problema, tako da se rješenje dalje može odrediti rješavanjem jednog od tih "manjih" problema.

17.4.1 Drugi korijen broja Metodu podijeli pa vladaj primijenit ćemo za približan proračun drugog korijena broja n. Metoda: Numerički se proračuni mogu provesti samo s ograničenom točnošću. Zbog toga zadovoljava postupak u kojem se određuje da vrijednost x predstavlja drugi korijen od n, ako je razlika (n – x2) približno jednaka nuli, odnosno manja od po volji odabrane vrijednosti epsilon. Točno rješenje se nalazi unutar nekog intervala [d,g]. Primjerice, sigurno je da se rješenje nalazi u intervalu [0,n] ako je n>1, odnosno u intervalu [0,1] ako je n<1. Interval može biti i uži ako smo sigurni da obuhvaća točno rješenje. Do rješenja se dolazi rekurzivno: Temeljni slučaj:

Ukoliko se uzme da je vrijednost od x u sredini intervala [d,g], tj. x=(d+g)/2, može se prihvatiti da je to zadovoljavajuće rješenje, ako je širina intervala manja od neke po volji odabrane vrijednosti epsilon (pr. 0,000001) , tj. ako je je g-d < epsilon.

Ako je širina intervala veća od epsilon, do rješenje se dolazi koristeći rekurziju prema pravilu (2).

Pravilo rekurzije:

Ako je n < x2 rješenje se traži u intervalu [d, x], inače, rješenje se traži u intervalu [x, g].

Implementacija: U programu korijen.c implementirana je i testrirana funkcija DrugiKorijen(n), koja vraća drugi korijen od n. U toj funkciji se prvo određuje donja i gornja granica intervala unutar kojega se nalazi rješenje, a zatim se poziva funkcija korijen_rek(n, d, g) koja obavlja proračun prema prethodnom rekurzivnom algoritmu. Točnost proračuna se ispituje usporedbom s vrijednošću kojeg vraća standardna funkcija sqrt(n).

Page 231: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

231

/* Program korijen.c */ #include <stdio.h> #include <math.h> #define EPSILON 0.000001 /* proizvoljni kriterij točnosti */ double korijen_rek(double n, double d, double g) { double x = (d + g)/ 2.0; if (g - d < EPSILON) /* temeljni slučaj */ return x; else if (n < x*x ) /* pravilo rekurzije */ return korijen_rek (n, d, x); else return korijen_rek (n, x, g); } double DrugiKorijen(double n) { double g, d=0.0; /* početne granice d=0.0, g=n ili 1 ako je n<1*/ if(n < 0) n= -n; /* samo za pozitivne vrijednosti */ if(n>1) g=n; else g=1.0; return korijen_rek(n, d, g); } int main( int argc, char *argv[]) { int i; double n; for (i = 1; i < argc; i++) { sscanf( argv[i], "%lf", &n); printf("Drugi korijen(%f) = %f (treba biti %f)\n", n, DrugiKorijen(n), sqrt(n)); } return 0; }

Nakon poziva programa:

c:>korijen 5 7.6 3.14

Dobije se ispis:

Drugi korijen (5.000000) = 2.236068 (treba biti 2.236068) Drugi korijen (7.600000) = 2.756810 (treba biti 2.756810) Drugi korijen (3.140000) = 1.772004 (treba biti 1.772005)

17.4.2 Binarno pretraživanje niza Drugi primjer primjene metode podijeli pa vladaj je traženje elementa sortiranog niza

metodom koja se naziva binarno pretraživanje niza.

Zadatak: Zadan je niz cijelih brojeva a[n] kojem su elementi sortirani od manje prema većoj vrijednosti, tj.

a[i-1] < a[i], za i=1,..n-1

Page 232: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

232

Potrebno je odrediti da li se u ovom nizu nalazi element vrijednosti x, i to pomoću funkcije

int binSearch( int a[],int x, int d, int g);

koja vraća indeks elementa a[i], kojem je vrijednost jednaka traženoj vrijednosti x. Ako vrijednost od x nije jednaka ni jednom elementu niza, funkcija binSearch() vraća negativnu vrijednost -1. Cjelobrojne vrijednosti d i g predstavljaju indekse niza koji određuju interval [d,g] unutar kojeg se vrši traženje. Metoda: Problem se može riješiti rekurzivno na sljedeći način: Ako u nizu a[i] postoji element jednak traženoj vrijednosti x, njegov indeks je iz intervala [d,g], gdje mora biti istinito g >= d. Trivijalni slučaj je za d=0, g=n-1, koji obuhvaća cijeli niz. Temeljni slučaj:

Razmatra se element niza indeksa i = (g+d)/2 (dijelimo niz na dva podniza). Ako je a[i] jednak x, pronađen je traženi element niza, a funkcija vraća indeks i.

Pravilo rekurzije:

Ako je a[i] <x rješenje se traži u intervalu [i+1, g], inače je u intervalu [d, i-1]. Implementacija:

int binSearch( int a[],int x, int d, int g) { int i; if (d > g) return –1; i = (d + g)/ 2; if (a[i] == x) return i; if (a[i] < x) return binSearch( a, x, i + 1, g); else return binSearch( a, x, d, i - 1); }

Proces traženja vrijednosti x=23 u nizu od 14 elemenata, ilustriran je na slici 17.3.

0 1 2 3 4 5 6 7 8 9 10 11 12 13 1 2 3 5 6 8 9 12 23 26 27 31 34 42 D i g 1 2 3 5 6 8 9 12 23 26 27 31 34 42 d i g 1 2 3 5 6 8 9 12 23 26 27 31 34 42 d i g

1.korak: d=0, g=13, i=6, a[6]<23 2.korak: d=i+1=7,g=14, i=10, a[10]>23 3.korak: d=7, g=i-1=9, i=8, a[8]==23

Slika 17.3. Binarno pretraživanje niza

17.5 Pretvorba rekurzije u iteraciju Neke rekurzivne funkcije se mogu transformirati u funkcije koje umjesto rekurzije koriste

iteraciju. To su funkcije u kojima je rekurzivni poziv funkcije posljednja naredba u tijelu funkcije, primjerice

Page 233: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

233

void petlja() { iskaz ... if (e) petlja(); }

U ovom slučaju, rekurzivni poziv funkcije petlja(), znači da se izvršenje vraća na početak tijela funkcije, zbog toga se ekvivalentna verzija funkcije može napisati tako da se umjesto poziva funkcije koristi goto naredba s odredištem na prvu naredbu tijela funkcije, tj.

void petlja() { start: iskaz ... if (e) goto start; }

Rekurzivni poziv, koji se vrši na kraju (ili na repu) tijela funkcije, često se naziva "repna rekurzija". (eng. tail recursion). Neki optimizirajući kompilatori mogu prepoznati ovakav oblik rekurzivne funkcije i transformirati rekurzivno tijelo funkcije u iterativnu petlju. Na taj način se dobije efikasnija funkcija, jer se ne gubi vrijeme i prostor na izvršnom stogu koji su potrebni za poziv funkcije.

Kod većine se rekurzivnih funkcija ne može izvršiti ova transformacije. Primjerice, funkcija fact() nije repno rekurzivna jer se u posljednjoj naredbi (return fact(n-1)*n;) najprije vrši poziv funkcije fact(), a zatim naredba množenja. Funkcija binSearch(), koja je opisana u prethodnom odjeljku, može se transformirati u funkciju s repnom rekurzijom, na sljedeći način:

int binSearch( int a[],int x, int d, int g) { int i; if (d > g) return –1; i = (d + g)/ 2; if (a[i] == x) return i; if (a[i] < x) d=i+1; else g=i-1 return binSearch( a, x, d, g); }

Dalje se može provesti transformacija u iterativnu funkciju

int binSearch( int a[],int x, int d, int g) { int i; start: if (d > g) return –1; i = (d + g)/ 2; if (a[i] == x) return i; if (a[i] < x) d=i+1; else g=i-1 goto start; }

Može se napisati iterativno tijelo funkcije i u obliku while petlje:

int binSearch( int a[],int x, int d, int g) { int i; while (d <= g) {

Page 234: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

234

i = (d + g)/2; if (a[i] == x) return i; if (a[i] < x) d=i+1; else g=i-1; } return –1; }

Iterativna verzija se može pojednostaviti u slučaju kada se pretražuje cijeli niz. Tada kao argument funkcije nije potrebna donja granica indeksa, jer je ona jednaka nuli, a umjesto gornje granice indeksa, argument je broj elemenata niza n.

int binSearch( int a[],int x, int n) { int i, d=0, g=n-1; while (d <= g) { i = (d + g)/2; if (a[i] == x) return i; if (a[i] < x) d=i+1; else g=i-1; } return –1; }

17.6 Standardna bsearch() funkcija U standardnoj biblioteci implementirana je polimorfna funkcija bsearch(). Služi za binarno pretraživanje nizova s proizvoljnim tipom elemenata niza. Deklarirana je na sljedeći način:

void *bsearch( const void *px, const void *niz, size_t n, size_t el_size, int (*pCmpFun)(const void *, const void *) );

Prvi parametar funkcije je pokazivač na objekt kojeg se traži. Drugi parametar je pokazivač na prvi element niza od n elemenata koji zauzimaju el_size bajta. Posljednji parametar je pokazivač na usporednu funkciju koja je ista kao kod qsort() funkcije. Funkcija bsearch() vraća pokazivač na element niza ili NULL ako niz ne sadrži traženi objekt. Realizacija te funkcije, ali pod imenom binsearch(), dana je i testirana programom binsearch.c, i to za slučaj da se vrši pretraživanje niza stringova.

/* Datoteka: binsearch.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> typedef char *string; void * binsearch( const void *px, const void *niz, size_t n, size_t el_size, int (*pCmpFun)(const void *, const void *) ) { int i, cmp, d=0, g=n-1; char * adr;

Page 235: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

235

while (d <= g) { i = (d + g)/2; /* adresa i-tog elementa niza*/ adr = (char *)niz + i*el_size; /*el. niza na toj adresi usporedi s objektom *px */ cmp = (*pCmpFun)((void *)adr, (void *)px); if (cmp == 0) return (void *) adr; /* objekt pronađen */ if (cmp < 0) d=i+1; else g=i-1; } return NULL; } int UsporediStringove( const void *pstr1, const void *pstr2 ) { /* Argumenti funkcije su pokazivači na objekte koje * usporedjujemo, u ovom slučaju objekt je string (char *). * Funkcija strcmp() prima argumente tipa string, stoga * treba izvršiti pretvorbu tipa i indirekciju da bi dobili * string,kao argument za strcmp() * ( str = *(string *) pstr)) */ return strcmp( *(string*) pstr1, *(string*)pstr2 ); } int main( void ) { string txt[] = {"Ante", "Ivo", "Marko", "Jure", "Bozo"}; int numstrings = sizeof(txt)/sizeof(txt[0]); string key="Ivo"; int i, idx; string * rez; /* sortiraj niz stringova leksikografski */ qsort((void *)txt, numstrings, sizeof(char*), UsporediStringove); for(i = 0; i < numstrings; i++ ) puts(txt[i]) ; /* pronađi string key */ rez =(string*) binsearch(&key, txt, numstrings, sizeof(char*), UsporediStringove); if(rez != NULL) printf("Pronadjen string \"%s\" na adresi %Fp\n", *pstr, pstr); else printf("Nije pronadjen string \"%s\" \n", key); return 0 ; }

Page 236: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

236

Dobije se ispis:

Ante Ivo Marko Jure Bozo Nakon sortiranja: Ante Bozo Ivo Jure Marko Pronadjen string "Ivo" na adresi 0022FF48

17.7 Složenost algoritama - "Veliki - O" notacija Pri analizi složenosti algoritama uvodi se mjera "dimenzije problema". Primjerice, kada se

obrađuju nizovi, onda dimenziju problema predstavlja duljina niza n, a kada su u pitanju kvadratne matrice, onda je dimenzija matrice istovremeno i dimenzija problema. Ako je n dimenzija problema, onda se efikasnost korištenja memorije može opisati funkcijom M(n), a vrijeme izvršenja algoritma funkcijom T(n).

Funkcija T(n) se često naziva vremenska složenost algoritma (engl. time complexity), dok je M(n) prostorna složenost. Funkciji T(n) se pridaje znatno više značaja nego veličini M(n), pa se pod skraćenim pojmom "složenost" (eng. complexity) obično podrazumijeva vremenska složenost. Razlog tome je iskustvo u razvoju algoritama, koje je pokazalo da je zauzeće memorije znatno manji problem od postizanja prihvatljivog vremena obrade.

Analiza brzine izvršenja programa se radi tako da se odredi ukupan broj operacija koje dominantno troše procesorsko vrijeme. Primjerice, u programu za zbrajanje elemenata kvadratne matrice, dimenzija nxn, označen je broj ponavljanja naredbi u kojima se vrši zbrajanje.

NAREDBE PROGRAMA BROJ PONAVLJANJA S = 0 ; for(i=0; i<n; i++) for(j=0; j<n; j++) S = S + M[i][j]

. n n*n n*n

Ako se uzme da prosječno vrijeme izvršenja jedne naredbe iznosi t0, dobije se da ukupno vrijeme izvršenja algoritma iznosi: T(n) = (2n² + n)*t0

Veličina T(n) je posebno važna za velike vrijednosti od n. U tom slučaju je: T(n) ≤ konst * n² Ovaj se zaključak u računarskoj znanosti piše u obliku tzv. "veliki-O" notacije:

T(n) = O(n²) i kaže se da je T(n) "veliki O od n²". Funkcija f(n) = n² predstavlja red složenosti algoritma. Uočite da vrijednost konstantnog faktora ne određuje složenost algoritma već samo faktor koji ovisi o veličini problema.

Definicija: Složenost algoritma u "veliki-O" notaciji definira se na sljedeći način: T(n) = O( f(n) ), (čita se: T je veliki-O od f)

Page 237: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

237

ako postoje pozitivne konstante C i n0, takove da je 0 ≤ T(n) ≤ C * f(n), za sve vrijednosti n ≥ n0.

U prethodnom je primjeru funkcija složenosti određena razmatrajući broj operacija u kojima se vrši zbrajanje. To ne treba uzeti kao pravilo, već za pojedini problem treba sagledati koje su operacije dominantne po zauzeću procesorskog vremena. Primjerice, kod metoda pretraživanja niza to će biti naredbe usporedbe i pridjele vrijednosti.

Klasifikacija algoritama prema redu funkcije složenosti za najpoznatije klase algoritama prikazana je u tablici 17.1. Ova je tablica uređena po kriteriju rastuće složenosti algoritama.

Tip algoritma f(n) Konstantan const. Logaritamski log2n Linearan N Linearno-logaritamski nlog2n Kvadratni n2 Stupanjski nk (k>2) Eksponencijalni kn (k>1) Faktorijelni n!

Tablica 17.1. Red složenosti algoritama

Pri izradi algoritama cilj je nalaženje rješenja koje je manjeg reda složenosti, a posebno je značajan rezultat kada se pronađe polinomsko ili logaritamsko rješenje nekog od eksponencijalnih problema. U nastavku je opisana svaka od navedenih tipova algoritama.

Konstantni algoritmi

Konstantni algoritmi su klasa algoritama kod kojih je vrijeme rada približno konstantno i ne ovisi od veličine problema. Primjerice, program koji računa kvadratni korijen realnog broja do rezultata po pravilu dolazi poslije približno istog vremena rada bez obzira kolika je veličina broja koji je uzet za ulazni podatak.

Logaritamski algoritmi

Kod logaritamskog algoritma vrijeme izvršenja programa proporcionalno je (najčešće binarnom) logaritmu veličine problema. Imajući u vidu sporost porasta logaritamske funkcije vidi se da se ovdje radi o najefikasnijim i stoga najpopularnijim algoritmima. Tipičan predstavnik logaritamskih algoritama je binarno pretraživanje sortiranog niza prikazano u prethodnom odjeljku.

Opća koncepcija logaritamskih algoritama je sljedeća: 1. Obaviti postupak kojim se veličina problema prepolovi. 2. Nastaviti razlaganje problema dok se ne dođe do veličine 1. 3. Obaviti završnu obradu s problemom jedinične veličine.

Ukupan broj razlaganja k dobije se iz uvjeta: n / 2k = 1, odnosno k=log2 n. Kako je po pretpostavci broj operacija koji se obavlja pri svakom razlaganju približno isti, to je i vrijeme rada približno proporcionalno broju razlaganja, odnosno binarnom logaritmu od n.

Page 238: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

238

Linearni algoritmi

Linearni algoritmi se javljaju u svim slučajevima gdje je obradom obuhvaćeno n istovjetnih podataka i gdje udvostručenje količine radnji ima za posljedicu udvostručenje vremena obrade. Opći oblik linearnog algoritma može se prikazati u vidu jedne for petlje:

for (i=0; i < n; i++) {obrada koja traje vrijeme t}

Zanemarujući vrijeme opsluživanja for petlje, u ovom slučaju funkcija složenosti je T(n)=nt, pa je T(n) = O(n).

U slučajevima kada nije moguće deterministički odrediti vrijeme izvršavanja sadržaja petlje može se umjesto vremena rada T(n) statistički odrediti srednji broj naredbi I(n) koje program obavlja. Tada se podrazumijeva da je T(n)=t1*I(n), gdje je vrijeme izvršavanja prosječne naredbe t1 = konst.

Budući da vrijedi T(n) = O( f(n) ) , I(n) = O( f(n) ), slijedi da se traženi red funkcije složenosti f(n) može odrediti kako iz T(n), tako i iz I(n). Primjerice, pronalaženje maksimalne vrijednosti elementa niza ima sljedeću analizu broja naredbi:

NAREDBE PROGRAMA BROJ PONAVLJANJA max = a[0] for(i=1; i<n; i++) if (a[i] > max) max = a[i]

1 (n-1) (n-1) (n-1)p (0<=p<=1)

Ovdje p označava vjerojatnost da dođe do ispunjenja uvjeta a[i]>max ( p ne ovisi od n). Sumirajući broj ponavljanja pojedinih naredbi dobije se da je ukupan broj izvršenih naredbi I(n) = 1 + (n-1)(3+p) = (3+ p) n - 1 - p. U ovom je izrazu dominantan samo prvi član polinoma, pa je f(n) = n, odnosno T(n) = O(n).

Linearno-logaritamski algoritmi

Linearno-logaritamski algoritmi, složenosti O(n log n) ), spadaju u klasu veoma efikasnih algoritama jer im složenost, za veliki n, raste sporije od kvadratne funkcije. Primjer za ovakav algoritam je Quicksort, koji će biti detaljno opisan kasnije. Bitna osobina ovih algoritama je sljedeća:

(1) Obaviti pojedinačnu obradu kojom se veličina problema prepolovi. (2) Unutar svake polovine sekvencijalno obraditi sve postojeće podatke. (3) Nastaviti razlaganje problema dok se ne dođe do veličine 1.

Slično kao i kod logaritamskih algoritama i ovdje je ukupan broj dijeljenja log2n, ali kako se nakon svakog dijeljenja sekvencijalno obrade svi podaci, to je ukupan broj elementarnih obrada jednak nlog2n, i to predstavlja red funkcije složenosti.

Page 239: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

239

Kvadratni i stupanjski algoritmi

Kvadratni algoritmi, složenosti O(n2), najčešće se dobivaju kada se koriste dvije for petlje jedna unutar druge. Primjer je dat na početku ovog poglavlja. Stupanjski algoritmi nastaju kod algoritama koji imaju k umetnutih petlji, pa je složenost O(nk).

Eksponencijalni algoritmi

Eksponencijalni algoritmi O(kn) spadaju u kategoriju problema za koje se suvremena računala ne mogu koristiti, izuzev u slučajevima kada su dimenzije takvog problema veoma male. Jedan od takovih primjera je algoritam koji rekurzivno rješava igru "Kule Hanoja", opisan u prethodnom poglavlju.

Faktorijelni algoritmi

Kao primjer faktorijelnih algoritama najčešće se uzima problem trgovačkog putnika. Problem je formuliran na sljedeći način: zadano je n+1 točaka u prostoru i poznata je udaljenost između svake dvije točke j i k. Polazeći od jedne točke potrebno je formirati putanju kojom se obilaze sve točke i vraća u polaznu točku, tako da je ukupni prijeđeni put minimalan.

Trivijalni algoritam za rješavanje ovog problema mogao bi se temeljiti na uspoređivanju duljina svih mogućih putanja. Broj mogućih putanja iznosi n!. Polazeći iz početne točke postoji n putanja do n preostalih točaka. Kada odaberemo jednu od njih i dođemo u prvu točku onda preostaje n-1 mogućih putanja do druge točke, n-2 putanja do treće točke, itd., n+1-k putanja do k-te točke, i na kraju samo jedna putanja do n-te točke i natrag u polaznu točku. Naravno da postoje efikasnije varijante algoritma za rješavanje navedenog problema, ali u opisanom slučaju, s jednostavnim nabrajanjem i uspoređivanjem duljina n! različitih zatvorenih putanja, algoritam ima složenost O(n!).

17.8 Sortiranje Sortiranje je postupak kojim se neka kolekcija elemenata uređuje tako da se elementi

poredaju po nekom kriteriju. Kod numeričkih nizova red elemenata se obično uređuje od manjeg prema većim elementima. Kod nizova stringova red se određuje prema leksikografskom rasporedu. Kod kolekcija strukturnog tipa obično se odabire jedan član strukture kao ključni element sortiranja. Sortiranje je važno u analizi algoritama, jer su analizom različitih metoda sortiranja postavljeni neki od temeljnih kompjuterskih algoritama.

Za analizu metoda sortiranja postavlja se sljedeći problem: odredite algoritam pomoću

kojeg se ulazni niz A[0..n-1], od n elemenata, transformira u niz kojem su elementi poredani u redu od manjeg prema većem elementu, tj. na izlazu treba biti ispunjeno:

A[0] ≤ A[1] ≤ A[2] ≤ ... ≤ A[n-2] ≤ A[n-1]. Najprije će biti analizirane dvije jednostavne metode, selekcijsko sortiranje i sortiranje

umetanjem, kojima je složenost O(n2). Zatim će biti analizirane dvije napredne metode, quicksort i mergesort, koje koriste metodu "podijeli pa vladaj". Njihova je složenost O(n log2 n). U analizi će biti korištena oznaka A[d..g] za označavanje da se analizira neki niz od indeksa d do indeksa g. Ovaj način označavanja ne vrijedi u C jeziku.

17.8.1 Selekcijsko sortiranje Ideja algoritma je:

1. Pronađi najmanji element i njegov indeks k. 2. Taj element postavi na početno mjesto u nizu (A[0]), a element koji je dotad postojao

na indeksu 0 postavi na indeks k.

Page 240: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

240

3. Zatim pronađi najmanji element, počevši od indeksa 1. Kada ga pronađeš, zamijeni ga s elementom indeksa 1.

4. Ponovi postupak (3) za sve indekse (2..n-2). Primjerice za sortirati niz od 6 elemenata: 6, 4, 1, 5, 3 i 2 , treba izvršiti sljedeće operacije:

6 4 1 5 3 2 -> min od A[0..5] zamijeni sa A[0] 1 4 6 5 3 2 -> min od A[1..5] zamijeni sa A[1] 1 2 6 5 3 4 -> min od A[2..5] zamijeni sa A[2] 1 2 3 5 6 4 -> min od A[3..5] zamijeni sa A[3] 1 2 3 4 6 5 -> min od A[4..5] zamijeni sa A[4] 1 2 3 4 5 6 -> niz je sortiran

Ovaj algoritam se može implementirati pomoću for petlje:

for (i = 0; i < n-1; i++) { imin = indeks najmanjeg elementa u A[i..n-1]; Zamijeni A[i] sa A[imin]; }

Uočite da se petlja izvršava dok je i < n-1, a ne za i < n, jer ako A[0..n-2] sadrži n-1 najmanjih elemenata, tada posljednji element mora biti najveći, i on se nalazi na ispravnom položaju , tj. A[n-1]. Indeks najmanjeg elementa u A[i..n-1] pronalazi se sa:

imin = i; for (j = i+1; j < n; j++) if (A[j] < A[min]) imin=j;

Zamjenu vrijednosti vrši funkcijom swap();

/* Zamjena vrijednosti dva int */ void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }

Sada se može napisati funkcija za selekcijsko sortiranje, niza A koji ima n elemenata, u obliku:

void selectionSort(int *A, int n) { int i, j, imin; /* indeks najmanjeg elementa u A[i..n-1] */ for (i = 0; i < n-1; i++) { /* Odredi najmanji element u A[i..n-1]. */ imin = i; /* pretpostavi da je to A[i] */ for (j = i+1; j < n; j++) if (A[j] < A[imin]) /* ako je A[j] najmanji */ imin = j; /* zapamti njegov indeks */ /* Sada je A[imin] najmanji element od A[i..n-1], */ /* njega zamjenjujemo sa A[i]. */

Page 241: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

241

swap(&A[i], &A[imin]); } }

Analiza selekcijskog sortiranja

Svaka iteracija vanjske petlje (indeks i) traje konstantno vrijeme t1 plus vrijeme izvršenja unutarnje petlje (indeks j). Svaka iteracija u unutarnjoj petlji traje konstantno vrijeme t2 . Broj iteracija unutarnje petlje ovisi u kojoj se iteraciji nalazi vanjska petlja:

i Broj operacija u unutarnjoj petlji

0 n-1 1 n-2 2 n-3 ... ... n-2 1

Ukupno vrijeme je:

T(n) = [t1 + (n-1) t2] + [t1 + (n-2) t2] + [t1 + (n-3) t2] + ... + [t1 + (1) t2]

odnosno, grupirajući članove u oblik t1 ( …) + (...) t2 dobije se

T(n) = (n-1) t1 + [ (n-1) + (n-2) + (n-3) + ... + 1 ] t2 Izraz u uglatim zagradama predstavlja sumu aritmetičkog niza

1 + 2 + 3 + ... + (n-1) = (n-1)n/2 = (n2-n)/2, pa je ukupno vrijeme jednako:

T(n) = (n-1) t1 + [(n2-n)/2] t2 = - t1 + t1 n - t2 n/2 + t2 n2/2 Očito je da dominira član sa n2 , pa je složenost selekcijskog sortiranja jednaka O(n2) .

17.8.2 Sortiranje umetanjem Algoritam sortiranja umetanjem (eng. insertion sort) se temelji na postupku koji je sličan načinu kako se slažu igraće karte. Algoritam se vrši u u n-1 korak. U svakom koraku se umeće i-ti element u dio niza koji mu prethodi (A[0..i-1]), tako taj niz bude sortiran. Primjerice, sortiranje niza od n=6 brojeva izgleda ovako:

6 4 1 5 3 2 -> ako je A[1]< A[0], umetni A[1] u A[0..0] 4 6 1 5 3 2 -> ako je A[2]< A[1], umetni A[2] u A[0..1] 1 4 6 5 3 2 -> ako je A[3]< A[2], umetni A[3] u A[0..2] 1 4 5 6 3 2 -> ako je A[4]< A[3], umetni A[4] u A[0..3] 1 3 4 5 6 2 -> ako je A[5]< A[4], umetni A[5] u A[0..4] 1 2 3 4 5 6 -> niz je sortiran

Algoritam se može zapisati pseudokôdom:

for (i = 1; i < n; i++) {

Page 242: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

242

x = A[i]; analiziraj elemente A[0 .. i-1] počevši od indeksa j=i-1, do indeka j=0 ako je x < A[j] tada pomakni element A[j] na mjesto A[j+1] inače prekini zatim umetni x na mjesto A[j+1] }

pa se dobije funkcija:

void insertionSort(int *A, int n) { int i, j, x; for (i = 1; i < n; i++) { x = A[i]; for(j = i-1; j >= 0; j--) { if(x < A[j]) A[j+1] = A[j]; else break; } A[j+1] = x; } }

Analiza složenosti metode sortiranja umetanjem

Sortiranje umetanjem je primjer algoritma u kojem prosječno vrijeme izvršenja nije puno kraće od vremena izvršenja koje se postiže u najgorem slučaju. Najgori slučaj Vanjska petlja se izvršava u n-1 iteracija, što daje O(n) iteracija. U unutarnjoj petlji se vrši od 0 do i<n iteracija, u najgorem slučaju vrši se također O(n) iteracija. To znači da se u najgorem slučaju vrši O(n) ⋅O(n) operacija, dakle složenost je O(n2). Najbolji slučaj U najboljem slučaju u unutarnjoj petlji se vrši 0 iteracija. To nastupa kada je niz sortiran. Tada je složenost O(n). Može se uzeti da to vrijedi i kada je niz "većim dijelom sortiran" jer se tada rijetko izvršava unutarnja petlja. Prosječni slučaj U prosječnom se slučaju vrši n/2 iteracija u unutarnjoj petlji. To također daje složenost unutarnje petlje O(n), pa je ukupna složenost: O(n2).

17.8.3 Sortiranje spajanjem sortiranih podnizova (merge sort) Sortiranje metodom spajanja sortiranih podnizova (eng. merge sort) temelji se na ideji da se niz rekurzivno dijeli na dva sortirana niza, te da se zatim izvrši spajanje tih sortiranih nizova. Problem će biti riješen za slučaj da se sortira niz A[d..g], tj. od donjeg indeksa d do gornjeg indeksa g, funkcijom

void mergeSort(int *A, int d, int g);

Page 243: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

243

Rekurzivnom se podjelom niza u dva podniza, A[d..s] i A[s+1,g], koji su otprilike podjednake veličine (indeks s se odredi kao srednja vrijednost s = (d+g)/2), dolazi se do temeljnog slučaja kada u svakom nizu ima samo jedan element. Taj jedno-elementni niz je već sortiran, pa se pri "izvlačenju" iz rekurzije može vršiti spajanje sortiranih podnizova. Ovaj postupak je ilustriran na slici 4.

Slika 17. 4. Prikaz sortiranja spajanjem sortiranih podnizova

Za implementaciju ovog algoritma bitno je uočiti sljedeće:

• Ulazni niz nije nužno "fizikalno" dijeliti na podnizove, jer se podnizovi ne preklapaju. Dovoljno je zapamtiti indekse ulaznog niza koji određuju neki podniz.

• Spajanje podnizova se uvijek provodi s elementima koji su u ulaznom nizu poredani jedan do drugog; prvi podniz je A[d..s], a drugi podniz je A[s+1..g]. U tu svrhu koristit će se funkcija:

void merge(int *A, int d, int s, int g) /* Ulaz: dva sortirana niza A[d..s] i A[s+1..g] */ /* Izlaz: sortirani niz A[d..g] */

Očito je da se radi o algoritmu tipa "podijeli pa vladaj":

1. Podijeli: podijeli niz A[d,g], na način da dva podniza A[d,s] i A[s+1,g] sadrže otprilike pojednak broj elemenata. To se postiže izborom: s=(d+g)/2.

2. Vladaj: rekurzivno nastavi dijeliti oba podniza sve dok njihova veličina ne postane manja od 2 elementa (niz koji sadrži nula ili jedan element je sortirani niz).

3. Spoji: Nakon toga, pri "izvlačenju" iz rekurzije, izvrši spajanje sortiranih nizova koristeći funkciju merge(A,d,s,g).

Implementacija ovog algoritma je jednostavna;

void mergeSort(int *A, int d, int g) { if (d < r ) { /* temeljni slucaj - 1 element */ int s = (d + g) / 2; /* s je indeks podjele niza */ mergeSort(A, d, s); /* rekurzivno podijeli A[d..s] */ mergeSort(A, s+1, g); /* rekurzivno podijeli A[s+1..g]*/ merge(A, d, s, g); /* zatim spoji sortirane nizove */

Page 244: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

244

} }

Još treba definirati funkciju merge(). Ona se može realizirati na način da se formiraju dva pomoćna niza, donji[] i gornji[], u koje se kopira sortirane nizove A[d..s] i A[s+1..g]. Zatim se iz tih pomoćnih sortiranih nizova formira jedan sortirani niz u području ulaznog niza A[d..g]. Postupak je ilustriran na slici 5.

Slika 17.5. Spajanje sortiranih nizova

Implementacija je sljedeća: /* Spajanje podnizove A[d..s] i A[s+1..g] u sortirani niz A[d..g]. */ void merge(int *A, int d, int s, int g) { int m = s - d + 1; /* broj elemenata u A[d..s] */ int n = g - s; /* broj elemenata u A[s+1..g] */ int i; /* indeks u donji niz*/ int j; /* indeks u gornji niz*/ int k; /* indeks u orig. niz A */ int *donji = malloc(sizeof(int) * m); /* niz A[d..s] */ int *gornji = malloc(sizeof(int) * n); /* niz A[s+1..g] */ /* Kopiraj A[d..s] u donji[0..m-1] i A[s+1..g] u gornji[0..n-1]. */ for (i = 0, k = d; i < m; i++, k++) donji[i] = A[k]; for (j = 0, k = s+1; j < n; j++, k++) gornji[j] = A[k]; /* Usporedbom donji[0..m-1] i gornji[0..n-1], */ /* pomakni manji element na sljedeću poziciju u A[d..g]. */ i = 0; j = 0; k = d; while(i < m && j < n; ) if (donji[i] < gornji[j]) A[k++] = donji[i++]; else A[k++] = gornji[j++]; /* Preostale elemente jednostavno kopiraj */ /* Jedna od ove dvije petlje će imati nula iteracija! */ while (i < m) A[k++] = donji[i++]; while (j < n) A[k++] = gornji[j++];

Page 245: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

245

/* Dealociraj memoriju koju zauzimaju donji i gornji. */ free(donji); free(gornji); } Operacija kopiranja iz pomoćnih nizova u sortirani niz A[d..g] provodi se jednostavnom usporedbom sadržaja donji[i] i gornji[j]. Kopira se manji element u A i inkrementira pozicija u nizu. Na taj način, ova se operacija vrši u linearnom vremenu O(g-d+1). Pomoćni nizovi su formirani alociranjem memorije, stoga se na kraju funkcije vrši oslobađanje memorije. U realnoj se primjeni može koristiti brži postupak, bez alociranja memorije, na način da se pomoćni nizovi deklariraju kao globalne varijable. Dimenzija ovih globalnih nizova mora biti veća od polovine dimenzije niza koji se sortira. Na sličan način se može provesti i sortiranje datoteka.

Složenost metode spajanja podnizova

Može se na jednostavan način pokazati da je vremenska složenost ovog algoritma jednaka O(n log2 n), ukoliko se uzme da je veličina niza potencija broja 2, tj. da je n = 2m. Pošto se pri svakom rekurzivnom pozivu niz dijeli na dva podniza, sve dok duljina podniza ne postane jednaka 1, proizlazi da je broj razina podijele niza jednak log2 n. Na k-toj razini niz je podijeljen na 2k podnizova duljine n/2k. To znači da spajanje sortiranih nizova na k-toj razini ima složenost 2kxO(n/2k)= O(n), a pošto ima log2 n razina, proizlazi da je ukupna složenost jednaka O(n log2 n). Do istog rezultata se dolazi i uz znatno rigorozniju analizu složenosti. Može se pokazati da je ovo najbolji rezultat koji se može postići pri sortiranju nizova. Jedini problem ovog algoritma je što zahtijeva povećanu prostornu složenost.

17.8.1 Quicksort Quicksort je najčešće korištena metoda sortiranja. Njome se u prosječnom slučaju postiže složenost O(n log2 n), a u najgorem slučaju O(n2). To je lošiji rezultat nego kod mergesort metode, međutim prostorna složenost je manja nego kod mergesort metode, jer se sve operacije vrše na ulaznom nizu. Quicksort algoritam koristi metodu podijeli pa vladaj u sljedećem obliku:

Algoritam: Sortiraj niz A[d..g]

PODIJELI. Izaberi jedan element iz niza A[d..g] i zapamti njegovu vrijednost. Taj element se naziva pivot. Nakon toga podijeli A[d..g] u dva podniza A[d..p] i A[g+1..d] koji imaju slijedeća svojstva:

Svaki element A[d..p] je manji ili jednak pivotu. Svaki element A[p+1..g] je veći ili jednak pivotu. Niti jedan podniz ne sadrži sve elemente (odnosno ne smije biti prazan).

VLADAJ. Rekurzivno sortiraj oba podniza A[d..p] i A[p+1..g], i problem će biti riješen kada oba podniza budu imala manje od 2 elementa.

Uvjet da nijedan podniz ne bude prazan je potreban jer, kada to ne bi bilo ispunjeno, rekurzivni problem bi bio isti kao originalni problem, pa bi nastala bekonačna rekurzija. Podjela na podnizove, tako da budu ispunjeni postavljeni uvjeti, vrši se funkcijom

int podijeli(int *A, int d, int g)

koja vraća indeks podjele na podnizove.

Page 246: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

246

Podjela se vrši pomoću dva indeksa (i, j) i pivota koji se proizvoljno odabire, prema pravilu:

• Pomakni i od početka prema kraju niza, dok se ne nađe element A[i] koji je veći ili jednak pivotu,

• Pomakni j od kraja prema početku niza, dok se ne nađe element A[j] koji je manji ili jednak pivotu,

• Zatim zamijeni vrijednosti A[i] i A[j], kako bi svaki svaki element A[d..i] bio manji ili jednak pivotu, a svaki element A[j..g] veći ili jednak pivotu.

Ovaj se proces nastavlja dok se ne dobije da je i > j. Tada je podjela završena, a j označava indeks koji vraća funkcija. Ovaj uvjet ujedno osigurava da nijedan podniz neće biti prazan. Postupak je ilustriran na slici 17.6.

Slika 17.6. Postupak podjele niza A[1..6]. Za pivot je odabran prvi element A[1] vrijednosti 6. Označeni su indeksi (i,j), a potamnjeni elementi pokazuju koje se

elemente zamjenjuje. Linija podjela je prikazana u slučaju kada indeks i postane veći od indeksa j.

/* Podijeli A[d..g] u podnizove A[d..p] i A[p+1..g], * gdje je d <= p < g, i * A[d..p] <= A[p+1..g]. * Početno se izabire A[d] kao pivot * Vraća indeks podjele. */ int podijeli(int *A, int d, int g) { int i = d - 1; /* index lijeve strane A[d..g] */ int j = g + 1; /* index desne strane A[d..g] */ int pivot = A[d]; /* izbor pivot-a */ while (1) { /* Nadji sljedeći manji indeks j za koji je A[j] <= pivot. */ while (A[--j] > pivot) {} /* Nadji sljedeći veći indeks i za koji je A[i] >= pivot. */ while (A[++i] < pivot) {} /* Ako je i >= j, raspored je OK. */

Page 247: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

247

/* inače, zamijeni A[i] sa A[j] i nastavi. */ if (i < j) swap(&A[i], &A[j]); else return j; } }

Pomoću ove funkcije se iskazuje kompletni quicksort algoritam:

/* Sortiraj niz A[d..g] - quicksort algoritmom * 1.Podijeli A[d..g] u podnizove A[d..p] i A[p+1..g], * d <= p < g, * i svaki element A[d..p] je <= od elementa A[p+1..g]. * 2.Zatim rekurzivno sortiraj A[d..p] i A[p+1..g]. */ void quicksort(int *A, int d, int g) { if (d < g) /* završava kada podniz ima manje od 2 elementa */ { int p = podijeli(A, d, g); /* p je indeks podjele */ quicksort(A, d, p); /* rekurzivno sortiraj A[d..p]*/ quicksort(A, p+1, g); /* rekurzivno sortiraj A[p+1..g]*/ } }

Analiza složenosti quicksort algoritma

Prvo treba uočiti da je vrijeme za podjelu podnizova od n elemenata, proporcionalno broju elemenata cn (konstanta c > 0), tj. složenost je O(n). Analizirajmo zatim broj mogućih rekurzivnih poziva.

Najbolji slučaj U najboljem slučaju nizovi se dijele na dvije jednake polovine. Pošto je tada ukupan broj razlaganja jednak log2 n dobije se da je složenost O(n)⋅O(log2 n) = O(n log2 n). Najgori slučaj U najgorem slučaju podjela na podnizove se vrši tako da je u jednom podnizu uvijek samo jedan element. To znači da bi tada nastaje n rekurzivnih poziva, pa je ukupna složenost O(n) ⋅O(n)= O(n2). Najgori slučaj nastupa kada je niz sortiran ili "skoro" sortiran. Prosječni slučaj Analiza prosječnog slučaja je dosta komplicirana. Ovdje se neće izvoditi. Važan je zaključak da se u prosječnom slučaju dobija složenost koja je bliska najboljem slučaju O(n log2 n). Postavlja se pitanje: Kako odabrati pivot element?

U prethodnom algoritmu za pivota je odabran početni element A[d]. U praksi se pokazalo da je znatno povoljnije izbor pivota vršiti po nekom slučajnom uzorku. Recimo, ako se slučajno

Page 248: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

248

odabere indeks ix i element A[ix] kao pivot, tada se smanjuje mogućnost da se dobije nepovoljna podjela kod skoro sortiranih nizova.

Sljedećim postupkom se dobiva vjerojatnost dobre podjele 50%. Odaberu se tri indeksa po slučajnom uzorku, a zatim se za pivot odabere jedan od ova tri elementa koji je najbliži njihovoj srednjoj vrijednosti (medijan). Dublje opravdanje ove metode ovdje neće biti dano. Pokazalo se, da uz primjenu takovog postupka, quicksort predstavlja najbrži poznati način sortiranja nizova, pa je implementiran u standardnoj biblioteci C jezika.

17.9 Zaključak Glavna korist od korištenja rekurzije kao programske tehnike je:

• rekurzivne funkcije su jasnije, jednostavnije, kraće, i lakše ih je razumjeti od odgovarajućih ne-rekurzivnih funkcija, jer najčešće neposredno odgovaraju apstraktnom algoritmu rješenja problema.

Glavni nedostatak korištenja rekurzije kao programske tehnike je:

• izvršenje rekurzivnih funkcija troši više računarskih resursa od odgovarajućih ne-rekurzivnih funkcija.

U mnogo slučajeva rekurzivne se funkcije mogu transformirati u ne-rekurzivne funkcije. To se obavlja jednostavno u slučaju korištenja “repne rekurzije”. Analiza složenosti algoritama se uglavnom provodi analizom vremena izvršenja programa. Složenost algoritma se iskazuje “veliki-O” notacijom. U analizi složenosti algoritama često se koristi princip matematičke indukcije. On se postavlja na sličan način kao i rekurzivna pravila: definira se temeljni slučaj i pravilo indukcije (rekurzije). Kao primjer analize algoritama, analizirano je nekoliko metoda za sortiranje nizova. Pokazano je da se korištenjem metode “podijeli pa vladaj” dobivaju efikasni algoritmi (quicksort i mergesort).

Page 249: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

249

18 Samoreferentne strukture i liste

Naglasci: • samoreferentne strukture • jednostruko vezana lista • dvostruko vezana lista • operacije s listom • sortirane liste • implementacija ADT STACK pomoću jednostruko vezane liste • implementacija ADT QUEUE pomoću jednostruko vezane liste • implementacija ADT DEQUEUE pomoću dvostruko vezane liste

18.1 Samoreferentne strukture i lista Karakteristika je strukturnih tipova da članovi strukture mogu biti bilo kojeg prethodno

definiranog tipa. To znači da struktura ne može sadržavati "samu sebe", jer se ona tek definira. Dozvoljeno je pak da struktura sadrži pokazivač na "sebe". Takve strukture se nazivaju samoreferentne ili rekurzivne strukture.

Razmotrimo strukturu node koja pored nekog elementa (elem) sadrži pokazivač (next) koji se može inicirati da pokazuje na objekt istog strukturnog tipa. struct node { int elem; struct node * next; }

Primjerice, neka su definirama tri objekta (N1, N2, N3) i pokazivač List, tipa structnode: struct node N1, N2, N3;

struct node *List;

tada je moguće inicirati pokazivač List tako da pokazuje na bilo koji od ova tri objekta. Uzet ćemo da pokazivač List pokazuje na objekt N1:

List = &N1;

Pošto objekti N1, N2 i N3 sadrže član next, koji je pokazivač tipa struct node, može se uspostaviti veza između objekata, tako da se pokazivaču next jednog objekta pridijeli adresa drugog objekta. Primjerice, sljedećim naredbama se definira sadržaj i veza sa drugim objektom.

N1.elem = 1; N2.elem = 2; N3.elem = 3; N1.next = &N2; /* prvi povezujemo s drugim */ N2.next = &N3; /* drugi povezujemo s trećim */ N3.next = NULL; /* treći element ostavimo nepovezanim */

Page 250: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

250

To stanje se može ilustrirati sljedećom slikom:

Slika 18.1 Vezana lista

Ovakova struktura se naziva vezana lista. Sastoji se od međusobno povezanih objekata koji se nazivaju čvorovi (eng. node), jer ilustracija prikazuje graf koji se sastoji od čvorova i usmjerenih veza. Prvi čvor vezane liste se obično naziva glava liste. Njegovu adresu se bilježi u pokazivaču List. Posljednji čvor se naziva kraj liste. Pokazivač next tog čvora se uvijek postavlja na vrijednost NULL, što služi kao oznaka kraja liste.

Strukturalna definicija: Vezana lista skup elemenata, koji je realiziran na način da je svaki element dio čvora, koji sadrži i vezu sa sljedećim čvorom. Prazna lista ne sadrži ni jedan čvor.

Do sada je pojam liste korišten za označavanje uređenog skupa elemenata A={a1,a2,...an}. Uređenost skupa je određena redom elemenata liste. Pojam liste se sada koristi i za označavanje strukture podataka koju se može definirati rekurzivno:

Apstraktna definicija: Lista je linearna struktura podataka koja sadrži nula ili više elemenata, a ima dvije karakteristike: 1. Lista može biti prazna 2. Lista se sastoji od elementa liste iza kojeg slijedi lista elemenata

Prema ovoj definiciji lista se može realizirati kao niz elemenata ili kao vezana lista. Listi, koja se formira kao niz elemenata, jednostavno se pristupa elementima liste pomoću indeksa niza. Znatno je kompliciraniji postupak ako treba umetnuti ili izbrisati element liste. Bit će pokazano da se te operacije, pri korištenju vezane liste, izvode brzo i jednostavno. Još je jedna bitna razlika između niza i vezane liste. Nizovi zauzimaju fiksnu veličinu memorije, a veličina vezane liste (i zauzeće memorije), je određena brojem elemenata liste. Ako se umetne element, vezana lista se smanjuje, a ako se izbriše element vezana lista se smanjuje.

Zbog ovog svojstva vezana lista je dinamička struktura podataka, a zbog svojstva da služi prikupljanju podataka često se kaže da predstavlja kolekciju elemenata.

18.2 Operacije s vezanom listom Cilj je pokazati kako se vrše operacije s vezanom listom kao dinamičkom strukturom

podataka. Najprije će biti pokazano kako se formira lista cijelih brojeva. Polazi se od definirane strukture “node”, koja sadrži jedan element kolekcije i vezu kojom se pristupa elementima kolekcije, u sljedećem obliku:

Page 251: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

251

typedef int elemT; typedef struct node Node; typedef Node *LIST; struct node { elemT elem; /* element liste */ Node *next; /* pokazuje na slijedeći čvor */ }

Tip Node označava čvor liste, a tip LIST označava pokazivač na čvor liste. Deklaracija

LIST List = NULL;

inicijalizira varijablu List koja, prema apstraktnoj definiciji, predstavlja praznu listu, a programski predstavlja pokazivač koji na čvor koji sadrži glavu liste. Zvat ćemo ga pokazivač liste.

Proces stvaranja vezane liste počinje stvaranjem čvora vezane liste. Formira se alociranjem memorije za strukturu node. List = malloc(sizeof (Node));

Nakon toga se upisuje vrijednost elementa liste, primjerice List->elem = 5;

Upitnik označava da nije definiran sadržaj pokazivača List->next. Uobičajeno je da se on postavi na vrijednost NULL, jer pokazuje na “ništa”. Time se ujedno označava kraj liste (po analogiju sa stringovima, gdje znak '\0' označava kraj stringa). To simbolički prikazujemo slikom: List->next = NULL;

Sada se u ovu kolekciju može dodati još jedan element, sljedećim postupkom. Prvo, se formira čvor kojem pristupamo pomoću pokazivača q. Node *q=malloc(sizeof(Node));

q->elem =7;

Zatim se ova dva čvora “povežu” sljedećim naredbama:

q->next = List;

Page 252: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

252

List = q;

Novi element je postavljen na početak liste. Pokazivač q više nije potreban jer on sada ima

istu vrijednost kao pokazivač List (q se koristi samo kao pomoćni pokazivač za formiranje vezane liste). Na ovaj se način može formirati lista s proizvoljnim brojem članova. Početak liste (ili glava liste) je zabilježen u pokazivaču kojeg se tradicionalno naziva List. Kraj liste se je obilježen s NULL vrijednošću “next” pokazivača krajnjeg čvora liste.

U ovom primjeru lista je formirana tako da se novi čvor postavlja na glavu liste. Kasnije će biti pokazano kako se novi čvor može dodati na kraj liste ili umetnuti između dva čvora liste.

Sada će postupak formiranja i manipuliranja s listom biti opisan s nekoliko temeljnih funkcija, koje se mogu lako prilagoditi različitim tipovima elementa liste. Imena funkcija koje su neovisne o tipu elementa liste bit će zapisana malim slovima. Ostale funkcije, čija imena sadrže velika slova, ovise o tipu elementa liste. Kasnije će biti pokazano kako se te funkcije prilagođavaju tipu elementa liste.

18.2.1 Formiranje čvora liste U radu s listom, za formiranje novog čvora liste koristit će se funkcija newNode().Ona

prima argument tipa elemT, a vraća pokazivač na alocirani čvor ili NULL ako se ne može izvršiti alociranje memorije.

Node *newNode(elemT x) { Node *n = malloc(sizeof(Node)); if(n != NULL) n->elem = x; return n; }

Također, potrebno je definirati funkciju freeNode(), kojom se oslobađa memorija koju zauzima čvor.

void freeNode(Node *p) { /* ako se element liste formira alociranjem memorije * tada ovdje treba dodati naredbu za dealociranje * elementa liste. */ free(p); /* dealociraj čvor liste */ }

Uočite da implementacija ove funkcije ovisi o definiciji elementa liste. Ako se element liste formira alociranjem memorije, primjerice, ako je element liste dinamički string, tada u ovu funkciju treba dodati naredbu za dealociranje elementa liste.

18.2.2 Dodavanje elementa na glavi liste Prethodno opisani postupak formiranja liste, na način da se novi čvor umeće na glavu liste,

implementiran je u funkciji koja se naziva add_front_node().

void add_front_node(LIST *pList, Node *n) { if(n != NULL) /* izvrši samo ako je alociran element */ {

Page 253: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

253

n->next = *pList; *pList = n; } }

Prvi argument funkcije je pokazivač na pokazivač liste. Ovakvi prijenos argumenta je nužan jer se u funkciji mijenja vrijednost pokazivača liste. Drugi argument je pokazivač čvora koji se unosi u listu.

Dodavanje elementa x u listu List sada se vrši jednostavnim pozivom funkcije:

add_front_node(&List, newNode(x));

Složenost ove operacije je O(1).

18.2.3 Brisanje čvora na glavi liste Brisanje čvora glave liste je jednostavna operacija. Njome čvor, koji slijedi iza glave liste,

(List->next) postaje glava liste, a trenutni čvor glave liste (n) se dealocira iz memorije. To ilustrira slika 18.2.

Node *n = List; List = List->next; freeNode(n);

Slika 18.2 Brisanje s glave liste

Postupak brisanja glave liste se formalizira funkcijom delete_front_node().

void delete_front_node(LIST *pList) { Node *n = *pList; if(n != NULL) { *pList = *pList->next; freeNode(n); } }

Uočite da se i u ovoj funkciji mijenja vrijednost pokazivača glave liste, stoga se u funkciju prenosi pokazivač na pokazivač liste. Složenost ove operacije je O(1).

18.2.4 Brisanje cijele liste Brisanje cijele liste se vrši funkcijom delete_all_nodes(), na način da se brišu svi čvorovi sprijeda.

void delete_all_nodes( LIST *pList ) { while (*pList != NULL) delete_front_node(pList); }

Page 254: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

254

18.2.5 Obilazak liste Ako je poznat pokazivač liste uvijek se može odrediti pokazivač na sljedeći element

pomoću “next” pokazivača.

Node *ptr = List->next;

Dalje se sukcesivno može usmjeravati pokazivač ptr na sljedeći element liste naredbom

ptr = ptr->next;

Na taj se način može pristupiti svim elementima liste. Taj postupak se zove obilazak liste (eng. list traversing). Obilazak liste završava kada je ptr == NULL. Primjer: Pretpostavimo da želimo ispisati sadržaj liste redoslijedom od glave prema kraju liste. To možemo ostvariti sljedećim programom:

Node *p = List; /* koristi pomoćni pokazivač p */ while (p != NULL) /* ponavljaj do kraja liste */ { printf("%d\n", p->elem); /* ispiši sadržaj elementa */ p = p->next; /* i postavi pokazivač na */ /* sljedeći element liste */ }

U ovom primjeru na sve elemente liste je primijenjena ista funkcija. Postupak kojim se na sve elemente liste djeluje nekom funkcijom može se poopćiti funkcijom list_for_each() u sljedećem obliku:

void list_for_each(LIST L, void (*pfun)(Node *)) { while( L != NULL) { (*pfun)(L); L = L->next; } }

Prvi argument ove funkcije je lista, a drugi argument ove funkcije je pokazivač na funkciju, koja se primijenjuje na sve elemente liste. To može biti bilo koja void funkcija kojoj je argument tipa Node *. Definiramo li funkciju:

void printNode(Node *n) { printf("%d\n", n->elem);}

tada poziv funkcije:

list_for each(L, printNode);

ispisuje sadržaj cijele liste

Kada je potrebno izvršiti obilazak liste od kraja prema glavi liste, ne može se primijeniti iterativni postupak. U tom slučaju se može koristiti rekurzivna funkcija reverse_list_for_each().

void reverse_list_for_each(LIST L, void (*pfun)(Node *)) { if (L == NULL) return; reverse_list_for_each(L->next, pfun); (*pfun)(L);

Page 255: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

255

}

Obilazak liste je nužan i kada treba odrediti posljednji čvor liste. To vrši funkcija last_node().

Node *last_node(LIST L) {/*vraća pokazivač na krajnji čvor liste*/ if(L == NULL) return NULL; while ( L->next != NULL) L = L->next; return L; }

Broj elemenata koji sadrži lista daje funkcija num_list_elements().

int num_list_elements(LIST L) {

int num = 0; while ( L != NULL) { num++; L = L->next; } return num; /* broj elemenata liste */ }

18.2.6 Traženje elementa liste Često je razlog za obilazak liste traženje elementa liste. U slučaju kada je element liste

prostog skalarnog tipa može se koristiti funkciju find_list_element().

/* funkcija: find_list_element * ispituje da li lista sadrži element x * Argumenti: * x – element kojeg se traži * List – pokazivač na glavu liste * Vraća: * pokazivač na čvor koji sadrži x, ili NULL ako x nije u listi */ Node *find_list_element(LIST L, elemT x) { while( L != NULL && p->elem != x ) L = L->next; return L; }

Pretraživanje liste ima složenost O(n), gdje je n broj elemenata liste.

18.2.7 Dodavanje čvora na kraju liste Dodavanje čvora na kraju liste vrši se na sljedeći način:

Ako lista još nije formirana, tj. ako je List==NULL, koristi se postupak opisan u funkciji add_front_node().

Ako je List != NULL, tada 1. Obilaskom liste odredi se pokazivač krajnjeg čvora liste. Taj pokazivač,

nazovimo ga p, ima karakteristiku da je p->next == NULL.

Page 256: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

256

2. Zatim se p->next postavi na vrijednost pokazivača čvora kojeg se dodaje u listu.

3. Pošto dodani čvor predstavlja novi kraj liste njega se zaključuje s NULL . Ovaj postupak je realiziran funkcijom add_back_node();

/* funkcija: add_back_node * --------------------- * Dodaje čvor na kraj liste * Argumenti: * pList - pokazivač na pokazivač liste * n – pokazivač na čvor koji se dodaje u listu */ void add_back_node(LIST *pList, Node *n) { if(n == NULL) /* Izvršava se samo ako je */ return; /* alociran čvor. */ if(*pList == NULL) { /* Ako lista nije formirana */ *pList = n; /* iniciraj pokazivač */ n->next = NULL; } else { LIST p = *pList; while ( p->next != NULL) /* 1. odredi krajnji čvor */ p = p -> next; p ->next = n; /* 2. dodaj novi čvor */ n->next = NULL; /* 3. označi kraj liste */ } }

Ovu funkciju se koristi po istom obrascu kao i funkciju add_front_node(), tj. novi element (x) se dodaje naredbom:

add_back_node(&List, newNode(x));

18.2.8 Umetanje i brisanje čvora unutar liste Postupak umetanja ili brisanja čvora n ilustrira slika 18.3

Slika 18.3 Umetanje i brisanje čvora unutar vezane liste

Brisanje čvora koji slijedi iza čvora “p” ( na slici, to je čvor “n”) vrši se naredbama:

Page 257: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

257

n = p->next; /* odredi sljedeći */ p->next = n->next; freeNode(n);

Ako treba izbrisati čvor “n”, potrebno je odrediti čvor “p”, koji mu prethodi.

p = /* odredi čvor koji prethodi čvoru n*/ p->next = n->next; freeNode(n);

Umetanje čvora “n” iza čvora “p” se provodi naredbama:

n->next = p->next; p->next = n;

Umetanje čvora “n” iza čvora “x” provodi se tako da se najprije odredi čvor “p” koji prethodi čvoru “x”, a zatim se provodi prethodni postupak. Operaciju kojom se određuje čvor koji prethodi čvoru “n” realizira se funkcijom get_previous_node(), koja vraća pokazivač na prethodni čvor, ili NULL ako ne postoji prethodni čvor.

Node *get_previous_node(LIST List, Node *n ) { Node *t, *pre; t = pre = List; /* start od glave */ while( (t != n) /* dok ne pronađeš n */ && (t->next != NULL )) { pre = t; /* pamti prethodni */ t = t->next ; } return (pre); /* vrati prethodni */ }

Sada se postupak brisanja čvora može realizirati funkcijom delete_node(). Njome se iz liste briše čvor “n”.

void delete_node(LIST *pList, Node *n) { if(*pList == NULL || n == NULL) return; if(*pList == n) { /* ako n pokazuje glavu */ *pList = n->next; /* odstrani glavu */ } else { Node *pre = get_previous_node(*pList, n ); pre->next = n->next; } freeNode(n); /* dealociraj čvor */ }

18.2.9 Brisanje čvora na kraju liste Brisanje čvora na kraju liste je jednako komplicirana operacija kao i brisanje unutar liste, jer se i u ovom slučaju mora odrediti čvor koji mu prethodi. To je realizirano funkcijom delete_back_node().

/* Funkcija: delete_back_node

Page 258: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

258

* odstranjuje čvor na kraju liste * Argumenti: * pList - pokazivač na pokazivač liste. */ void delete_back_node(LIST *pList) { Node *pre, back; /* pre – prethodni */ if (*pList == NULL) /* back – krajnji */ return; back = pre = *pList; /* start od glave */ while(back->next != NULL ) { /* pronađi kraj liste*/ pre = back; /* zapamti prethodni */ back = back->next ; } if(back == *pList) /* ako je krajnji = glava */ *pList == NULL; /* napravi praznu listu */ else /* inače */ pre->next = NULL; /* prethodni postaje krajnji*/ freeNode(back); /* dealociraj čvor */ }

Primjer: U program testlist.c testiraju se prije opisane funkcije.

void printNode(Node *n) { if(n) printf( "%d ", n->elem ); } void printList(LIST List) { if( L == NULL ) printf( "Lista je prazna\n" ); else list_for_each(L, printNode); printf( "\n" ); } int main( ) { LIST List; int i; /* obvezno iniciraj listu */ List = NULL ; /* formiraj listu s 10 cijelih brojeva */ for( i = 0; i < 10; i++ ) { add_front_node(&List, newNode(i)); printList(List); } /* izbriši prednji i stražni element */ delete_front_node(&List); delete_back_node(&List); printList(List); if(find_list_element(List, 5) != NULL) printf( "pronadjen element :%d\n", 5 );

Page 259: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

259

if(find_list_element(List, 9) == NULL) printf( "Nije pronadjen element :%d\n", 9 ) ; add_back_node(&List, newNode(9)); printList(List); delete_all_nodes(&List) ; printList(List); return 0; }

Nakon izvršenja dobije se ispis:

0 1 0 2 1 0 3 2 1 0 4 3 2 1 0 5 4 3 2 1 0 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 8 7 6 5 4 3 2 1 pronadjen element :5 Nije pronadjen element :9 8 7 6 5 4 3 2 1 9 Lista je prazna

18.3 Što može biti element liste Funkcije koje su u prethodnom poglavlju korištene za rad s vezanom listom mogu se, u

neizmijenjenom obliku, primijeniti jedino na listu cijelih brojeva. Sada će biti pokazano kako se te funkcije mogu prilagoditi za rad s listama koje sadrže proizvoljan tip podataka.

Uzmimo za primjer da u listi treba zapisati podatke o studentima: 1) njihovo ime i 2) ocjenu. To se može realizirati na dva načina:

Prvi je način da se čvor liste formira tako da sadrži više različitih tipova podataka, primjerice:

typedef struct snode StudentNode; typedef StudentNode *STUDENTLIST; typedef struct snode { StudentNode *next; char *ime; int ocjena; } StudentNode;

Drugi je način da se element liste definira strukturom koja ima više članova, primjerice:

typedef struct student_info { char *ime; int ocjena; }Student; struct snode

Page 260: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

260

{ StudentNode *next; /* pokazuje na slijedeći čvor */ Student elem; /* element liste tipa Student */ };

U oba slučaja lista se formira istim postupkom kao i lista stringova, jedino je potrebno modificirati funkcije newNode(), freeNode(), Find() i Print(). Primjerice, funkcija newNode() u ovom slučaju ima dva argumenta: ime i ocjenu, a realizira se na sljedeći način. Prvi način Drugi način StudentNode *newNode(char *ime, int ocjena) { StudentNode *novi = malloc(sizeof(StudentNode)); if(novi != NULL) { novi->ime = strdup(ime); novi->ocjena = ocjena; } return novi; }

StudentNode *newNode(char *ime, int ocjena) { StudentNode *novi = malloc(sizeof(StudentNode)); if(novi != NULL) { novi->elem.ime = strdup(ime); novi->elem.ocjena = ocjena; } return novi; }

18.4 Lista sa sortiranim redoslijedom elemenata U dosadašnjim primjerima podaci su bili svrstani u listi redoslijedom kojim su i uneseni u

listu. Često je pak potrebno da podaci u listi budu u sortiranom redoslijedu. To se može postići na dva načina. Prvi je način da se izvrši sortiranje liste, a drugi je način da se već pri samom unošenju podataka ostvari sortirani redoslijed elemenata. S obzirom da je namjena liste da služi kao kolekcija u koju se često unose i odstranjuju elementi, ovaj drugi pristup se češće koristi.

Izrada liste s podacima o imenu i ocjeni studenta je tipičan primjer korištenja liste u kojem je poželjno imati sortiran redoslijed elemenata. Sada će biti prikazana izvedba modula za manipuliranje s listom studenata.

Prema uputama za formiranje modula, iznesenim u poglavlju 9, modul za listu studenata će se formirati od sljedećih datoteka:

1. datoteka specifikacije modula ("studentlist.h") sadrži deklaracije funkcija i struktura koje se koriste za rad s listom.

2. datoteka implementacije ("studentlist.c") sadrži definicije varijabli i implementaciju potrebnih funkcija.

3. datoteka primitivnih funkcija za manipuliranje listom ("listprim.c") koje su definirane u prethodnom poglavlju. Ova datoteka će se koristiti isključivo kao #include datoteka u datoteci "studentlist.c". Sve funkcije iz ove datoteke su označene prefiksom static, što znači da će biti vidljive samo u datoteci "studentlist.c".

4. Testiranje modula se provodi programom studlist-test.c. Datoteka specifikacije –"studentlist.h"

#ifndef _STUDENTLIST_H_ #define _STUDENTLIST_H_ /* sortirana lista za evidenciju studenata */ typedef struct stnode StudentNode; typedef StudentNode *STUDENTLIST;

Page 261: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

261

struct stnode { StudentNode *next; char *ime; int ocjena; }; void StudentL_insertSorted (STUDENTLIST *pList, char *ime, int ocjena); /* U listu umeće podatke o studentu: ime i ocjenu * Ako ime već postoji, zamjenuje se vrijednost ocjene. * Lista je uvijek sortirana prema imenu studenta */ StudentNode *StudentL_find(STUDENTLIST L, char *s); /* Traži čvor liste u kojem je ime jednako stringu s * Vraća pokazivač na čvor, ili NULL ako nije pronađen string */ void StudentL_deleteNode(STUDENTLIST *pList, StudentNode *n); /* Odstranjuje čvor liste na kojeg pokazuje n */ void StudentL_delete(STUDENTLIST *pList) ; /* Odstranjuje sve čvorove liste */ int StudentL_size(STUDENTLIST List); /* Vraća broj elemanate liste */ void StudentL_print(STUDENTLIST L ); /* Ispisuje sadržaj liste */ #endif

Sve deklaracije su napisane unutar makro naredbi

#ifndef _STUDENTLIST_H_ #define _STUDENTLIST_H_ ........ deklaracije ..... #endif

To osigurava da se u jednoj izvornoj datoteci samo jedan put može umetnuti h-datoteka. Čvor liste je opisan strukturom StudentNode, a tip pokazivač na taj čvor je nazvan STUDENTLIST. Imena svih funkcija počinju prefiksom StudentL_ , a ostatak imena je u skladu s imenom ekvivalentnih primitivnih funkcija koje su definirane u prethodnom poglavlju. Datoteka implementacije – "studentlist.c"

#include <stdio.h> #include <stdlib.h> #include "studentlist.h" static StudentNode *newNode(char *ime, int ocjena) { StudentNode *n = malloc(sizeof(StudentNode)); if(n != NULL) { n->ime=strdup(ime);

Page 262: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

262

n->ocjena = ocjena; } return n; } static void freeNode(StudentNode *n) { if(n) { free(n->ime); free(n); } } #define LIST STUDENTLIST #define Node StudentNode #include "listprim.c" #undef LIST #undef Node int StudentL_size(STUDENTLIST List) { return num_list_elements(List); } void StudentL_deleteNode(STUDENTLIST *pList, StudentNode *n) { delete_node(pList, n); } void StudentL_delete(STUDENTLIST *pList) { delete_all_nodes(pList); } void StudentL_insertSorted (STUDENTLIST *pList, char *ime, int ocjena) { StudentNode *tmp = *pList; StudentNode *pre = NULL; StudentNode *n; int cmp; if (!*pList) { /* ako je lista prazna, formiraj prvi čvor */ n= newNode (ime, ocjena); n->next=NULL; *pList = n; return; } /* nađi zadnji čvor (i njemu prethodni) u kojem je ime manje ili jednako zadanom imenu, ili kraj liste */ cmp = strcmp(ime, tmp->ime); while ((tmp->next) && (cmp > 0)) { pre = tmp; tmp = tmp->next; cmp = strcmp(ime, tmp->ime); } /* ako ime već postoji, zamijeni ocjenu */ if(cmp == 0) { tmp->ocjena = ocjena; return; }

Page 263: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

263

/*inače dodaj novi čvor */ n= newNode (ime, ocjena); n->next=NULL; /* ako je dosegnut kraj liste, dodaj na kraj liste */ if ((tmp->next == NULL) && (cmp > 0)) { tmp->next = n; return; } /*ili umetni iza prethodnog*/ if (pre != NULL) { pre->next = n; n->next = tmp; } /*ili stavi na početak liste ako je prethodni == NULL*/ else { n->next = *pList; *pList = n; } } StudentNode *StudentL_find(STUDENTLIST L, char *ime) { while( L != NULL && strcmp(L->ime, ime) ) L = L->next; return L; } void PrintStInfo(StudentNode *n ) { printf( "Ime:%s, ocjena:%d \n", n->ime, n->ocjena ); } void StudentL_print(STUDENTLIST L ) { if( L == NULL ) printf( "Lista je prazna\n" ); else list_for_each(L, PrintStInfo); printf( "\n" ); }

Izvedba svih funkcija je izvršena po poznatom obrascu, jedino je potrebno objasniti algoritam za sortirano umetanje čvorova liste. On glasi:

Algoritam: Sortirano umetanje čvora liste (ime,ocjena) Ako je lista prazna, Formiraj čvor s zadanim imenom i ocjenom i stavi ga na početak liste inače

Nađi zadnji čvor (i njemu prethodni) u kojem je ime leksikografski manje ili jednako zadanom imenu, ili kraj liste Ako čvor s zadanim imenom već postoji, Zamijeni ocjenu inače Formiraj čvor s zadanim imenom i ocjenom. Ako je dosegnut kraj liste Dodaj na kraj liste

Page 264: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

264

inače Umetni iza prethodnog

ili stavi na početak liste ako je prethodni == NULL Testiranje modula se provodi programom studlist_test.c.

/* Datoteka: studlist_test.c */ #include <stdio.h> #include <stdlib.h> #include "studentlist.h" int main( ) { STUDENTLIST List, n; int i; List = NULL ; StudentL_insertSorted (&List, "Bovan Marko", 5); StudentL_insertSorted (&List, "Radic Jure", 2); StudentL_insertSorted (&List, "Marin Ivo", 3); StudentL_insertSorted (&List, "Bovan Marko", 2); printf("Lista ima %d elemenata\n", StudentL_size(List)); StudentL_print(List ); n = StudentL_find(List, "Bovan Marko"); if(n != NULL) StudentL_deleteNode(&List, n); StudentL_print(List); StudentL_delete(&List) ; return 0; }

Program se kompilira komandom:

c:> cl studlist_test.c studentlist.c

Nakon izvršenja, dobije se sljedeći ispis:

Lista ima 3 elemenata Ime:Bovan Marko, ocjena:2 Ime:Marin Ivo, ocjena:3 Ime:Radic Jure, ocjena:2 Ime:Marin Ivo, ocjena:3 Ime:Radic Jure, ocjena:2

Zadatak: Objasnite zašto u prethodnom ispisu lista početno ima tri elementa, iako je u programu četiri puta primijenjena funkcija StudentL_insertSorted(). Zadatak: Napišite program u kojem korisnik unosi rezultate ispita (ime i ocjenu studenta) u sortiranu listu. Unos završava kada se otkuca “prazno ime” ili ocjena 0. Nakon toga treba

Page 265: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

265

rezultate ispita upisati u tekstualnu datoteku, na način da se u jednom retku ispiše redni broj, ime i ocjena studenta.

18.5 Implementacija ADT STACK pomoću linearne liste U poglavlju 16 izvršena je implementacija ADT STACK pomoću niza elemenata. Stog se

može efikasno implementirati i pomoću linearne liste. Uzmemo li da je specifikacija ADT STACK ista kao i specifikacija ADT za implementaciju pomoću niza u datoteci "stack.h", koja je opisana u poglavlju 16, implementacija se može napraviti na način kako je opisano u datoteci "stack-list.c".

Sada STACK predstavlja pokazivač na strukturu stack, koja ima samo jedan element, pokazivač na čvor linearne liste. Taj pokazivač je nazvan top, i on pokazuje na glavu liste. Operacija Push() je implementirana kao umetanje elementa na glavu liste, a pop() kao brisanje elementa s glave liste.

/* Datoteka: stack-list.c */ /* Implementacija ADT STACK pomoću vezane liste */ #include <stdlib.h> #include <assert.h> #include "stack.h" /* typedef int stackElemT; definiran u stack.h*/ /* typedef struct stack *STACK; definiran u stack.h*/ struct node { stackElemT elem; struct node *next; }; struct stack { struct node *top; }; static void stack_error(char *s) { printf("\nGreska: %s\n", s); exit(1); } STACK stack_new(void) {/*alocira ADT STACK*/ STACK S = malloc(sizeof(struct stack)); if(S != NULL) S->top = NULL; return S; } void stack_free(STACK S) {/*dealocira ADT STACK*/ struct node *n; assert(S != NULL); if( !stack_empty(S)) for (n = S->top; n != NULL; n = n->next) free(n); } int stack_empty(STACK S)

Page 266: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

266

{/* vraća != 0 ako je stog prazan*/ assert(S != NULL); return ( S->top==NULL); } unsigned stack_count(STACK S) {/*vraća broj elemenata na stogu*/ unsigned num = 0; struct node *n; assert(S != NULL); if( !stack_empty(S)) for (n = S->top; n != NULL; n = n->next) num++; return num; } stackElemT stack_top(STACK S) {/*vraća vrijednost elementa na vrhu stoga*/ assert(S != NULL); return S->top->elem; } stackElemT stack_pop(STACK S) {/* Skida čvor s vrha stoga */ stackElemT el; struct node *n; assert(S != NULL); if (stack_empty(S)) stack_error("Stog je prazan"); n = S->top; el = n->elem; S->top = n->next; free(n); return el; } void stack_push(STACK S, stackElemT el) {/* Ubacuje čvor na vrh stoga*/ struct node *n; assert(S != NULL); n = malloc(sizeof(struct node)); if (n != NULL) { n->elem = el; n->next = S->top; S->top = n; } else printf(" Nema dovoljno memorije!\n"); }

Testiranje ove implementacije se provodi s programom stack-test.c, koji je opisan u poglavlju 16. Jedina razlika je da se u u tom programu umjesto uključenja datoteke "stack-arr.c" uključi datoteka "stack-list.c". Zadatak: Napišite ADT imena STRSTACK za stog na kojeg će se stavljati stringovi. Vodite računa da treba alocirati i dealocirati memoriju koju zauzima string. Napišite program za testiranje ADT STRSTACK.

Page 267: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

267

18.6 Implementacija ADT QUEUE pomoću vezane liste Red se može jednostavno implementirati i pomoću vezane liste. Za implementacija reda

pomoću vezane liste koristit će se prethodna specifikaciju ADT-a QUEUE (danu u datoteci "queue.h"). U ovoj implementaciji QUEUE predstavlja pokazivač na strukturu queue, koja ima dva člana: front i back. To su pokazivači na prednji i stražnji element liste. Operacijom put() stavlja se element na kraj liste, a operacijom get() skida se element s glave liste.

Slika 18.4 Operacije s listom koju se koristi kao red za čekanje

/* Datoteka: queue-list.c */ /* Queue realiziran pomoću vezane liste*/ #include <stdio.h> #include <stdlib.h> #include <assert.h> #include "queue.h" /* typedef int queueElemT; */ /* typedef struct queue *QUEUE; */ struct node { queueElemT elem; struct node *next; }; struct queue { struct node * front; struct node * back; }; QUEUE queue_new(void) { /*alocira ADT QUEUE*/ QUEUE Q = malloc(sizeof(struct queue)); if(Q != NULL){ Q->front = Q->back = NULL; } return Q; } void queue_free(QUEUE Q) {/*dealocira ADT QUEUE*/ struct node *n; assert(Q != NULL); while ((n = Q->front) != NULL ) { Q->front = n ->next; free(n); }

Page 268: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

268

free(Q); } int queue_empty(QUEUE Q) {/* vraća != 0 ako je red prazan*/ assert(Q != NULL); return Q->front == NULL; } int queue_full(QUEUE Q) { /* vraća != 0 ako je red popunjen*/ /* uvijek možemo dodati element u listu */ return 0; } void queue_put(QUEUE Q, queueElemT el) {/* stavlja element el u red Q*/ struct node * n; assert(Q != NULL); n = malloc(sizeof(struct node)); if (n != NULL) { n->elem = el; n->next = NULL; if (queue_empty(Q)) Q->front = n; else Q->back->next = n; Q->back = n; } } queueElemT queue_get(QUEUE Q) {/* vraća element iz reda Q*/ queueElemT el; struct node * n; assert(Q != NULL); n = Q->front; el = n->elem; Q->front = n->next; if (Q->front == NULL) Q->back = NULL; free(n); return el; } unsigned queue_count(QUEUE Q) {/*vraća broj elemenata reda*/ unsigned num = 0; struct node *n; assert(Q != NULL); if( !queue_empty(Q)) for (n = Q->front; n != NULL; n = n->next) num++; return num; } void queue_print(QUEUE Q) { int i; struct node *n; assert(Q != NULL); n = Q->front;

Page 269: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

269

if( Q->front == NULL ) printf( "Red je prazan\n" ); else do { printf( "%d, ", n->elem ); n = n->next; } while( n != NULL ); printf( "\n" ); }

Testiranje ove implementacije se provodi s programom queue-test.c, opisanim u pogavlju 16, tako da se u njemu umjeto uključenja datoteke "queue-arr.c" uključi datoteka "queue-list.c".

18.7 Dvostruko vezana lista Ukoliko se čvor liste formira tako da sadrži dva pokazivača, next - koji pokazuje na

sljedeći čvor i prev - koji pokazuje na prethodni čvor, dobije se dvostruko vezana lista. Realizira se pomoću sljedeće strukture podataka:

typedef int elemT; typedef struct node Node; typedef Node *DLIST; struct node { elemT elem; /* element liste */ Node *next; /* pokazuje na sljedeći čvor */ Node *prev; /* pokazuje na prethodni čvor */ }

Slika 18.5 Dvostruko vezana lista

Veze među čvorovima ilustrira slika 18.5. Karakteristike prikazane liste su: 1. Pokazivač next krajnjeg elementa i pokazivač prev glave liste su jednaki NULL.

2. Ovakvu listu se može iterativno obilaziti u oba smjera, od glave prema kraju liste (korištenjem pokazivača next) i od kraja prema početku liste (korištenjem pokazivača prev).

3. Umetanje unutar liste i brisanje iz liste se provodi jednostavnije i brže nego kod jednostruko vezane liste, jer je u svakom čvoru poznat pokazivač na prethodni čvor.

4. U odnosu na jednostruko vezanu listu, dvostruko vezana lista zauzima više memorije (zbog pokazivača prev) .

Page 270: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

270

5. Ako se vrši umetanje i brisanje čvorova samo na početku liste, tada je bolje rješenje koristiti jednostruko vezanu listu.

Kroz dva primjera bit će pokazano kako se implementira dvostruko vezana lista. Primjer: Prikana je implementacija funkcija za umetanje čvora na glavi liste (dlist_add_front_node) i odstranjivanje čvora na glavi liste (dlist_delete_front_node). Uočite da je potrebno izvršiti nešto više operacija nego kod jednostruko vezane liste.

void dlist_add_front_node(DLIST *pList, Node *n) { if(n != NULL) /* izvrši samo ako je alociran čvor */ { n->next = *pList; n->prev = NULL; if(*pList != NULL) (*pList)->prev = n; *pList = n; } } void dlist_delete_front_node(DLIST *pList) { Node *tmp = *pList; if(*pList != NULL) { *pList = (*pList)->next; if(*pList != NULL) (*pList)->prev = NULL; freeNode(tmp); } }

Primjer: Prikazana je implementacija funkcija za brisanje čvora unutar liste (dlist_delete_node). Uočite da se ova operacija izvršava efikasnije nego kod primjene jednostruko vezane liste.

void dlist_delete_node(DLIST *pList, Node *n) { if(*pList == NULL || n == NULL) return; if(*pList == n) { /* ako n pokazuje glavu */ *pList = n->next; /* odstrani glavu */ *plist->prev = NULL; } else { n->prev->next = n->next; if(n->next != NULL) n->next->prev = n->prev; } freeNode(n); /* dealociraj čvor */ }

Zadatak: Napišite funkciju za brisanje i umetanje čvora na kraju liste. Za testiranje implementacije koristite isti program kao kod jednostruko vezane liste.

Page 271: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

271

Napomena: Funkcije za formiranje čvora, brisanje liste i traženje elementa liste su iste kao kod jednostruko vezane liste.

18.8 Generički dvostrani red - ADT DEQUEUE Dvostruka se lista može iskoristiti za efikasnu implementaciju dvostranog reda (eng. double ended queue) u obliku ADT DEQUEUE. Temeljne operacije s dvostranim redom - DEQUEUE su:

front() - vraća element na glavi liste push_front(el) - stavlja element el na glavu liste pop_front() - skida element s glave liste back() - vraća element s kraja liste push_back(el) - stavlja element el na kraj liste pop_back() - skida element s kraja liste find(el) - vraća pokazivač na traženi element ili null ako element nije u listi delete(el) - briše element el ako postoji u listi for_each(fun) - primjenjuje funkciju fun na sve elemente liste size() - vraća broj elemenata u listi empty() - vraća nenultu vrijednost ako je red prazan, inače vraća 0

Cilj je izvršiti generičku implementaciju ADT DQUEUE, tako da se on može primijeniti na bilo koji tip elemenata reda. Zbog toga će se u implementaciji ADT-a koristiti sljedeće strukture podataka:

typedef struct dnode DNode; struct dnode { void *elem; DNode *prev; DNode *next; }; typedef struct dequeue Dequeue; typedef struct dequeue *DEQUEUE; typedef int (*CompareFuncT)(void *, void *); typedef void *(*CopyFuncT)(void *); typedef void (*FreeFuncT)(void *); struct dequeue { DNode *front; /*pokazivač prednjeg čvora liste */ DNode *back; /*pokazivač stražnjeg čvora liste */ int size; /*broj elemenata u listi */ CompareFuncT CompareElem; /*funkcija za usporedbu elemenata */ CopyFuncT CopyElem; /*funkcija za kopiranje i alociranje*/ FreeFuncT FreeElem; /*funkcija za dealociranje elementa */ };

Čvor liste je opisan strukturom DNode. Sadrži dva pokazivača koji pokazuju na prethodni i sljedeći čvor liste te pokazivač na element liste koji je deklariran kao void*. Tom pokazivaču se može pridijeliti adresa bilo koje strukture podataka, ali void pokazivači ne sadrže

Page 272: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

272

informaciju o operacijama koje se mogu provoditi s podacima na koje oni pokazuju. Zbog toga će biti potrebno da korisnik definira tri pokazivača na funkcije;

o CompareElem - pokazivač funkcije za usporedbu dva elementa (poput strcpy), o CopyElem - pokazivač funkcije za alociranje i kopiranje elementa (poput strdup) i o FreeElem - pokazivač funkcije za dealociranje memorije koju element liste zauzima

(popot free).

Ovi se pokazivači bilježei u strukturi Dequeue, koja je temelj za definiranje ADT DEQUEUE. Tip ovih funkcija je definiran s tri typedef definicije. U structuri Dequeue bilježe se i pokazivači na prednji (front) i stražnji element liste (back) te broj elemenata u listi (size). Sadržaje ove strukture se određuje pri inicijalizaciji ADT-a funkcijom dequeue_new(). Ta i ostale funkcije opisane su u datoteci "dequeue.h".

Datoteka specifikacije - "dequeue.h":

/* Datoteka: dequeue.h * Specifikacija ADT DEQUEUE */ #ifndef _DEQUEUE_H_ #define _DEQUEUE_H_ typedef struct dequeue *DEQUEUE; typedef int (*CompareFuncT)(void *, void *); typedef void *(*CopyFuncT)(void *); typedef void (*FreeFuncT)(void *); DEQUEUE dequeue_new(CompareFuncT Compare, CopyFuncT Copy, FreeFuncT Free); /* Konstruktor ADT DEQUEUE * Argumenti: * Free - pokazivač funkcije za dealociranje elementa liste * Copy - pokazivač funkcije za alociranje elementa liste * Compare - pokazivač funkcije za usporedbu elemenata liste * vraća 0 ako su elementi jednaki, * <0 ako je prvi element manji od drugoga * >0 ako je prvi element veći od drugoga * Ako su svi pokazivači funkcija jednaki nuli, * tada se podrazumijeva se da je element liste cijeli broj. * Primjer korištenja: * Za rad s listom stringova konstruktor je * DEQUEUE d = dequeue_new(strcmp,strdup, free); * Za rad s listom cijelih brojeva konstruktor je * DEQUEUE d = dequeue_new(0,0,0); * i tada se void pokazivači tretiraju kao cijeli brojevi */ void dequeue_free(DEQUEUE d); /* Destruktor DEQUEUE d */ int dequeue_size(DEQUEUE d); /* Vraća veličunu DEQUEUE d*/ int dequeue_empty(DEQUEUE d); /* Vraća 1 ako je DEQUEUE d prazan, inače vraća 0*/

Page 273: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

273

void *dequeue_front(DEQUEUE d); /* Vraća pokazivač elementa na glavi DEQUEUE d * ili cijeli broj ako lista sadrži cijele brojeve */ void dequeue_push_front(DEQUEUE d, void *el); /* Stavlja element el na glavu DEQUEUE d*/ void dequeue_pop_front(DEQUEUE d); /* Skida element s glave DEQUEUE d*/ void *dequeue_back(DEQUEUE d); /* Vraća pokazivač elementa s kraja DEQUEUE d * ili cijeli broj ako lista sadrži cijele brojeve */ void dequeue_push_back(DEQUEUE d, void *el); /* Stavlja element el na kraj DEQUEUE d*/ void dequeue_pop_back(DEQUEUE d); /* Skida element s kraja DEQUEUE d*/ void *dequeue_find(DEQUEUE d, void *el); /* Vraća pokazivač elementa liste koji je jednak elementu el */ int dequeue_delete(DEQUEUE d, void *el); /* Briše element el. Ako postoji vraća 1, inače vraća 0 */ void dequeue_for_each(DEQUEUE d, void (*func)(void *)); /* Primjenjuje funkciju func na sve elemente DEQUEUE d*/ #endif

Implementacija ADT DEQUEUE je opisana u datoteci "dequeue.c" .

/* Datoteka: dequeue.c * Implementacija ADT DEQUEUE */ #include <stdlib.h> #include <string.h> #include <assert.h> #include "dequeue.h" typedef struct dnode DNode; struct dnode { void *elem; DNode *prev; DNode *next; }; typedef struct dequeue Dequeue; struct dequeue { DNode *front; DNode *back; int size; CompareFuncT CompareElem; CopyFuncT CopyElem; FreeFuncT FreeElem;

Page 274: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

274

}; static int CompareInternal(void *a, void *b) { if((int)a > (int)b) return 1; else if((int)a < (int)b) return -1; else return 0; } DEQUEUE dequeue_new( CompareFuncT Compare, CopyFuncT Copy, FreeFuncT Free) { DEQUEUE d=(DEQUEUE) malloc(sizeof(Dequeue)); if(d ) { d->front = d->back = NULL; d->size = 0; d->CopyElem = Copy; d->FreeElem = Free; if(Compare == NULL) d->CompareElem = CompareInternal; else d->CompareElem = Compare; } return d; } /* dvije pomoćne funkcije - make_node i free_node*/ static DNode *make_node(DEQUEUE d, void *el) {/* stvara čvor koristeći CopyElem funkciju */ DNode *n=(DNode *) malloc(sizeof(DNode)); if(n) { n->prev = n->next = NULL; if(d->CopyElem != NULL) n->elem = d->CopyElem(el); else n->elem = el; } return n; } static void free_node(DEQUEUE d, DNode *n) {/* dealocira čvor koristeći FreeElem funkciju */ if(n) { if(d->FreeElem != NULL) d->FreeElem(n->elem); free(n); } } void dequeue_free(DEQUEUE d) { DNode *tmp, *node; assert(d); node = d->front; while (node) { tmp = node; node = node->next; free_node(d, tmp);

Page 275: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

275

} free(d); } int dequeue_size(DEQUEUE d) { assert(d); return d->size; } int dequeue_empty(DEQUEUE d) { assert(d); return d->size==0; } void *dequeue_front(DEQUEUE d) { assert(d); if(d->front) return d->front->elem; else return NULL; } void dequeue_push_front(DEQUEUE d, void *elem) { DNode *new_node; assert(d); new_node = make_node(d, elem); if(new_node == NULL) return; if (d->front) { new_node->next = d->front; d->front->prev = new_node; d->front = new_node; } else { d->front = d->back = new_node; } d->size ++; } void dequeue_pop_front(DEQUEUE d) { DNode *old; assert(d); if (d->front) { old = d->front; d->front = d->front->next; if(d->front == NULL) d->back = NULL; else d->front->prev = NULL; free_node(d, old); d->size --; } } void* dequeue_back(DEQUEUE d) { assert(d);

Page 276: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

276

if(d->back) return d->back->elem; else return NULL; } void dequeue_push_back(DEQUEUE d, void * elem) { DNode *new_node; DNode *last; assert(d); new_node = make_node(d, elem); if(new_node == NULL) return; if (d->back) { last = d->back; last->next = new_node; new_node->prev = last; d->back = new_node; } else { d->front = d->back = new_node; } d->size++; } void dequeue_pop_back(DEQUEUE d) { DNode *old; assert(d); if (d->back) { old = d->back; d->back = d->back->prev; if(d->back == NULL) d->front = NULL; else d->back->next = NULL; free_node(d, old); d->size--; } } int dequeue_delete(DEQUEUE d, void * elem) { DNode *tmp; assert(d); tmp = d->front; while (tmp) { /*prvo pronađi element*/ if ((*d->CompareElem)(tmp->elem, elem) != 0) tmp = tmp->next; /*element u čvoru tmp*/ else if(tmp == d->front) { dequeue_pop_front(d); break; } else if(tmp == d->back){ dequeue_pop_back(d); break; } else { if (tmp->prev) tmp->prev->next = tmp->next; if (tmp->next) tmp->next->prev = tmp->prev; free_node(d, tmp);

Page 277: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

277

d->size--; break; } } return tmp != NULL; } void *dequeue_find(DEQUEUE d, void * elem) { DNode* node; assert(d); node = d->front; while (node){ if((*d->CompareElem)(node->elem, elem) == 0) return node->elem; node = node->next; } return NULL; } void dequeue_for_each(DEQUEUE d, void (*func)(void *)) { DNode* node; assert(d); node = d->front; while (node) { DNode *next = node->next; (*func) (node->elem); node = next; } }

Testiranje DEQUEUE se provedi programom dequeue-teststr.c. U njemu korisnik proizvoljno umeće ili briše stringove u dvostranom redu.

Datoteka: dequeue-teststr.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include "dequeue.h" void upute(void) { printf ("Izbornik:\n" " 1 Umetni ime sprijeda \n" " 2 Umetni ime straga \n" " 3 Odstrani sprijeda\n" " 4 Odstrani straga\n" " 0 Kraj\n"); } void print_elem(void *el) {printf("%s ", (char*)el);} void dequeue_print(DEQUEUE D) {

Page 278: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

278

if(!dequeue_empty(D)) dequeue_for_each(D, print_elem); } int main() { int izbor; char elem[255]; DEQUEUE D = dequeue_new(strcmp, strdup, free); upute(); printf("? "); scanf("%d", &izbor); while (izbor != 0) { switch(izbor) { case 1: printf("Otkucaj ime: "); scanf("\n%s", elem); dequeue_push_front(D, elem); printf("%s ubacen u red.\n", elem); dequeue_print(D); break; case 2: printf("Otkucaj ime: "); scanf("\n%s", elem); dequeue_push_back(D, elem); printf("%s ubacen u red.\n", elem); dequeue_print(D); break; case 3: if (!dequeue_empty(D)) { printf("Bit ce %s odstranjen sprijeda.\n", (char *) dequeue_front(D)); dequeue_pop_front(D); } dequeue_print(D); break; case 4: if (!dequeue_empty(D)) { printf("Bit ce %s odstranjen straga.\n", (char *)dequeue_back(D)); dequeue_pop_back(D); } dequeue_print(D); break; default: printf("Pogresan Izbor.\n\n"); upute(); break; } printf("\n? "); scanf("%d", &izbor); } if(dequeue_find(D, "ivo")) {

Page 279: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

279

dequeue_delete(D, "ivo"); dequeue_print(D); } dequeue_free(D); return 0; }

Uočite da operacije traženja imaju složenost O(n), a sve ostale operacije imaju složenost O(1). Zadatak: Prethodni primjer testiranja dvostranog reda, koji sadrži stringove, promijenite tako da se njime testira dvostrani red koji sadrži cijele brojeve. Koristite konstruktor oblika:

DEQUEUE d = dequeue_new(0,0,0);

Zadatak: Prethodni primjer testiranja dvostranog reda, koji sadrži stringove, promijenite tako da se njime testira dvostrani red koji sadrži realne brojeve tipa double. U tom slučaju morate definirati funkcije

void FreeFunc(double* pd); /*dealocira memoriju koju zauzima broj tipa double*/ double *CopyFunc(double* pd); /*vraća pokazivač na kopiju alociranog broja *pd */ int CompareDouble(double* pd1, double* pd1); /* Vraća 0 ako je *pd1 == *pd2 * Vraća 1 ako je *pd1 > *pd2 * Vraća -1 ako je *pd1 < *pd2 */

i koristiti konstruktor oblika;

DEQUEUE d = dequeue_new(FreeFunc, Copy Func, CompareFunc);

Zadatak: Generički pristup koji je korišten za definiranje ADT DEQUEUE može se iskoristiti za definiranje ADT koji služi za rad sa skupovima. Definirajte ADT SET kojim se, kao i u matemetici, može raditi sa skupovima pomoću sljedećih operacija:

ADT SET Empty(S) size(S) insert(S, x) member(S, x) delete(S, x) for_each(S, fun) intersection(S1, S2) union(S1, S2) difference(S1, S2)

- vraća nenultu vrijednost ako S prazan skup - vraća broj elemenata u skupu S, tj. vraća |S| - stavlja element x u skup S, ako x ∉ S. Nakon toga je x ∈ S - vraća true ako je x ∈ S - briše element x ako postoji u skupu S - primjenjuje funkciju fun na sve elemente skupa S - vraća skup S koji je jednak presjeku skupova S1 ∩ S2 - vraća skup S koji je jednak uniji skupova S1 ∪ S2 - vraća skup S koji je jednak razlici skupova S1 \ S2

Za implementaciju ovih operacija dovoljno je koristiti jednostruko vezanu listu. Zadatak: Često se kolekcija elemenata, koja, za razliku od skupa, može sadržavati više istovrsnih elemenata, naziva vreća (eng. BAG). Realizirajte ADT BAG pomoću vezane liste.

Page 280: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

280

Svakom elementu liste pridodajte atribut kojeg se obično naziva referentni izbroj (eng. referent count). Primjerice, možete za čvor liste koristiti sljedeću strukturu:

struct bag_node { int ref_count; void *element; struct bag_node *next; }

Referentni izbroj pokazuje koliko je istih elemenata u vreći. Realizirajte generički ADT BAG koji podržava sljedeće operacije:

ADT BAG empty(B) size(B) insert(B, x) find(B, x) delete(B, x)

- vraća nenultu vrijednost ako je vreća prazna - vraća broj elemenata u vreći - stavlja element x u vreću B po sljedećem algoritmu: ako u vreći već postoji element vrijednoti x, tada uvećavaj x.ref_count inače, kopiraj i alociraj element x u vreću, postavi x.ref_count na vrijednost 1. - vraća vrijednost x.ref_count koji znači koliko ima istovrsnih elemenata vrijednosti x, ili 0 ako element x nije u vreći - briše element x ako postoji u vreći, po sljedećem algoritmu: smanji x.ref_count za jedan. ako je x.ref_count jednak nuli, tada dealociraj memoriju koju zauzima x vrati x.ref_count

18.9 Zaključak Vezana lista je dinamička strukutura. Povećava se i smanjuje prema zahtjevima korisnika. Liste su kolekcije sa “sekvencijalnim pristupom”, za razliku od nizova koje se koristi kao

kolekcije sa “slučajnim pristupom”. Apstraktno, liste predstavljaju kolekciju elemenata. Operacije umetanja i brisanja

elemenata kolekcije provode se jednostavnije s vezanom listom, nego je to slučaj s nizovima. Umetanje i brisanje elementa unutar jednostruko vezane liste je relativno spora operacija jer se u njoj mora odrediti i položaj elementa koji prethodi mjestu umetanja. Znatno je brže umetanje elemenata na glavi liste.

Vezane liste se mogu slikovito usporediti s vlakom kojem su vagoni povezani jedan za drugim. Iz jednog vagona se može prijeći samo u susjedni vagon. Vagoni se mogu dodavati tako da se spoje na postojeću kompoziciju ili da se umetnu unutar kompozicije vlaka. Dodavanje vagona je jednostavna operacija, a umetanje vagona unutar kompozicije je složena operacija.

Kada je nužno vršiti često umetanje i brisanje čvorova unutar liste, tada je povoljno koristiti dvostruko vezanu listu.

Korištenjem tehnike definiranja apstraktnih tipova pokazano je da se pomoću liste mogu efikasno realizirati apstraktni tipovi STACK, QUEUE i DEQUEUE. U implementaciji ADT DEQUEUE pokazano je kako se mogu stvarati generičke kolekcije podataka.

Page 281: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

281

19 Razgranate strukture - stabla

Naglasci: • binarno stablo • stablo s aritmetičkim izrazima • leksički i sintaktički analizator aritmetičkih izraza • višesmjerna stabla • hrpa i prioritetni redovi

19.1 Definicija stabla Stablo (eng. tree) je apstraktna struktura podataka koja se četo koristi za predstavljanje hijerarhijskih odnosa, primjerice, slika 19.1 prikazuje hijerarhiju porodičnog stabla. Struktura stabla je potpuno određena čvorovima stabla i vezama između čvorova. Čvorovi stabla sadrže neki podatak, a veze određuju hijerarhijske odnose, prema sljedećem pravilu:

1. svaki čvor (osim jednog) ima samo jednog prethodnika, 2. svaki čvor ima određen broj slijednika.

Slika 19.1 Porodično stablo

Ako nije ograničen broj slijednika, stablo se naziva općenito stablo (eng. general tree). Prikazano je na slici 19.2. Ako čvorovi stabla imaju maksimalno k slijednika, stablo se naziva k-stupanjsko stablo. U specijalnom slučaju, kada elementi stabla imaju maksimalno 2 slijednika, stablo se naziva binarno stablo.

a

g

b c

0

1

2

3

d e f h

i j

x

yvanjski čvor(list)

unutarnji čvor

korijen

visina stabla

dubina

Slika 19.2 Strukturalni prikaz općenitog stabla

Page 282: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

282

Jedinstveni čvor koji nema prethodnika naziva se korijen (eng. root) stabla. Čvorovi koji nemaju slijednika nazivaju se listovi stabla (eng. leaf) ili vanjski čvorovi, svi ostali čvorovi su unutarnji čvorovi, a predstavljaju podstabla (eng. subtree). Slijednik čvora se naziva dijete (eng. child), a prethodnik čvora se naziva roditelj (eng. parent). Ako više čvorova ima istog roditelja oni su jedan drugome braća (ili sestre) (eng. siblings). U binarnom stablu čvor može imati dvoje djece koja se nazivaju lijevo dijete (eng. left child) i desno dijete (eng. right child). Kada je važan redoslijed djece, stablo se naziva uređeno stablo (eng ordered tree). Uzmemo li niz čvorova n1, n2, . . . , nk na način da je ni roditelj od ni+1 for 1 ≤ i < k, tada ovaj niz tvori stazu od čvora n1 do čvora nk. Duljina staze je jednaka broju čvorova u stazi umanjeno za jedan (od čvora do njega samog vodi staza nulte duljine). Visina čvora u stablu jednaka je duljini najdulje staze od čvora do lista (visina stabla je jednaka visini korijena stabla). Dubina čvora je jednaka je duljini staze od korijena stabla do tog čvora. Ona određuje razinu čvora. Puno ili popunjeno k-stupanjsko stablo je stablo u kojem svi listovi imaju istu dubinu, a svaki čvor ima svih k grana. U takvom stablu, na dubini 0 postoji 1 (k0 ) čvor, na dubini 1 postoji k1 čvorova, na dubini 2 postoji k2 čvorova..., pa ukupan broj unutarnjih čvorova Nu u stablu visine h iznosi:

11..1

0

121

−−

==++++= ∑=

kkkkkkN

hh

i

ihu (1)

i maksimalni broj vanjskih čvorova (listova) iznosi kh.

19.2 Binarno stablo Najčešće korišteni oblik stabla je binarno stablo. Definira se rekurzivno: Definicija: Binarno stablo je:

1. prazno stablo, ili 2. sastoji se od čvora koji se naziva korijen i dva čvora koji se nazivaju lijevo i desno

dijete, a oni su također binarna stabla (podstabla).

Slika 19.3 (a) Puno i (b) potpuno binarno stablo

Page 283: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

283

Ukupan broj unutarnjih čvorova punog binarnog stabla dobije se iz izraza (1). Tada je k=2 i Nu=2h-1. Broj vanjskih čvorova (listova) jednak je Nv = 2h. Makimalni broj čvorova u punom stablu, visine h, jednak je N = Nu+ Nu = 2h+1-1. Duljina staze od korijena do čvora jednaka je visini stabla. Kod punog stabla visina iznosi h = log2(N+1)-1. Kaže se da je binarno stablo potpuno 1 (eng. complete binary tree) ako je to puno stablo ili ako od tog stabla može nastati puno stablo dodavanjem čvorova na na donjoj razini s desna. Ova stabla su ilustrirana na slici 19.3. Kasnije ćemo vidjeti da se potpuna stabla mogu efikasno implementirati pomoću nizova.

19.2.1 Programska implementacija binarnog stabla Čvorovi binarnog stabla se najčešće opisuju samoreferentnim dinamičkim strukturama,

primjerice u C jeziku čvor binarnog stabla se može definirati strukturom tnode:

typedef char elemT; typedef struct tnode { elemT elem; struct tnode * left; struct tnode * right; } Tnode, *TREE;

Ova struktura sadrži neki element i dva pokazivača: left pokazuje na lijevo dijete, a right pokazuje na desno dijete. Ponekad se ovoj strukturi dodaje i pokazivač na roditelja. Pokazivači služe uspostavljanju veza među čvorovima. Slika 19.4 prikazuje veze među čvorovima jednog stabla.

Slika 19.4 Dva načina prikaza binarnog stabla: a) strukturalni prikaz i b) prikaz pomoću pokazivača na strukturu tnode. U strukturalnom prikazu krugovi označavaju unutarnje

čvorove, a pravokutnici označavaju listove stabla

Binarno stablo se može prikazati i tekstualnim zapisom, prema algoritmu prefiksne notacije stabla, na sljedeći način:

Algoritam: Prefiksna notacija stabla

1. Ako čvor predstavlja prazno stablo (NULL pokazivač), tada zapiši prazne zagrade ():

2. Ako čvor predstavlja list stabla, tada zapiši element lista.

3. Ako čvor predstavlja unutarnji čvor, tada unutar zagrada zapiši element čvora te lijevi i desni čvor.

1 Neki autori puno stablo nazivaju potpuno stablo, a potpuno stablo nazivaju "skoro potpuno stablo".

Page 284: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

284

Za stablo sa slike 19.4 vrijedi zapis:

(A (B () D) ili (A (B () D) (C E F) ). (C E F) )

Ovaj oblik notacije se naziva prefiksna notacija jer se apstraktno može smatrati da element unutarnjeg čvora stabla predstavlja prefiksni operator koji djeluje na svoju djecu.

Prefiksna notacija se često koristi za zapis aritmetičkih izraza. Ona se vrši na način da se u zagradama najprije zapiše operator, a zatim dva operanda. Operand može biti broj ili izraz. Ako je operand izraz, ponovo se primjenjuje isto pravilo. Primjerice, izraz infiksne notacije 8*(7+3) ima prefiksnu notaciju ( * 8 (+ 3 7) ). Općenito, svaki se aritmetički izraz može napisati u prefiksnoj notaciji. Zbog ovog svojstva aritmetički izrazi se mogu pohraniti u binarnom stablu. Primjer je prikazan na slici 19.5.

infiksna notacija = (7 – 3) * (4 + 5)

prefiksna notacija = ( * (- 7 3) (+ 4 5))

Slika 19.5 Binarno stablo aritmetičkog izraza

Programski se stablo se može formirati pomoću funkcije make_tnode().

TREE make_tnode (elemT elem, TREE left, TREE right) { TREE t = malloc(sizeof(Tnode)); if(t) { t->left = left; t->right = right; t->elem = elem;} return t; }

Argumenti funkcije su vrijednost elementa čvora (elem) i pokazivači na lijevo i desno dijete (left, right). Funkcija vraća pokazivač na formirani čvor, ili NULL ako se ne može izvršiti alokacija memorije. Kada se formira vanjski čvor stabla (list), tada se vrijednost argumenata left i right postavlja na vrijednost NULL. U svim ostalim čvorovima bar jedan od ova dva pokazivača mora biti različit od NULL. Primjer: Stablo sa slike 19.4 može se formirati sljedećim naredbama:

/* 1. formiraj listove */ TREE l1 = make_tnode('7', NULL,NULL); TREE l2 = make_tnode('3', NULL,NULL); TREE l3 = make_tnode('4', NULL,NULL); TREE l4 = make_tnode('5', NULL,NULL); /* 2.formiraj podstabla */ TREE t1 = make_tnode('-', l1, l2); TREE t2 = make_tnode('+', l3, l4); /* 3. formiraj korijena stabla */

Page 285: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

285

TREE t = make_tnode('*', t1, t2);

Varijabla t je pokazivač na korijen stabla. Ostale varijable su pomoćne pokazivačke varijable za formiranje listova i podstabala. Isto se može ostvariti jednom naredbom:

TREE t = make_tnode('*', make_tnode('-', make_tnode('7', NULL,NULL) ), make_tnode('3', NULL,NULL) )), make_tnode('+', make_tnode('4', NULL,NULL), make_tnode('5', NULL,NULL)) );

Uočite da se u ovom slučaju poziv funkcije make_tnode() obavlja prema prije opisanoj prefiksnoj notaciji stabla.

U prethodnom primjeru korisnik obavlja sve operacije formiranja stabla. Kasnije će biti pokazano kako se može automatizirati postupak formiranja stabla.

Prazno stablo se formira naredbom:

TREE t = NULL;

pa funkcija

int tree_is_empty(TREE t) {return t == NULL;}

vraća nulu ako je stablo prazno, ili nenultu vrijednost ako je stablo nije prazno.

Korisna je i funkcija tree_is_leaf() kojom se određuje da li čvor predstavlja list stabla.

int tree_is_leaf(TREE t) {return !(t->left || t->right);}

tree_is_leaf()vraća nulu ako čvor t nije list stabla, ili nenultu vrijednost ako je čvor t list stabla.

Rekurzivna definicija stabala čini da je većinu operacija nad stablom najlakše definirati rekurzivno.

Primjer: Funkcija tree_size(t) vraća broj čvorova binarnog stabla.

int tree_size(TREE t) { if(tree_is_empty(t)) return 0 ; else return 1 + tree_size(t->left) + tree_size(t->right); }

Primjer: Funkcija print_prefiks() ispisuje prefiksnu notaciju stabla koje sadrži aritmetički izraz. Realizirana je prema rekurzivnom algoritmu prefiksne notacije stabla.

void print_prefiks (TREE t) { if(t == NULL) {printf(" () "); return;} if(tree_is_leaf(t)) {printf("%c ",t->elem); return;} printf("( %c ",t->elem); print_prefiks (t->left); print_prefiks (t->right); printf(" )");

Page 286: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

286

}

Zadatak: Testirajte primjenu funkcije print_prefilks() u programu:

/* Datoteka: print_nodes.c int main() { TREE t = make_tnode('*', make_tnode('-', make_tnode('7', NULL,NULL), make_tnode('3', NULL,NULL)), make_tnode('+', make_tnode('4', NULL,NULL), make_tnode('5', NULL,NULL)) ); print_prefiks (t); return 0; }

19.2.2 Obilazak binarnog stabla Obilazak stabla je postupak kojim se na sve čvorove stabla primjenjuje neka operacija.

Kroz čvor se smije "proći" više puta, ali se operacija nad čvorom izvršava samo jedan put. Općeniti obrazac za rekurzivni obilazak binarnog stabla, počevši od čvora N (koji nije

prazan), temelji se na tri operacije:

(L) rekurzivno obiđi lijevo stablo. Nakon ove operacije ponovo si u čvoru N.

(D) rekurzivno obiđi desno stablo. Nakon ove operacije ponovo si u čvoru N.

(P) procesiraj čvor N. Ove tri operacije se mogu izvršiti bilo kojim redoslijedom. Ako se vrši (L) prije (D) tada je obilazak s lijeva na desno (eng. left-right traversal), u suprotnom je obilazak s desna na lijevo (right-left traversal). Prema redoslijedu operacija (L) (D) (P), razlikuju se tri načina obilaska stabla:

Obilazak stabla Redoslijed operacija Pre-order ili prefiks (P) (L) (D) Post-order ili postfiks (L) (D) (P) In-order ili infiks (L) (P) (D)

Slika 19.6 prikazuje stazu obilaska stabla za sva tri načina.

Page 287: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

287

Slika 19.6 Tri načina obilaska stabla (za obilazak s lijeva na desno). Usmjerene crtkane linije pokazuju redoslijed obilaska čvorova

Obilazak stabla se programski može realizirati funkcijom tree_traverse():

enum torder {PREORDER,INORDER,POSTORDER}; void tree_traverse(TREE t, int order, void (*visit)(TREE)) { if (t != NULL){ if (order == PREORDER) visit(t); tree_traverse(t->left, order, visit); if (order == INORDER) visit (t); tree_traverse(t->right, order, visit); if (order == POSTORDER) visit(t); } }

Funkciji tree_traverse() prvi je argument pokazivač čvora stabla. Drugi argument je vrijednost iz skupa {PREORDER, INORDER, POSTORDER}, kojom se određuje način obilaska stabla. Treći argument je pokazivač na tzv. visit funkciju kojoj je argument pokazivač čvora na koji ta funkcija djeluje. Primjerice, "visit" funkcija print_char() ispisuje vrijednost elementa čvora.

void print_char(TREE t) { printf("%c ",t->elem); }

Zadatak: Funkcije print_char() i tree_traverse() uvrstite u prethodni program "print_nodes.c", a u funkciji main(), umjesto naredbe print_prefiks(), napišite naredbe:

printf("\nPREORDER: "); tree_traverse(t, PREORDER, print_el); printf("\nINORDER: "); tree_traverse(t, INORDER, print_el); printf("\nPOSTORDER: "); tree_traverse(t, POSTORDER, print_el);

Nakon izvršenja programa dobije se ispis:

PREORDER: * - 7 3 + 4 5 INORDER: 7 - 3 * 4 + 5 POSTORDER: 7 3 - 4 5 + *

Ovaj primjer pokazuje da se pomoću binarnog stabla aritmetički izrazi lako pretvaraju u prefiksni, infiksni i postfiksni oblik.

19.2.3 Brisanje stabla Brisanje stabla je operacija kojom se dealociraju svi čvorovi stabla, koje time postaje

prazno stablo. Ovu operaciju treba izvesti tako da se najprije dealociraju listovi stabla, a tek onda unutarnji čvorovi. Slika 19.4 pokazuje da se to može izvesti postorder obilaskom stabla. Operacija brisanja stabla je implementirana u funkciji tree_delete().

TREE tree_delete( TREE t ) { if( t != NULL ) { tree_delete( t->left ); tree_delete( t->right );

Page 288: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

288

free( t ); } return NULL; }

Kod primjene ove funkcije treba uvijek voditi računa da se ona koristi u obliku

t = tree_delete(t);

jer na taj način t postaje jednako NULL, što označava prazno stablo.

19.2.4 Vrednovanje aritmetičkog izraza Gotovo svi moderni kompilatori i interpreteri, u prvoj fazi kompiliranja, pretvaraju izvorni

kôd u oblik koji je podesan za optimiranje kôda i generiranje strojnog kôda ili interpretiranje kôda. Praksa je pokazala da je najbolji način da se izvorni kod najprije pretvori u jedan oblik "sintaktičkog" stabla. Dio tog sintaktičkog stabla, kojim se bilježi aritmetičke izraze, sličan je ovdje opisanom binarnom stablu.

U prethodnim primjerima, zbog jednostavnosti, element čvora je bio tipa char. Ako se pak želi formirati stablo koje će sadržavati realne brojeve, tada treba izmijeniti definiciju tipa elementa čvora. Kod aritmetičkih izraza potrebno je da čvor sadrži ili znak operatora ili realni broj. To se može ostvariti tipom koji je unija elemenata tipa char (ili int) i tipa double. Koristit će se definicija:

typedef union elemT { int op; double num; } elemT; typedef struct tnode { elemT elem; struct tnode * left; struct tnode * right; } Tnode, *TREE;

Za formiranje čvora koristit će se funkcije make_opnode() i make_numnode().

TREE make_opnode (int op, TREE left, TREE right) { /* formira unutarnji čvor koji sadrži operator op*/ /* vraća pokazivač na taj čvor, ili NULL ako je greška*/ TREE t = malloc(sizeof(Tnode)); if(t) { t->left = left; t->right = right; t->elem.op = op;} return t; } TREE make_numnode (double num) { /* formira vanjski čvor koji sadrži realni broj - num*/ /* vraća pokazivač na taj čvor, ili NULL ako je greška */ TREE t = malloc(sizeof(Tnode)); if(t) { t->left = NULL; t->right = NULL; t->elem.num = num;} return t; }

Programski jezici Lisp i Sheme koriste prefiksnu notaciju za zapis svih svojih konstrukcija, a pohrana i izračunavanje vrijednosti izraza (tzv. evaluacija) vrši se obilaskom binarnog stabla. Koristi se sljedeći algoritam:

Page 289: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

289

Algoritam: Vrednovanje aritmetičkog izraza koji je zapisan u binarnom stablu Temeljni slučaj: Ako čvor sadrži operand, vrati vrijednost operanda Pravilo rekurzije: Ako čvor sadrži operator, rekurzivno dobavi lijevi i desni operand, izvrši operaciju koju određuje operator i vrati rezultat

Ovaj algoritam je implementiran u funkciji evaluate().

double evaluate(TREE t) { /* vraća vrijednost izraza koji je u stablu t*/ double x,y; int op; if(tree_is_leaf(t)) /* ako je t list stabla */ return t->elem.num; /* vrati vrijednost operanda */ /* inače, */ op = t->elem.op; /* dobavi operator */ x = evaluate(t->left); /* dobavi lijevi operand */ y = evaluate(t->right); /* dobavi desni operand */ switch (op) { /* izračunaj vrijednost izraza*/ case '+': return x+y; case '-': return x-y; case '*': return x*y; case '/': return (y == 0)? 0 : x/y; default: printf("\nGreska"); return 0; } }

Primjer: U programu evaluate.c, pokazana je primjena funkcije evaluate() za proračun vrijednosti prefiksnog izraza (*(- 7.5 3)(+ 4 5.1)). Program koristi funkcije i definicije koje su definirane u datotekama "prefix_tree.h" i "prefix_tree.c".

/***************************************************************/ /* Datoteka: prefix_tree.h */ /***************************************************************/ #ifndef _PREFIX_TREE_H_ #define _PREFIX_TREE_H_ typedef union elementip { int op; double num; }elemT; typedef struct tnode { elemT elem; struct tnode * left; struct tnode * right; } Tnode, *TREE; int tree_is_leaf(TREE t); void print_prefiks (TREE t); TREE make_opnode (int op, TREE left, TREE right); TREE make_numnode (double num); double evaluate(TREE t); #endif /***************************************************************/

Page 290: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

290

/* Datoteka: prefix_tree.c */ /***************************************************************/ #include <stdio.h> #include <stdlib.h> #include "prefix_tree.h" int tree_is_leaf(TREE t) {return !(t->left || t->right);} void print_prefiks (TREE t) { if(t == NULL) {printf(" () "); return;} if(tree_is_leaf(t)) {printf("%f ",t->elem.num); return;} printf("( %c ",t->elem.op); print_prefiks (t->left); print_prefiks (t->right); printf(" )"); } TREE make_opnode (int op, TREE left, TREE right) { TREE t = malloc(sizeof(Tnode)); if(t) { t->left = left; t->right = right; t->elem.op = op;} return t; } TREE make_numnode (double num) { TREE t = malloc(sizeof(Tnode)); if(t) { t->left = NULL; t->right = NULL; t->elem.num = num;} return t; } TREE tree_delete( TREE t ) { if( t != NULL ) { tree_delete( t->left ); tree_delete( t->right ); free( t ); } return NULL; } double evaluate(TREE t) { double x,y; int op; if(tree_is_leaf(t)) /* operand*/ return t->elem.num; /*else analyse operator*/ op = t->elem.op; x = evaluate(t->left); y = evaluate(t->right); switch (op) { case '+': return x+y; case '-': return x-y;

Page 291: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

291

case '*': return x*y; case '/': return (y==0)? 0 : x/y; default: printf("\nGreska"); return 0; } } /************************************************************/ /* Datoteka: evaluate.c */ /************************************************************/ #include <stdio.h> #include <stdlib.h> #include "prefix_tree.c" int main() { TREE t = make_opnode('*', make_opnode('-', make_numnode(7.5), make_numnode(3)), make_opnode('+', make_numnode(4), make_numnode(5.1)) ); printf("\nVrijednost prefiksnog izraza:\n"); print_prefiks(t); printf("\njednaka je: %f", evaluate(t)); return 0; }

Program se kompilira komandom:

c:> cl evaluate.c prefix_tree.c

Nakon izvršenja programa "evaluate.exe" dobije se ispis:

Vrijednost prefiksnog izraza: ( * ( - 7.500000 3.000000 )( + 4.000000 5.100000 ) ) jednaka je: 40.950000

19.3 Interpreter prefiksnih izraza Sada će biti pokazano kako se realizira interpreter prefiksnih izraza koje unosi korisnik pomoću tipkovnice ili iz datoteke.

19.3.1 Dobavi-vrednuj-ispiši Rad interpretera se obično opisuje tzv. dobavi-vrednuj-ispiši petljom (eng. read-eval-print loop), koja je definirana sljedećim algoritmom: Algoritam : Interpreter s dobavi-vrednuj-ispiši petljom

Ponavljaj: 1. Dobavi naredbu – dobavi izvorni kod naredbe i interno ga spremi u prikladnu

podatkovnu strukturu. Ako je primljen zahtjev za kraj rada, završi petlju. 2. Vrednuj naredbu – izvrši operacije na internoj podatkovnoj strukturi, koje rezultiraju

nekom vrijednošću. Primjerice, obilaskom binarnog stabla izračunaj vrijednost aritmetičkog izraza.

3. Ispiši rezultat.

Page 292: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

292

U prethodnoj sekciji je pokazano kako se vrednuje i ispisuje rezultat aritmetičkog izraza koji je zapisan u binarnom stablu. Sada će biti pokazano kako se realizira "dobavi" operacija, tj. kako se od korisnika dobavlja naredba te kako se vrši leksička i sintaktička analiza izvornog kôda naredbe, koja rezultira binarnom stablom aritmetičkog izraza. U leksičkoj i sintaktičkoj analizi koristit će se specifikacije u BNF notaciji, koja je opisana u poglavlju 6. U BNF notaciji gramatička se pravila zapisuju u obliku produkcije:

A : α gdje je A neterminalni simbol, a α predstavlja niz terminalnih ili neterminalnih simbola X1 X2...Xn . Ako je simbol Xi neterminalni simbol, zapisuje se kurzivom. Na taj se način razlikuje od terminalnog simbola. Ako je simbol Xi opcioni, što znači da ne mora biti u produkciji, zapisuje sa sufiksom opt, tj. Xopt. Ako je neterminalni simbol A definiran s više alternativnih produkcija A : α1 , A : α2 ... A : αn., koristi se notacija:

A : α1 | α2 | ...| αn U gramatičkim analizama opcioni simbol se tretira kao neterminalni simbol koji je definiran produkcijom:

Xopt : X | ε

gdje ε označava "praznu" produkciju. Kaže se da je Xopt definiran ili kao X ili kao prazna produkcija (ε-produkcija).

19.3.1 Leksička analiza prefiksnih aritmetičkih izraza Razmotrimo jedan prefiksni izraz, s realnim brojevima, koji je napisan u više linija teksta:

(* (- 3.7 35) # komentar (/ 8 (+ 9.1 -5) ) ) # ....... $ # kraj unosa

Ovaj izraz se može pročitati na sljedeći način: "pomnoži razliku 3.7 i 35 i kvocijent od 8 i zbroja 9.1 i -5". Nakon ovog izraza slijedi znak $. On označava kraj unosa. U prikazanom prefiksnom aritmetičkom izrazu su zastupljene sljedeće leksičke i gramatičke kategorije:

leksem (iz ulaznog toka)

značaj token - gramatički terminalni simbol

vrijednost tokena ( ili atribut tokena)

(, ), zagrade za grupiranje izraza

'( ' ')' '(' ')'

3.7 35 -5 realni broj NUM numerička vrijednost realnog broja

+, -, *, / ,

aritmetički operatori OPERATOR '+', '-', '*', '/'

$, EOF oznak kraja unosa (EOF kod datoteka)

QUIT '$' ili EOF

razmak, tab, nova linija

separatori (bijeli znakovi: '\n', '\t', '\n' )

# ....... komentar (počinje

Page 293: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

293

znakom # ) Leksička analiza je proces kojim se sekvencijalno u nizu ulaznih znakova prepoznaje neki leksem i gramatički simbol koji on predstavlja, tj. token. Primjerice, leksem "37.5" predstavlja token NUM. Ako je potrebno, uz token se bilježi i njegova vrijednost, koja predstavlja atribut tokena. Leksički analizator se može jednostavno implementirati pomoću funkcije getToken() i sljedećih deklaracija:

/**************************************************************/ /* datoteka: prefix_lex.h */ /**************************************************************/ #ifndef _PREFIX_LEX_H_ #define _PREFIX_LEX_H_ /* tokeni (terminalni simboli) */ #define NUM 255 #define OPERATOR 256 #define QUIT 257 /* globalne varijable*/ extern FILE *input = stdin; /* ulazni tok */ extern elemT tokenval; /* vrijednost tokena */ extern char lexeme[]; /* leksem tokena */ int getToken(); /* Pri svakom pozivu funkcija vraća sljedeći token iz ulaznog toka, ili 0, ako je registriran nepredviđeni znak. Vrijednost tokena i leksem tokena se zapisuju u globalne varijable tokenval i lexeme. */ #endif

Implementacija funkcije getToken() je u datoteci "prefix_lex.c".

/**************************************************************/ /* datoteka prefix_lex.c */ /**************************************************************/ #include <stdio.h> #include <stdlib.h> #include <ctype.h>

#include "lexan.h"

FILE *input = stdin; /* ulazni tok */ elemT tokenval; /* vrijednost tokena */ char lexeme[64]; /* leksem tokena */ int getToken() { int ch; while(1) { ch = getc(input);

Page 294: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

294

/* preskoči bijele znakove */ if(ch == ' ' || ch =='\t' || ch =='\n' || ch =='\r') continue; /* zapamti prvi znak */ lexeme[0]=(char)ch; lexeme[1]='\0'; if (isdigit(ch)) { /* dobavi broj */ int i = 1; /* prva znamenka je u lexeme[0]*/ ch = getc(input); while(isdigit(ch) && i<62) { lexeme[i++]=(char)ch; ch = getc(input); } if(ch == '.') { lexeme[i++]=(char)ch; ch = getc(input); while(isdigit(ch) && i<62){ lexeme[i++]=(char)ch; ch = getc(input); } } lexeme[i] = '\0'; tokenval.num = atof(lexeme); if (ch != EOF) ungetc(ch,input); return NUM; } else if(ch == '+' || ch == '-' || ch == '*' || ch == '/' ) { tokenval.op = ch; return OPERATOR; }else if(ch == '(' || ch == ')') { return ch; }else if(ch == '$' || ch == EOF) { return QUIT; }else if(ch == '#'){ /*komentar do kraja linije */ while (ch != '\n') ch = getc(input); }else return 0; /* nedozvoljen znak */ } }

U funkciji getToken() se unutar petlje dobavlja znak iz toka naredbom getc(input). Petlja se ponavlja ako su na ulazu "bijeli znakovi" ili ako je dobavljen znak '#', koji označava početak komentara. U svim ostalim slučajevima petlja se prekida nakon analize dobavljenog znaka, a analiza se vrši na sljedeći način:

• Ako je dobavljeni znak numerička znamenka, dobavlja se realni broj oblika:

realni_broj : niz_znamenki decimalni_dioopt decimalni_dio : . niz_znamenki opt

Bilježenje broja se vrši prema prethodnom pravilu: najprije se bilježi niz znamenki, a zatim, ako iza njih slijedi točka, bilježi se decimalni dio. Leksem broja se bilježi u stringu lexeme, njegova numerička vrijednost u tokenval.num, a funkcija getToken() vraća token NUM.

• Ako je dobavljeni znak operator +, -, * ili / , getToken() vraća token OPERATOR, a vrijednost tokena bilježi u tokenval.op.

• Ako je dobavljeni znak lijeva ili desna zagrada, getToken() vraća ASCII vrijednost znaka, što služi kao oznaka tokena.

• Ako je dobavljeni znak &, ili ako je kraj datoteke, getToken() vraća token QUIT.

Page 295: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

295

• U svim ostalim slučajevima getToken() vraća vrijednost nula, što označava nepoznati token.

Funkcija getToken() ima jednostavnu strukturu. Lako se može proširiti da obuhvati širi

skup tokena. Zbog toga, ona može poslužiti kao obrazac prema kojem se može se napisati kvalitetni leksički analizator za znatno složeniji programski jezik.

19.3.3 Sintaktička analiza prefiksnih izraza U skladu s BNF notacijom gramatika naredbe za proračun prefiksnih aritmetičkih izraza glasi:

Naredba : ( Izraz )

| QUIT Izraz : OPERATOR Operand Operand Operand : NUM

| - NUM | ( Izraz )

gdje su Naredba, Izraz i Operand neterminalni simboli, a zagrade ( ), OPERATOR, NUM i QUIT su terminalni simboli (tokeni), koji postoje u ulaznom toku. Naredba je startni simbol gramatike. Desna strana njegove produkcije određuje temeljni oblik rečenice koja se može ostvariti ovom gramatikom. Moguća su dva oblika rečenice: "QUIT" – što predstavlja naredbu za kraj rada interpretera i "( Izraz )" – što predstavlja naredbu da se izračuna vrijednost Izraza. Ovaj drugi oblik sadrži neterminalni simbol Izraz. Zbog toga, to nije stvarna rečenica jezika, već se naziva rečenična forma (eng. sentential form). Izraz označava pravilo po kojem u ulaznom toku, na mjestu gdje se očekuje prefiksni izraz, simboli moraju biti poredani tako na najprije bude simbol OPERATOR, a iza njega moraju biti dva operanda. Ako u rečeničnoj formi (Izraz) zamijenimo neterminalni simbol Izraz s desnom stranom produkcije, tada rečenična forma ima oblik (OPERATOR Operand, Operand). Operand je neterminalni simbol koji je definiran pravilom po kojem u ulaznom toku, na mjestu gdje se očekuje operand, može biti ili broj (NUM) , ili negativni broj (-NUM) ili zagradama omeđeni Izraz. Rečenice, koje može generirati gramatika, su sve rečenice koje se mogu dobiti iz rečenične forme, na način da se u rečeničnoj formi neterminalni simboli zamijene s desnom stranom njihove produkcije, i to sve dok rečenična forma ne sadrži isključivo terminalne simbole. Opisani proces se naziva derivacija, a označava se simbolom ⇒. Evo primjera dvije moguće derivacije: Naredba ⇒ ( Izraz ) ⇒ ( OPERATOR Operand Operand ) ⇒ ( OPERATOR NUM Operand ) ⇒ ( OPERATOR NUM ( Izraz ) ) ⇒ ( OPERATOR NUM ( OPERATOR Operand Operand ) ) ⇒ ( OPERATOR NUM ( OPERATOR NUM Operand ) ) ⇒ ( OPERATOR NUM ( OPERATOR NUM NUM ) ) ⇒ ( OPERATOR NUM ( OPERATOR NUM NUM ) )

Naredba ⇒ ( Izraz ) ⇒ ( OPERATOR Operand Operand ) ⇒ ( OPERATOR NUM Operand ) ⇒ ( OPERATOR NUM -NUM )

Page 296: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

296

U svakoj rečeničnj formi podcrtan je simbol koji se zamjenjuje s desnom stranom njegove produkcije.

Različitost mogućih derivacija je posljedica alternativnih definicija neterminalnog simbola Operand. Primijetite da je moguć beskonačan broj različitih derivacija jer je Izraz definiran rekurzivno (Izraz je definiran pomoću Operanda, a Operand pomoću Izraza).

Prvi zadatak sintaktičke analize je otkriti da li postoji derivacija koja odgovara ulaznom nizu tokena. Ona se može vršiti na više načina. Ovdje će biti korištena tzv. "top-down" analiza, u kojoj se ulazni niz promatra s lijeva na desno, jedan po jedan token, i istovremeno vrši derivacija rečenične forme koja slijedi iz položaja tokena u ulaznom nizu.

Pogledajmo najprije kako čovjek može izvršiti sintaktičku analizu. U "top-down" analizi polazi se od startnog simbola gramatike i analizira desna strana njegove produkcija, koja predstavlja temeljnu rečeničnu formu. Simboli iz rečenične forme se analiziraju jedan po jedan, s lijeva na desno, i uspoređuju s ulaznim tokenom. Na temelju usporedbe rečeničnog i ulaznog simbola, vrše se dvije radnje:

Prihvati - Ako u rečeničnoj formi slijedi token, on mora biti jednak ulaznom tokenu. Ako je to zadovoljeno, dobavlja se sljedeći ulazni token, inače ulazni niz nije u skladu s gramatikom.

Proširi - Ako u rečeničnoj formi slijedi neterminalni simbol, umjesto njega se u rečeničnu

formu umeće desna strana produkcije. Međutim, ako je neterminalni simbol definiran s više alternativnih produkcija, tada se umeće ona alternativna produkcija koja generira rečenice koje mogu počinjati s ulaznim tokenon. Ako neterminalni simbol predstavlja opcioni simbol, odnosno simbol koji može generirati praznu rečenicu, tada se umjesto njega umeće desna strana produkcija samo u slučaju ako taj simbol generira rečenice koje mogu počinjati s ulaznim tokenom, inače se taj simbol odstranjuje i analizira se sljedeći simbol.

Analiza se nastavlja sve dok sljedeći ulazni token ne postane posljednji token ulaznog niza. Analiza je uspješna ako rečenična forma isključivo sadrži tokene.

Primjer ovakve analize prikazuje slika 19.7. Prvi stupac sadrži rečeničnu formu, koja nastaje tijekom derivacije. Drugi stupac opisuje operaciju kojem se prihvaća ili proširuje rečenični oblik. Treći stupac prikazuje sljedeći ulazni token. U svakoj rečeničnoj formi podebljan je token kojeg se prihvaća i podcrtan je neterminalni simbol kojeg se zamjenjuje desnom stranom produkcije.

Derivacija rečenične forme za ulazni niz ( * 9 (+ 8 3) ) Operacija Ulaz Naredba ⇒ ( Izraz ) ⇒ ( OPERATOR Operand Operand ) ⇒ ( OPERATOR NUM Operand ) ⇒ ( OPERATOR NUM ( Izraz ) ) ⇒ ( OPERATOR NUM ( OPERATOR Operand Operand ) ) ⇒ ( OPERATOR NUM ( OPERATOR NUM Operand ) ) ⇒ ( OPERATOR NUM ( OPERATOR NUM NUM ) ) ⇒ ( OPERATOR NUM ( OPERATOR NUM NUM ) )

proširi Naredba prihvati: (, proširi Izraz prihvati OP, proširi Operand prihvati NUM, proširi Operand prihvati (, proširi Izraz prihvati OP, proširi Operand prihvati NUM, proširi Operand prihvati NUM, prihvati ) prihvati ) .

( * 9 ( + 8 3 ) )

Slika 19.7 Top-down sintaktička analiza – gramatička derivacija za ulazni niz (*9(+8 3)).

Prethodno izvršena analiza naziva se LL(1) analiza. Ovaj naziv slijedi iz činjenice da je ulazni niz analiziran s lijeva (L) i da je u rečeničnoj formi vršena supstitucija najljevijeg

Page 297: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

297

neterminalnog simbola (L) na temelju poznavanja samo jednog sljedećeg tokena (1). Gramatika, koja se može analizirati na ovaj način, naziva se LL(1) gramatika.

Tri su temeljna zahtjeva da bi gramatika bila LL(1): 1. Neterminalni simbol ne smije biti definiran s više alternativnih produkcija koje počinju

s istim simbolom, jer se tada ne može znati koju produkciju primijeniti na temelju poznavanja samo jednog sljedećeg ulaznog tokena. Primjerice, u produkciji

A : t B | t C obje alternative počinju s istim tokenom t. Ova restrikcija se može lako otkloniti ako se produkcija transformira oblik

A : t A1 A1 : B | C

2. Neterminalni simbol ne smije biti definiran produkcijom koja počinje s tim istim

simbolom. Takve produkcije se nazivaju lijevo rekurzivne produkcije. Primjerice, u produkciji

A : x | A + x neterminal A može biti definiran s x, zbog toga alternativa, A + x, također može početi s x. Prema tome, ne može se odrediti koju produkciju primijeniti. To vrijedi bez obzira koliko ulaznih tokena poznajemo, jer se A može proširiti s A+x proizvoljan broj puta.

3. Ako u nizu simbola postoji opcioni simbol, odnosno simbol koja generira praznu rečenicu, tada se mora razlikovati skup tokena s kojim mogu počinjati rečenice koje generira taj simbol i skup tokena s kojim mogu počinjati rečenice koje generira simbol koji slijedi iza njega. Ako to nije ispunjeno ne može se odrediti da li analizirati opcioni simbol ili simbol koji slijedi iza njega.

Bez obzira na ove gramatičke restrikcije, LL(1) gramatika je korištena u izradi mnogih interpretera i kompilatora, jer se za ovaj tip gramatike može jednostavno napisati program koji vrši sintaktičku analizu.

19.3.4 Rekurzivno silazni parser Program koji obavlja sintaktičku analizu, za bilo koji ulazni niz tokena, naziva se sintaktički analizator ili parser. Parser za gramatiku tipa LL(1) može se realizirati u obliku koji se naziva rekurzivno silazni parser (eng, recursive descent parser). Program parsera se sastoji od potprograma koji imaju imena neterminalnih simbola gramatike. Zadatak je svakog potprograma, počevši od startnog simbola gramatike, da provjeri može li se "proširiti" rečenična forma s desnom stranom produkcije, a na temelju poznavanja sljedećeg ulaznog tokena. Formalno se taj postupak može opisati na sljedećim algoritmom: Algoritam: Definiranje rekurzivno silaznog parsera za LL(1) gramatiku 1. Ako je neterminalni simbol definiran produkcijom oblika A : α gdje je α općenito predstavlja niz od jedan ili više tokena ili neterminalnih simbola, potprogram A() ima oblik:

A() { Analiziraj α ; }

Analiziraj α; simbolički označava naredbu koja ima oblik:

Ako je α token t, tada

Page 298: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

298

Analiziraj t; ≡ Ako je ulazni token jednak t, dobavi sljedeći ulazni token inače dojavi grešku Ako je α neterminalni simbol N, tada

Analiziraj N; ≡ N(); /*poziv potprograma za neterminalni simbol N*/ Ako je α = X1 X2...Xn , gdje Xi može biti token ili neterminalni simbol, tada

Analiziraj X1 X2...Xn ; ≡ Analiziraj X1 ; Analiziraj X2 ; ... Analiziraj Xn ; Ako je α opcioni simbol Xopt , tada

Analiziraj Xopt ; ≡ Ako X generira rečenice koje mogu počinjati s ulaznim tokenom, tada

Analiziraj X;

2. Ako je neterminalni simbol definiran s alternativnim produkcijama, potprogram prvo treba odrediti koja alternativna produkcija odgovara položaju sljedećeg ulaznog tokena, a zatim se provjerava odabrana produkcija. Formalno, ako produkcija ima oblik A: α1 | α2 | .. αn gdje su α1, α2.. αn desne strane alternativnih produkcija, potprogram A() ima oblik:

A() {

Ako α1 generira rečenice koje mogu počinjati s ulaznim tokenom Analiziraj α1; inače, ako α2 generira rečenice koje mogu počinjati s ulaznim tokenom Analiziraj α2;

.... inače, ako αn generira rečenice koje mogu počinjati s ulaznim tokenom Analiziraj αn; inače, dojavi grešku

} 3. Rekurzivni silazni parser se realizira pozivom potprograma koji ima ime startnog simbola gramatike. Taj potprogram se definira prema prethodna dva pravilu, uz dopunu da prva naredba mora biti dobava sljedećeg ulaznog tokena, koji se tretira kao globalna varijabla. 4. Dojava greške, u najprostijem obliku, je operacija kojom se ispisuje poruka o nastaloj greški i prekida izvršenje programa. Postoje naprednije metode obrade greške, u kojima se ne prekida izvršenje programa, međutim one neće biti opisane u ovoj knjizi. Primjer: Parser prefiksnih aritmetičkih izraza. Parser koristi funkciju getToken() za dobavu sljedećeg tokena, koji se bilježi u globalnoj varijabli next_token. Globalna varijabla tokenval sadrži vrijednost tokena. Funkcija za dojavu sintaktičke greške i prekid programa naziva se syn_error().

void syn_error(char *poruka) /* Dojava sintaktičke greške */ { /* u obliku "Greska: poruka" */ printf("\nGreska: %s",poruka);

Page 299: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

299

exit(1); /* i prekid programa */ } int next_token; /* sljedeći ulazni token */

Za rad parsera definirane su tri funkcije: Naredba(), Izraz() i Operand(), čija imena su neterminalni simboli gramatike, a prema algoritmu za rekurzivno silazni parser.

void Izraz(); void Operand(); void Naredba() /* Naredba : ( Izraz ) | QUIT*/ { next_token = getToken(); if(next_token == '(') { next_token = getToken(); Izraz(); if(next_token != ')') syn_error(" nedostaje ')' !"); } else if(next_token == QUIT) return; else syn_error(" nedozvoljen simbol!"); } void Izraz() /* Izraz : OPERATOR Operand Operand */ { if(next_token == OPERATOR) next_token = getToken(); else else syn_error(" nedozvoljen operator!"); Operand(); Operand(); } void Operand() /* Operand : NUM |-NUM | ( Izraz ) */ { if( next_token == NUM) { next_token = getToken(); } else if( next_token == OPERATOR && tokenval.op == '-') { next_token = getToken(); if( next_token == NUM) next_token = getToken(); else syn_error("pogresno unesen broj!"); } else if( next_token == '(') { next_token = getToken(); Izraz(); if(next_token == ')') next_token = getToken(); else syn_error(" nedostaje ')' !"); } else syn_error("pogresno unesen broj!"); }

Postupak parsiranja se može provjeriti sljedećim programom:

int main() { printf("Otkucaj prefiksni izraz, primjerice (*5(- 8 3.1))\n"); Naredba();

Page 300: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

300

printf("Parsiranje uspjesno!"); return 0; }

Nakon izvršenja ovog programa korisnik će vidjeti samo dva moguća ispisa: Ako je izraz unesen u skladu s gramatikom bit će ispisano:

Parsiranje uspjesno!

inače, bit će dojavljena greška, primjerice u obliku:

Greska: nedostaje ) !

Ovaj parser jedino zna prepoznti da li je ulazni niz u skladu s zadanom gramatikom. Međutim, mi želimo da on obavi još jednu radnju - da generira binarno stablo koje sadrži aritmetički izraz. Kako se to može obaviti, bit će pokazano u sljedećem odjeljku.

19.3.5 Semantičke akcije Radnje, koje slijede iz značenja gramatički ispravnog zapisa, nazivaju se semantičke akcije.

U programskim jezicima često je dovoljno sagledati samo dijelove rečenice da bi se izvršile semantičke akcije. Primjerice, kod analize aritmetičkih izraza, koje želimo prikazati pomoću binarnog stabla, znamo da kada bude prepoznat token NUM tada treba formirati čvor koji je list binarnog stabla. Taj čvor sadrži atribut tokena NUM (tj. njegovu numeričku vrijednost). Postavlja se pitanje: kome pridjeliti pokazivač na taj čvor? Možemo rezonirati na sljedeći način: ako je prepoznat NUM, to ujedno znači da je prepoznata produkcija Operand : NUM, pa apstraktno možemo uzeti da Operand ima semantički atribut (ili vrijednost) koja je jednaka pokazivaču na čvor stabla.

Slično rezoniramo i ako je prepoznat operator i dva operanda. Tada treba formirati unutarnji čvor binarnog stabla. Pošto to ujedno znači da je prepoznata produkcija za Izraz, možemo uzeti da Izraz ima semantički atribut koji je, također, pokazivač na čvor stabla.

Konačno, možemo uzeti da simbol Naredba ima semantički atribut tipa pokazivača na čvor stabla, i to na korijen stabla, jer analizom produkcije Naredba : (Izraz) | QUIT, se dobije pokazivač na čvor stabla kojim počinje Izraz. Ako je pak prepoznata naredba QUIT, tada se ne formira stablo, pa u tom slučaju semantički atribut simbola Naredba mora biti NULL pokazivač.

Uvedimo sada sljedeće oznake semantičkih atributa: • NUM:num je vrijednost tokena NUM (dobije se iz globalne varijable

tokenval.num) • OPERATOR:op je vrijednost tokena OPERATOR (dobije se iz globalne varijable

tokenval.op) • Naredba:t je pokazivač korijena binarnog stabla. • Izraz:t je pokazivač unutarnjeg čvora binarnog stabla, koji sadrži operator i

pokazivače na operande. • Operand:t je pokazivač na čvor binarnog stabla, koji je ili list stabla ili pokazivač na

čvor koji sadrži izraz. Sada se semantičke akcije mogu specificirati zajedno sa specifikacijom gramatike, kao što je prikazana na slici 19.8. Gramatika Pseudo kôd za semantičke akcije Naredba :

Page 301: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

301

( Izraz ) | QUIT Izraz : OPERATOR Operand Operand Operand : NUM | - NUM | ( Izraz )

Naredba:t = Izraz:t; Naredba:t = NULL; Izraz:t = tree_opnode(OPERATOR:op, Operand:t1, Operand:t2); Operand:t = tree_numnode( NUM:num); Operand:t = tree_numnode( NUM:num); Operand:t = Izraz:t;

Slika 19. 8 Semantičke akcije za generiranje stabla s aritmetičkim izrazima.

Prikazane semantičke akcije se mogu uvrstiti u izvorni kôd parsera na sljedeći način: 1. Za sve neterminalne simbole, kojima je atribut stablo izraza, deklariraju se parsne funkcije koje vraćaju pokazivač stabla. Deklaracija triju funkcija parsera glasi:

TREE Naredba(); TREE Izraz(); TREE Operand();

Vrijednost koju ove funkcije vraćaju je vrijednost semantičkog atributa odgovarajućih terminalnih simbola, primjerice, vrijednost koju vraća funkcija Izraz() u pseudo kôdu semantičkih akcija je označena s Izraz:t.

2. Unutar definicije funkcija parsera na prikladan način se umeću semantičke akcije koje su definirane na slici 19.5. Primjerice, funkcija Izraz() ima oblik:

TREE Izraz() { TREE t1,t2, t = NULL; if(next_token == OPERATOR) { int op = tokenval.op; next_token = getToken(); t1 = Operand(); t2 = Operand(); t = make_opnode(op, t1, t2); } else syn_error(" nedozvoljen operator!"); return t; }

Podebljano je prikazan kôd semantičkih akcija. Unutar tijela funkcije definirane su tri varijable tipa TREE. Varijabla t je semantički atribut simbola Izraz, i njenu vrijednost vraća ova funkcija. Varijable t1 i t2 su semantički atributi simbola Operand, a dobiju se pozivom funkcije Operand(). Formiranje čvora stabla se vrši tek kada su "prepoznati" operator i oba operanda. Vrijednost semantičkog atributa tokena OPERATOR se bilježi u varijabli op.

Sličan je postupak definiranja funkcija Naredba() i Operand(). Kompletni izvorni kôd je u programu prefix_int.c.

Page 302: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

302

/* Datoteka: prefix_int.c #include <stdio.h> #include <stdlib.h> #include "prefix_lex.h" #include "prefix_tree.h" void syn_error(char *str) /* dojava greške */ { printf("\nGreska: %s",str); exit(1); } int next_token; TREE Izraz(); TREE Operand(); TREE Naredba () /* Naredba : ( Izraz ) | QUIT */ { TREE t; next_token = getToken(); if(next_token == '(') { next_token = getToken(); t = Izraz(); if(next_token != ')') syn_error(" nedostaje ')' !"); } else if(next_token == QUIT) return NULL; else syn_error(" nedozvoljen simbol !"); return t; } TREE Izraz() /* Izraz : OPERATOR Operand Operand */ { TREE t1,t2, t = NULL; if(next_token == OPERATOR) { int op = tokenval.op; next_token = getToken(); t1 = Operand(); t2 = Operand(); t = make_opnode(op, t1, t2); } else syn_error(" nedozvoljen operator!"); return t; } TREE Operand () /* Operand : NUM | - NUM | (Izraz)*/ { TREE t = NULL; if( next_token == '(') { next_token = getToken();

Page 303: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

303

t = Izraz(); if(next_token != ')') syn_error(" nedostaje ')' !"); else next_token = getToken(); } else if( next_token == NUM) { t = make_numnode(tokenval.num); next_token = getToken(); } else if( next_token == OPERATOR && tokenval.op == '-') { next_token = getToken(); if( next_token == NUM) { t = make_numnode(-tokenval.num); next_token = getToken(); } else syn_error("krivo unesen broj !"); } else syn_error("krivo unesen broj !"); return t; } int main(int argc, char **argv) { double rez; TREE t; if(argc == 2) { /* unos iz datoteke */ input = fopen(argv[1],"r"); if (input == NULL) { printf("Ne postoji datoteka imena: %s", argv[1]); exit(1); } } else /* unos tipkovnicom - input == stdin*/ printf( "Unesite prefiksni aritmeticki izraz ili $ za kraj\n>"); while (1) { t = Naredba(); if( t == NULL) break; rez = evaluate(t); if(input == stdin) /* unos tipkovnicom */ printf("\nRezultat: %f\n>", evaluate(t)); else { /* unos iz datoteke */ print_prefiks (t); printf("\n=%f\n", evaluate(t)); } t = tree_delete(t); } if(input != stdin) fclose(input); return 0; }

Program se kompilira komandom:

Page 304: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

304

c:> cl prefix_int.c prefix_tree.c prefix_lex.c

Program "prefix_int.exe" se može koristiti na dva načina. Prvi je način da se program pozove bez argumenata komandne linije. Tada program čeka da korisnik unese aritmetički izraz ili znak za kraj unosa ($) s tipkovnice. Drugi je način da se program pozove s argumentom koji je ime datoteke u kojoj je zapisano više prefiksnih izraza, primjerice, komandom

c:> prefix_int pr.txt

analizira se tekstualna datoteka "pr.txt". Ako ona ima sljedeći sadržaj:

(* 8 9) (* 8 (+ 8 9)) (+ 8 (+ 8 9))

program ispisuje rezultat u obliku:

( * 8.000000 9.000000 ) =72.000000 ( * 8.000000 ( + 8.000000 9.000000 ) ) =136.000000 ( + 8.000000 ( + 8.000000 9.000000 ) ) =25.000000

Primijetite da u datoteci "pr.txt" nije korišten znak $, koji pri unosu s tipkovnice predstavlja naredbu za kraj unosa, jer program prihvaća i znak za kraj datoteke (EOF) kao naredbu za kraja unosa. Zadatak: Napišite interpreter infiksnih izraza, za kojeg vrijedi sljedeća gramatika:

Naredba : Izraz ENTER ; Izraz : Clan '+' Izraz | Clan '-' Izraz | Clan Clan : Faktor '*' Clan | Faktor '/' Clan | Faktor Faktor : NUM | - NUM | ( Izraz )

Tokeni su: ENTER - znak nove linije

NUM - realni broj +,-,*,/ - operatori

Interpreter očekuje da korisnik otkuca aritmetički izraz u infiksnom obliku, primjerice

67.8*(7-9) i da pritisne <enter>, tj znak nove linije. Nakon toga interpreter ispisuje rezultat.

U analizi ovog zadatka prvo treba primijetiti da prethodna gramatika nije LL(1) jer u drugom i trećem pravilu postoje zajednički lijevi prefiksi. Zbog toga, treba koristiti modificiranu gramatiku:

Naredba : Izraz ENTER Izraz : Clan IzrazOptopt IzrazOpt : '+' Izraz

| '-' Izraz Clan : Faktor ClanOptopt ClanOpt: '*' Clan

Page 305: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

305

| '/' Clan Faktor : NUM | - NUM | ( Izraz )

Ovo je LL(1) gramatika i za nju se može napisati rekurzivni silazni parser. Primjerice, funkcija parsera za pravilo Clan ima oblik:

TREE Clan () { TREE t2, t1; t1 = Factor(); if(next_token == '*' || next_token = '/') { int tok = next_token; t2 = ClanOpt(); return make_tnode(tok, t1, t2); } else return t1; } TREE ClanOpt() { if(is_next_token('*')) { match('*'); return Clan (); } else if(is_next_token('/')) { match('/'); return Clan (); } else syn_error("greška") }

Napišite ostale funkcije parsera i izvršite potrebne izmjene u leksičkom analizatoru (funkcija getToken() treba vraćati poseban token za sve operatore i za znak nove linije).

19.4 Stabla s proizvoljnim brojem grana Ako čvorovi stabla imaju više djece, kao u M-stupanjskom stablu tada se oni mogu opisati samoreferentnom strukturom Node koja sadrži niz od M pokazivača na djecu čvora.

#define NUM_CHILD M typedef int elemT: typedef struct _node { void * data; struct _node * child[NUM_CHILD]; } Node;

Čvor se formira pomoću funkcije makeNode():

Node * makeNode( elemT elem, Node *N0, Node *N1,..., Node *Nm) { Node *n=(Node *) malloc(sizeof(Node)); if(n) { n->elem = elem; n->child[0] = N0;

Page 306: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

306

n->child[1] = N1; ..... n->child[m] = Nm; } return n; }

Ako podstabla imaju različite stupnjeva, zovemo ih višesmjerna stabla (eng. multiway tree). U tom slučaju može se koristiti prethodna struktura tako da se svi child[i] pokazivači postave jednakim nuli za i-smjerni čvor. Primjerice, dvosmjerni čvor bi formirali naredbom:

makeNode( elem, N0, N1, NULL,..,NULL);

Loša strana ovakvog pristupa je da se troši memorija za pokazivače nepostojećih čvorova. Taj se problem može riješiti na dva načina. Prvi je da se i niz child alocira dinamički te da se u strukturi Node bilježi broj alociranih čorova. Tada struktura Node može biti ovakva:

typedef struct _node { elemT elem; int num_child; /*broj alociranih čvorova*/ struct _node **child; /*pokazivač na niz pokazivača*/ } Node;

Drugi način za stvaranje čvorova višesmjernog stabla je da se koriste dva pokazivača. Prvi, nazvat ćemo ga first_children, pokazuje prvo dijete čvora, a drugi, nazvat ćemo ga next_sibling pokazuje na listu braće prvog djeteta.

typedef struct _node { elemT elem; struct _node *first_child; struct _node *next_sibling; } Node;

Dakle, next_sibling je pokazivač glave jednostruko vezane liste, koja sadrži djecu čvora osim prvog djeteta. Kraj liste se uvijek označava NULL pokazivačem. Za stvaranje čvorova i uspostavljanje veza među njima mogu poslužiti sljedeće funkcije:

Node *makeLeaf(elemT elem) { /*stvaranje lista*/ Node *n= (Node*)malloc(sizeof(Node)); if(n) { n->first_child = n->next_sibling = 0; n->elem = elem; } return n; } Node * make1ChildNode(elemT elem, Node *N1) { /*čvor s jednim djetetom*/ Node *n=makeLeaf(elem); if(n) n->first_child = N1; return n; } Node * make2ChildNode(elemT elem, Node *N1, Node *N2) { /*čvor s dva djeteta*/ Node *n=makeLeaf(elem); if(n) {

Page 307: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

307

n->first_child = N1; N1->next_sibling = N2; N2->next_sibling = 0; } return n; } Node * make3ChildNode(elemT elem, Node *N1, Node *N2, Node *N3) { /*čvor s tri djeteta*/ Node *n=makeLeaf(elem); if(n) { n->first_child = N1; N1->next_sibling = N2; N2->next_sibling = N3; N3->next_sibling = 0; } return n; } itd.

Veze čvora zapisane u Node *child[3];

Veze čvora zapisane u Node *first-child Node *next-sibling

Ekvivalentno binarno stablo

1 /|\ / | \ / | \ 2 3 4 / \ | 5 6 7 / \ 8 9 a)

1 / / / 2---3---4 / / 5---6 7 / 8---9 b)

1 / 2 / \ 5 3 \ \ 6 4 / 7 / 8 \ c) 9

Slika 19.9 Tri načina zapisa stabla kojem čvorovi imaju promjenjljivi broj djece. a) pokazivači čvorova su u nizu child[], b) pokazivači čvorova su first_child i elementi liste next_sibling, c) ekvivalentno binarno stablo koje se dobije iz drugog oblika rotiranjem liste čvorova braće za 45o u smjeru okretanja kazaljke sata.

Važno je primijetiti da zapis stabla pomoću first_child i next_sibling čvorova ima ekvivalentan zapis pomoću binarnog stabla. To je pokazano na slici 19.9. Stablo je oformljeno naredbama:

Node *N1 = make2ChildNode (7, makeLeaf(8), makeLeaf(9)); Node *N2 = make1ChildNode (4, N1); Node *N3 = make2ChildNode (2, makeLeaf(5), makeLeaf(6)); Node *N0 = make3ChildNode (1, N3, makeLeaf(3), N2);

Ekvivalentno binarno stablo se dobije iz višesmjernog stabla (first_child-next_sibling tipa) "rotiranjem" veza next_sibling liste za 45o u smjeru okretanja kazaljke sata.

Page 308: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

308

Definicija: Višesmjerno stablo T se može predstaviti odgovarajućim binarnim stablom B. Ako su {n1,..., nk} čvorovi T, a {n'1,..., n'k} čvorovi B, tada čvor nk odgovara čvoru n'k. Oni imaju isti sadržaj. Ako je nk korijen od T, tada je n'k korijen od B. Veze među čvorovima su:

• Ako je nl prvo dijete od nk, tada je n'l lijevo dijete od n'k. (Ako nk nema djece, tada n'k nema lijevo dijete.)

• Ako je ns sljedeći neposredno desni brat od nk, tada je n's desno dijete od n'k.

Bez obzira na ovu ekvivalentnost, sva tri opisana oblika implementacije višesmjernog stabla imaju različiti apstraktni značaj pa se i koriste u različitim primjenama. Zapis s nizom pokazivača djece često se koristi kod izrade sintaktičkih stabala, a zapis s first_child i next_sibling pokazivačima je uobičajen kod interpretetera jezika LISP i SCHEME.

Algoritmi za obilazak višesmjernog stabla slični su algoritmima binarnog stabla, i lako se definiraju rekurzivno: Algoritam: visina stabla kojem je korijen n tree_height(n) = Ako je n != 0 tada vrati 0, inače

vrati 1 + MAX(tree_height (child1), … tree_height (childn) ) Algoritam: veličina stabla kojem je korijen n tree_size(n) = Ako je čvor n != 0 tada vrati 0, inače

vrati 1 + tree_size(child1) + … + tree_size(childn) Algoritam: obilazak stabla - postorder postorder (n) = Ako je čvor n != 0 tada za svako dijete od n pozovi postorder(dijete).

Posjeti čvor. Algoritam: obilazak stabla - preorder preorder (n) = Ako je čvor n != 0 tada

Posjeti čvor n, zatim za svako dijete od n pozovi postorder(dijete). Primjer: funkcija postorder() vrši postorder obilazak stabla i na čvorove primjenjuje funkciju (*visit)().

void postorder(Node * n, void (*visit)(Node *)) { Node * c; if (n == NULL) return; c = n->first_child; while (c != NULL) { postorder(c, visit); c = c->next_sibling; } visit(n); }

Stablo, kojem je pokazivač korijena t, možemo izbrisati naredbom postorder(t, free);. Primjer: funkcija tree_size() vrši postorder obilazak stabla i vraća broj čvorova stabla.

int tree_size(Node * n) { /* postorder obilazak */

Page 309: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

309

Node * c; int m; if (n == NULL) return 0; m = 1; c = n->first_child; while (c != NULL) { m += tree_size(c); c = c->next_sibling; } return m; }

Primjer: funkcija print_prefiks()vrši preorder obilazak stabla i ispisuje prefiksnu notaciju stabla.

void print_prefiks (Node * n) { Node *x; if(n == NULL) { printf(" () "); return;} if(isLeaf(n)) { printf("%d ",n->data); return; } printf("( %d ",n->data); x = n->first_child; while(x != 0) { print_prefiks (x); x = x->next_sibling; } printf(" )"); }

Ako se ova funkcija primijeni na stablo koje je prikazano na slici 19.9 (print_prefiks(N0);), dobije se ispis:

( 1 ( 2 5 6 ) 3 ( 4 ( 7 8 9 ) ) )

Pitanje: Zašto kod višesmjernih stabala nema smisla "inorder" obilazak stabla?

Obilazak stabla može biti i proizvoljan. Tada obično korisnik ispituje sadržaj čvora i na temelju toga određuje u kojem smjeru će dalje obilaziti stablo.

19.5 Prioritetni redovi i hrpe U prethodnim odjeljcima stabla su programski implementirana pomoću pokazivača i

samoreferentnih struktura. Sada će biti pokazan primjer u kojem se stabla mogu efikasno implementirati pomoću nizova. Analizirat će se posebni tipovi stabala, odnosno nizova, koji se nazivaju hrpa i pomoću kojih će biti realizirani tzv. prioritetni redovi (eng. priority queue).

Najjednostavnije kazano, prioritetni red je skup podataka u kojem se svakom elementu skupa pridružuje oznaka prioriteta . Prioritetni redovi se često koriste. Evo dva primjera:

Primjer 1: Pacijenti ulaze u čekaonicu, te iz nje odlaze liječniku na pregled. Prvi na redu za liječnika nije onaj koji je prvi ušao, već onaj čije je stanje najteže. Primjer 2: U računalu više procesa čeka u redu za izvršenje. Redoslijed kojim se izvršavaju procesi određen je prioritetom procesa. Procesor uzima iz reda program s najvećim prioritetom, te ga izvodi.

U odnosu na obične redove, koji sadrže neki element x, prioritetni redovi sadrže elemente koji imaju dva atributa: ključ(x) i vrijednost(x). Ključ simbola određuje njegov prioritet. Najčešće se uzima da najveći prioritet ima simbol s najmanjim ključem, ali ponekad se uzima i obrnuto, da najveći prioritet ima simbol s najvećim ključem.

Page 310: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

310

Kod prioritetnog reda važne su samo dvije operacije : ubaci element i izbaci element s najmanjim ključom (ili alternativno, s najvećim ključem). Ovdje ćemo pokazati izvedbu apstraktnog dinamičkog tipa PQUEUE koji je definiran na sljedeći način:

ADT PQUEQUE insert(Q, x) delete_min(Q) size(Q)

- ubacuje element x u red Q. - vraća najmanji element reda i izbacuje ga iz reda Q. Operacija nije definirana ako Q ne sadrži ni jedan element, tj ako je size(Q) = 0. - vraća broj elemenata u prioritetnom redu Q.

Ovaj ADT se može jednostavno realizirati pomoću linearne sortirane liste. U tom slučaju operacija delete_min(), u kojoj se pronalazi i izbacuje prvi element u listi, ima vremensku složenost O(1). Operacija insert() mora ubaciti novi element na “sortirano mjesto”. To znači da u prosjeku treba obići bar pola sortirane liste, pa je vremenska složenost operacije insert() O(n).

Efikasnija implementacija se može napraviti pomoću hrpe (eng. heap). Hrpa je naziv za potpuno binarno stablo koje je zapisano pomoću niza elemenata, prema sljedećim pravilima:

1. Pravilo rasporeda: Potpuno stablo s N čvorova, koji sadrže elemente tipa <ključ,vrijednost>, može se bilježiti u nizu elemenata, na indeksima i = 1,2,..N-1, prema sljdedećem pravilu:

Ako element indeksa i predstavlja čvor stabla, tada - element indeksa 2*i+1 predstavlja lijevo dijete čvora, - element indeksa 2*i+2 predstavlja desno dijete čvora, - element indeksa (i-1)/2 predstavlja roditelja čvora,

2. Pravilo hrpe: Ključ roditalja nekog čvora uvijek je manji od ključa tog čvora, pa je najmanji ključ u korijenu stabla.

32

24

2665

6931 19

21 16

13

13 21 16 24 31 19 69 65 26 32

Slika 19.10 Potpuno binarno stablo i ekvivalentni niz hrpe.

Na slici 19.10 prikazano je potpuno binarno stablo i ekvivalentni niz hrpe. Uočimo:

- indeks namanjeg elementa (koji je u korijenu stabla) jednak je i = 0. - indeks kranjeg elementa je N-1. To je krajnji desni list stabla.

Kapacitet niza treba biti veći od broja elemenata niza. Zbog toga je povoljno da se niz realizira kao dinamički niz koji se može povećavati (realocirati) ako, pri operaciji umetanja, broj elemenata niza dosegne kapacitet niza.

Page 311: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

311

Postupno će biti opisana implementacija ADT PQUEUE na temelju specifikacije koja je opisana u datoteci "pq.h".

/* Datoteka: pqueue.h */ /* Specifikacija ADT PQUEUE */ typedef struct pq_elem { /* element prioritetnog reda */ int key; /* ključ za postavljanje prioriteta */ void *val; /* vrijednost elementa (bilo što) */ } elemT; PQUEUE pqueue_new(int capacity) /* Konstruktor ADT-a. * Argument: capacity - određuje početni kapacitet hrepe * Vraća: pokazivač ADT-a */ void pqueue_free(PQUEUE pQ) /*Destruktor objekta pQ*/ unsigned pqueue_size(PQUEUE pQ); /*Vraća broj elemenata u redu pQ*/ void pqueue_insert(PQUEUE pQ, elemT x); /* Umeće element na kojeg pokazuje px u red * Argumenti: pQ - red * x - element kojeg se umeće */ elemT pqueue_delete_min(PQUEUE pQ); /* Briše najmanji element iz reda PQ * Vraća: element koji je izbrisan */

Implementacija ADT PQUEUE je opisana u datoteci "pq.c".

/* Datoteka: pq.c */ #include <stdlib.h> #include <stdio.h> #include <memory.h> #include <assert.h> #include "pq.h" typedef struct pq { int capacity; /* kapacitet PQUEUE */ int N; /* broj umetnutih elemenata */ elemT *elements; /* niz elemenata reda */ } PQueue, *PQUEUE;

PQUEUE pqueue_new(int capacity) { PQUEUE pq = (PQUEUE)malloc(sizeof(PQueue)); if(pq) { pq->N=0; pq->capacity=capacity; pq->elements = malloc(sizeof(elemT)*capacity); if(!pq->elements) exit(1); } return pq;

Page 312: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

312

} void pqueue_free(PQUEUE pQ) { /*Destruktor ADT-a*/ assert(pQ); free(pQ->elements); free(pQ); }

19.5.1 Umetanje elementa u prioritetni red Ako želimo dodati element u prioritetni red možemo ga dodati na kraj niza. To je ekvivalentno dodavanju krajnjeg desnog lista u stablu (prikazan crtkano na slici 19. 10). Ta operacija ima za posljedicu da se N uveća za jedan, ali i da možda stablo ne zadovoljava pravilo hrpe. Da bi se zadovoljilo pravilo hrpe koristi se postupak, ilustriran na slici 19.11, opisan u sljedećem algoritmu: Algoritam: Umetanje elementa x u hrpu

1. Element iza krajnjeg elementa niza se tretira kao prazan (to je sljedeći krajnji desni list stabla). Podrazumijeva se da je kapacitet niza veći od broja elemenata u nizu.

2. Ako je ključ roditelja praznog čvora manji od ključa elementa x, tada se element x upisuje u prazni čvor. Time postupak završen.

3. Ako je ključ roditelja praznog čvora veći od ključa elementa x, tada se element roditelja kopira u prazni čvor, a čvor u kojem je bio roditelj se uzima kao prazni čvor. Postupak se ponavlja korakom 2.

32

24

2665

6931 19

21 16

13

13 21 16 24 31 19 69 65 26 32

17

17

Slika 19.11 Stablo sa slike 19.10 prilikom dodavanja ključa 17. Strelice pokazuju koje elemente treba pomaknuti da bi se otvorilo mjesto za novi element.

Prethodni algoritam je primijenjen u funkciji pqueue_insert(). U toj funkciji se najprije provjerava da li je popunjen kapacitet reda. Ako je popunjen, dvostruko se povećava kapacitet reda. Nakon toga se vrši umetanje elementa prema prethodnome algoritmu.

void pqueue_insert(PQUEUE pQ, elemT x) { int i; assert(pQ); /* provjeri kapacitet reda i povećaj ga ako je N>=kapacitet*/ if ( pQ->N >= pQ->capacity-1 ) { pQ->capacity *= 2; /*udvostruèi kapacitet hrpe*/ pQ->elements = (elemT *) realloc(pQ->elements, sizeof(elemT)*pQ->capacity); if(!pQ->elements) exit(1); }

Page 313: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

313

/* umetni element x i povećaj N */ i = pQ->N++; while (i) { int parent = (i-1)/2; if (x.key > pQ->elements[parent].key) break; pQ->elements[i].key = pQ->elements[parent].key; i = parent; } pQ->elements[i] = x; }

19.5.2 Brisanje elementa iz prioritetnog reda Brisanje elementa iz prioritetnog reda se vrši samo na jedan način: briše se element s minimalnim ključem pomoću funkcije delete_min(). Minimalni element se nalazi u korijenu stabla, odnosno u hrpi na indeksu 0. Ako ga izbrišemo tada je korijen prazan i u njega treba zapisati neki drugi element. Najzgodnije je u njega zapisati krajnji element niza i smanjiti veličinu hrpe. Problem je što tada možda nije zadovoljeno pravilo hrpe. Dovođenje niza u stanje koje zadovoljava pravilo hrpe vrši se funkcijom heapify(), prema sljedećem algoritmu: Algoritam: uređenje hrpe kada vrh hrpe nije u skladu s pravilom hrpe

1. Započni s indeksom koji predstavlja vrh hrpe. Spremi taj element u varijablu x. Nadalje se uzima da je vrh hrpe "prazan".

2. Analiziraj djecu praznog čvora i odredi koje dijete je manje. 3. Ako je ključ od x manji od ključa manjeg djeteta spremi x u prazan čvor i završi,

inače upiši element manjeg djeteta u prazan čvor i postavi da čvor manjeg djeteta bude prazan čvor. Ponovi korak 2.

void heapify(elemT *elements,int vrh, int N) { int min_child, i = vrh; /*zapamti element kojem tražimo mjesto u hrpi */ elemT x = elements[i]; /* (i) označava "prazni" element*/ while (i < N/2) { int left = min_child = 2*i+1; int right = left + 1; /* Prvo odredi indeks manjeg djeteta - min_child */ if ( (left < N-1) && (elements[left].key > elements[right].key) ) min_child = right; /* Ako je min_child manji od x, upiši ga na prazno mjesto (i), inače break */ if ( x.key < elements[min_child].key) break; elements[i] = elements[min_child]; i = min_child; } /* konačno stavi x na prazno mjesto*/ elements[i] = x; }

Sada je implementacija funkcije pqueue_delete_min() jednostavna. u njoj je najprije zapamćen minimalni element, koji je na indeksu 0, u varijabli minimum. Zatim je krajnji element upisan na početak niza, a veličina hrpe je smanjena. Slijedi poziv funkcije heapify(), kojom se uređuje hrpa, ako je poremećeno pravilo hrpe. Postupak brisanja elementa je ilustriran na slici 19.12.

Page 314: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

314

elemT pqueue_delete_min(PQUEUE pQ) { elemT minimum; assert(pQ); assert( pQ->N > 0 ); /* minimum je u korijenu (indeks 0) */ minimum = pQ->elements[0]; /* zadnji element prebaci u korijen i smanji veličinu hrpe*/ pQ->elements[0] = pQ->elements[--pQ->N]; /* preuredi hrpu od vrha prema dnu (vrh je na indeksu 0) */ heapify(pQ->elements, 0, pQ->N); return minimum; }

32

24

2665

6921 19

17 16

13

31 32

24

2665

6921 19

17

16

32

24

2665

6921 31

17 19

16

X > 16

minumum

X > 19 x

X = 31

Slika 19.12 Primjer kako se izvršava operacija delete_min(). Iz reda se odstranjuje ključ 13 koji je na vrhu hrpe. Strelice pokazuju koje elemente treba pomaknuti da bi se otvorilo mjesto za element s kraja hrpe ( x=31).

Testiranje se provodi programom "pq-test.c". U programu se najprije 10 slučajno generiranih brojeva upisuje u prioritetni red pQ, a zatim se briše element po element i ispisuje vrijednost ključa.

/* Datoteka: pq-test.c*/ /* 1. Generira se 10 slučajnih brojeva i sprema u pqueue * 2. Briše iz pqueue i ispisuje element po element */ #include <stdlib.h> #include <stdio.h> #include <memory.h> #include <assert.h> #include "pq.h" int main() { int i; elemT elem; PQUEUE pQ =pqueue_new(20); printf("Slucajno generirani brojevi:\n"); for(i=0; i<10; i++) { elem.key = rand() % 100 +1; pqueue_insert(pQ, elem); printf("%d ", elem.key); }

Page 315: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

315

printf("\nIzlaz iz prioritetnog reda:\n"); while(pqueue_size(pQ)) { elem = pqueue_delete_min(pQ); printf("%d ", elem.key); } pqueue_free(pQ); printf("\n"); return 0; }

Kada se program izvrši dobije se ispis:

Slucajno generirani brojevi: 42 68 35 1 70 25 79 59 63 65 Izlaz iz prioritetnog reda: 1 25 35 42 59 63 65 68 70 79

Vidimo da je izlaz sortiran, jer smo iz reda brisali jedan po jedan minimalni element. Vremenska složenost operacija insert() i delete_min() je malena jer se obje operacije provode samo po jednoj stazi od vrha do dna hrpe ili obrnuto. Kod potpunog stabla duljina staze iznosi log2(n+1)-1. Pošto nas zanima asimptotska vrijednost (za veliki n), vremenska složenost iznosi O(log2n).

19.5.3 Sortiranje pomoću prioritetnog reda i heapsort metoda Prethodni prinjer testiranja ADT PQUEUE pokazuje kako se može izvršiti sortiranje pomoću prioritetnog reda

Algoritam: sortiranje niza A pomoću prioritetnog reda

1. formiraj objekt tipa PQUEUE pQ. 2. za sve elemente niza A[i], i =0,1,..N-1 pqueue_insert(pQ, A[i]) 3. za sve i =0,1,..N-1 A[i] = pqueue_deletemin(pQ); 4. dealociraj objekt pQ

Ovaj algoritam ima vremensku složenost O(n logn), jer se u njemu n puta ponavljaju operacije umetanja i brisanja, a one imaju složenost O(logn). Loša strana algoritma je da zahtijeva dodatnu memoriju veličine originalnog niza.

Funkcija heapify() se može efikasno upotrijebiti za sortiranje nizova bez upotrebe dodatne memorije. Algoritam je jednostavan i primijenjen je u funkciji heapsort(). Sastoji se od dva koraka. U prvom koraku se od proizvoljnog niza stvara hrpa. Funkcija heapify() se primjenjuje n/2 puta, na svim unutarnjim čvorovima, najprije na najmanjem podstablu, koji je trivijalno potpuno stablo, a zatim iterativno na svim većim podstablima do samog korijena. U drugom koraku se element nultog indeksa (koji je u ovom slučaju minimalan) iterativno zamjenjuje s krajnjim elementom, a zatim se funkcija heapify() primjenjuje na niz s umanjenim brojem elemenata.

void heapsort(elemT *A, int N ) { /* sortira niz A duljine N -> A[i] > A[i+1]*/ int i; /*1. stvari hrpu od postojeceg niza */

Page 316: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

316

for(i = N/2-1; i >= 0; i--) heapify( A, i, N ); /*2. zamijeni minimum s posljednjim elementom i uredi hrpu */ for(i = N-1; i>0; i-- ) { elemT tmp = A[i]; A[i]=A[0]; A[0]= tmp; heapify( A, 0, i); } }

Ova verzija heapsort() funkcije sortira nizove od veće prema manjoj vrijednosti. Ako bi trebali suprotan raspored elemenata tada treba izmijeniti funkciju heapify(), tako da ona stvara hrpu s najvećim elementom na vrhu hrpe. Izmjena je jednostavna - treba u naredbama usporedbe zamijeniti operatore < i >.

Vremenska složenost heapsort metode je O(nlog2n) i to u najgorem slučaju, jer se u njoj funkcija heapify() poziva maksimalno 3n/2 puta. Brzina izvršenja u prosječnom slučaju je nešto sporija nego kod primjene funkcije quicksort(), ali postoje slučajevi kada heapsort() daje najveću brzinu izvršenja. Zadatak: Napišite verziju ADT-a za prioritetni red PQ kojem je na vrhu hrpe element s maksimalnim ključem.

ADT PQ insert(Q, x) delete_max(Q,) size(Q)

- ubacuje element x u red Q. - vraća najveći element reda i izbacuje ga iz reda Q Operacija nije definirana ako Q ne sadrži ni jedan element, tj ako je size(Q) = 0. - vraća broj elemenata u prioritetnom redu Q.

19.6 Zaključak Stabla omogućuju jednostavnu apstrakciju različitih podatkovnih struktura, od slike

porodičnog stabla do jezičkih sintaktičkih i semantičkih veza. U sljedećem poglavlju bit će pokazano da stabla omogućuju stvaranje općih podatkovnih struktura tipa tablice, rječnika i skupova.

Stabla se najčešće koriste u obliku binarnog stabla. Pokazano je da se višesmjerna stabla mogu prikazati ekvivalentnim binarnim stablima.

Pokazana su dva načina programske implementacije stabla. U prvoj se stablo realizira pomoću samoreferentnih struktura koje sadrže pokazivače, a u drugoj se stablo realizira pomoću prostih nizova.

Korištenje stabala je uvijek povezano uz neku primjenu za koju se definira potreban skup operacija. Pokazana je potpuna realizacija interpretera prefiksnih aritmetičkih izraza, a pokazano je i kako se može napraviti interpreter prefiksnih izraza. Izneseni su temelji sintaktičke analize jednostavnih jezičkih konstrukcija koje zadovoljavaju LL(1) tip gramatike. Pokazano je kako se realizira jednostavni leksički analizator i kako se iz BNF notacije gramatike realizira rekurzivno silazni parser.

Opisana je izvedba apstraktnog tipa podataka PQUEUE kojom se stvaraju prioritetni redovi. Korištena je struktura tipa hrpe i pomoću nje je realizirana metoda sortiranja koja se naziva heapsort.

Page 317: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

317

20 Strukture za brzo traženje podataka

Naglasci: • Tablice simbola i rječnici • Hash tablica • Otvorena hash tablica • Zatvorena hash tablica • BST - binarno stablo traženja • RBT - crveno-crno stablo

20.1 Tablice simbola i rječnici Tablice simbola i rječnici imaju sličnu karakteristiku, a to je da svaki simbol ima neku ključnu oznaku (ključ) i vrijednost (ili definiciju) koja opisuje značaj simbola. Može se i reći da je tablica simbola skup parova <ključ, vrijednost>. Ključ je najčešće tipa string, ali može biti i numerička vrijednost. Tri su temeljne operacije koje definiraju ADT tablice (ili rječnika):

ADT TABLE

insert (T, k, v) find (T, k) delete(T, k)

- umeće simbol, ključa k i vrijednosti v u tablicu T, ako već nije u T. - vraća vrijednost simbola ako u T postoji ključ k, - iz tablice T briše simbol kojem je ključ k, ako postoji .

Brzina ovih operacija ovisna je o tome koja struktura podataka je upotrijebljena za tablicu simbola. Za implementaciju tablice moguće je koristiti nizove i liste. To ćemo pokazati primjerom. Primjer: Implementacija tablice pomoću niza.

Simbol opisuje struktura:

typedef struct symbol { char *key; /* ključ simbola */ void *val; /* pokazivač vrijednost simbola */ }Symbol;

Tablica se sastoji od niza simbola :

typedef struct _table { Symbol *array; /* alocirani niz simbola */ int N; /* broj simbola u nizu */ int M; /* kapacitet alociranog niza */ } *TABLE

Primjer funkcije za traženje:

Page 318: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

318

Symbol *find(TABLE T, char *key) { /* vraća pokazivač simbola ili NULL */ int i; for (i = 0; i < T->N; i++) if(!strcmp(T->array[i].key, key); return &T->array[i]; return NULL; } /*sami napišite funkcije insert() i delete()*/

Zbog jednostavnosti uzeto je da je ključ tipa string i da je vrijednost simbola označana pokazivačem tipa void * (dakle, pokazivačem na bilo koji tip podatka). Primjer: Implementacija tablice pomoću vezane liste se može temeljiti na sljedećim strukturama:

typedef struct symbol { struct symbol *next /* veza u listi */ char * key; /* ključ simbola */ void * val; /* pokazivač vrijednost simbola */

}Symbol; typedef struct _table { Symbol *list; /* tablica je lista*/ } *TABLE Symbol *find(TABLE T, char * key) { Symbol *L = T->list; while( L != NULL) { if (!strcmp(L->key, key)) return L; L = L->next; } return NULL; }

U oba slučaja kompleksnost algoritma traženja je O(n) pa ovakova rješenja mogu zadovoljiti samo za implementaciju manjih tablica. Kada je potrebno imati veće tablice cilj je smanjiti kompleksnost na O(log n) ili čak na O(1). Ta poboljšanja se mogu ostvariti pomoću tzv. "hash" tablica i specijano izvedenih stabala za traženje (BST, AVL, RED-BLACK, B-tree). Neke od ovih struktura upoznat ćemo u ovom poglavlju.

20.2 Hash tablica Ideju hash2 tablice se može opisati na slijedeći način. Kada u telefonskom imeniku tražimo

neko ime, najprije moramo pronaći stranicu na kojoj je zapisano to ime, a zatim pregledom po stranici dolazimo do traženog imena. Iskustvo pokazuje da više vremenu treba za traženje stranice, nego za traženje imena na stranici. Zbog toga se kod telefonskih imenika grupe stranica označavaju slovima abecede, kako bi se brže pronašla željena stranica. Na sličan način podaci u hash tablici se grupiraju u više skupova (ili buketa). U kojem skupu se nalaze podaci, određuje se iz ključa simbola pomoću posebne funkcije koja se naziva hash-funkcija.

2 eng. hash – znači podjelu na manje dijelove, koji su otprilike jednake veličine

Page 319: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

319

Definicija: Funkcija hash(k,m) raspodjeljuje argument k u jedan od m skupova, sa statistički uniformnom razdiobom. Funkcija vraća vrijednost iz intervala i ∈ [0..m-1]. Ta vrijednost označava da string pripada i-tom skupu.

Postoji više izvedbi hash-funkcije koje zadovoljavaju navedenu definiciju. Za rad sa

stringovima ovdje će biti korišten sljedeći oblik hash-funkcije:

unsigned hash(char *s, unsigned m) { /* Argument: s – string * m - maksimalni broj grupa * Vraća: hash vrijednost iz intervala [0..m-1] */ unsigned x=0; while(*s != '\0') { s = *s + 31 * x; k++; } return x % m; }

Može se primijetiti sličnost ove funkcije s funkcijom rand(), koja je opisana u poglavlju 11. Tada je slučajni broj xi , iz intervala [0..n-1], bio generiran iz prethodne vrijednost xi-1, prema izrazu:

xi = (a * xi-1 + b) % m gdje su a i b konstante, koje su određene uvjetom da se broj generira sa statistički uniformnom razdiobom. U slučaju prikazane hash-funkcije ova se zakonitost primjenjuje kumulativno za sve elemente stringa (a=31, b=*s). Kada je ključ cjelobrojnog tipa tada se može koristiti funkcija

unsigned hash(int k, unsigned M) { return k > 0? k % M : - k % M; }

20.2.1 Otvorena hash tablica Sada će biti opisana izvedba tablice simbola, koja se naziva otvorena hash-tablica (eng.

Open Hashing ili Separate Chaining). Ilustrirana je na slici 20.1. Otvorena hash tablica se realizira kao niz koji sadrži pokazivače na liste simbola koje imaju istu hash vrijednost. Te liste se nazivaju buketi (eng. bucket).

Page 320: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

320

Slika 20.1 Hash tablica s vezanom listom

Operacije se provode prema sljedećem pravilu:

insert(k,v) Umetni simbol<k, v> na glavu liste bucket[hash(k, m)]. find (k) Traži simbol ključa k u listi bucket[hash(k, m)]. Vraća

simbol ili NULL delete(k) Odstrani simbol<k, v> iz liste bucket[hash(k, m)].

Element niza buckets[i] je pokazivač i-te liste. Početno su svi pokazivači jednaki NULL. Kada u tablici treba tražiti ili unijeti simbol kojem je ključno ime key, to se vrši u listi kojoj je pokazivač jednak buckets[hash(key,m)]. Slijedi opis implementacije tablice, prema specifikaciji ADT TABLE iz datoteke "table.h". Specifikacija za generičku hash tablicu /*Datoteka: table.h */ /*Apstraktni tip TABLE */ typedef struct _table *TABLE; typedef struct _symbol *SYMBOL; /* Primjer za string */ typedef int (*CompareFuncT)(void *, void *); /* strcmp() */ typedef void *(*CopyFuncT)(void *); /* strdup() */ typedef void (*FreeFuncT)(void *); /* free() */ typedef unsigned (*HashFuncT)(void *, unsigned); /* hash() */ TABLE table_new(unsigned m, CompareFuncT f, HashFuncT h); /* Konstruktor tablice * Argument: m - veličina tablice * f - pokazivač funkcije za poredbu ključa (kao strcmp) * h - pokazivač hash funkcije (kao hash_str) * Ako se umjeto pokazivača f i h upiše 0 * podrazumjeva se rad s ključem tipa int * Vraća: pokazivač tablice ili NULL ako se ne može oformiti tablica */ void table_set_alloc(TABLE T, CopyFuncT copykey, FreeFuncT freekey, CopyFuncT copyval, FreeFuncT freeval); /* Postavlja pokazivače funkcija za alociranje ključa i vrijednosti * Argumenti: copykey - pokazivač funkcije za alociranje ključa * freekey - pokazivač funkcije za dealociranje ključa * copyval - pokazivač funkcije za alocira vrijednosti

Page 321: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

321

* freeval - pokazivač funkcije za dealocirane vrijednosti */ void table_free(TABLE T); /* Briše tablicu - oslobađa memoriju*/ int table_insert(TABLE T, void *k, void *v); /* Umeće simbol <k,v>, u tablicu simbola, ako već nije u tablici. * Argumenti: T - tablica simbola (rječnik) * k - ključ simbola * v - pokazivač vrijednosti simbola * Vraća: 1 ako je umetanje uspješno, inaće vraća 0 */ SYMBOL table_find(TABLE T, void *k); /* Traži simbol poznatog ključa k u tablici simbola T * Argumenti: T – tablica simbola (rječnik) * k – ključ simbola * Vraća: pokazivač simbola, * ili NULL ako simbol nije pronađen */ void *table_symbol_value(SYMBOL S); /* Vraća pokazivač vrijednosti simbola S * ili cijeli broj, ako je vrijednost cijeli broj */ void *table_symbol_key(SYMBOL s); /* Vraća pokazivač ključa simbola S * ili cijeli broj, ako je ključ cijeli broj */ int table_delete(TABLE T, void *k); /* Traži simbol poznatog ključa k i briše ga iz tablice * Argumenti: T – tablica simbola (rjecnik) * k – ključ simbola * Vraća: 1 ako je simbol izbrisan ili 0 ako simbol nije u tablici */ unsigned table_size(TABLE T); /* Vraća broj elemenata u tablici */ Pokazivači ključa i vrijednosti simbola su tipa void *. Na taj način specifikacija se može koristiti za implementaciju generičkih tablica, za bilo koji tip ključa i vrijednosti simbola. Za potpunu generičku primjenu ADT-a, nakon inicijalizacije treba registrirati funkcije koje se koriste za dinamičko alociranje i dealociranje memorije. To se vrši funkcijom table_set_aloc(). Njeni argumenti su pokazivači na funkcije za alociranje i dealociranje ključa i vrijednosti simbola. Ovo pokazivači na funkcije se koriste i definiraju na isti način koji je opisan kod ADT DEQUEUE u poglavlju 18. Zbog zahtjeva za generičkim operacijama potrebno je prilagoditi i deklaracije hash funkcija. U datoteci "hash.c" definirane su hash funkcije za rad sa stringovima (hash_str) i cijelim brojevima (hash_int):

/* Datoteka: hash.c */ unsigned hash_str(void *key, unsigned M)

Page 322: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

322

{/* Vraća hash vrijednost iz intervala [0..M-1] za string key */ unsigned hashval=0; char *s = key; while(*s != '\0') { hashval = *s + 31 * hashval; s++; } return hashval % M; } unsigned hash_int(void *p, unsigned M) {/* Vraća hash vrijednost iz intervala [0..M-1] za cjeli broj p */ /* Prvi argument će biti int, iako je deklariran void *p */ int k = (int)p; return k > 0? k % M : - k % M; }

Prema specifikacji ADT TABLE, tablica T, čiji simboli imaju ključ i vrijednost tipa string, inicijalizira se naredbama:

TABLE T = new_table(1023, strcmp, hash_str); table_set_alloc(T, strdup, free, strdup, free);

Veličina tablice se odabire proizvoljno. Poželjno je da m bude prosti broj.

Ako je ključ cjelobrojnog tipa inicijalizacija se vrši samo jednom naredbom. Primjerice, tablicu veličine m=1023, kojoj su ključ i vrijednost tipa int inicijalizira se naredbom:

TABLE T = new_table(1023, 0, 0);

To znači da u implementaciji ADT-a treba predvidjeti automatsko postavljanje funkcije usporedbe ključeva i hash funkcije za rad s cijelim brojevima, kada su drugi i treći argument jednaki nuli.

Pored apstraktnog objekta TABLE, specificiran je apstraktni objekt SYMBOL. Ideja je sljedeća: želimo da su simboli tablice dostupni korisniku. Simboli imaju dva atributa: ključ i vrijednost. Ako želimo pronaći vrijednost (ili ključ) simbola iz tablice, koristimo najprije funkciju table_find(), koja vraća tip SYMBOL, a zatim pomoću funkcije table_symbol_value() ili table_symbol_key(), iz poznatog simbola, dobijemo pokazivač vrijednosti ili ključa simbola. Primjerice, nakon naredbe

char *v = table_symbol_value(table_find(T, "xxx"));

varijabla v pokazuje string koji je vrijednost simbola koji ima ključ "xxx". Ako ključ "xxx" nije u tablici varijabla v sadrži NULL pokazivač. Ako simbol sadrži ključ i vrijednost cjelobrojnog tipa, tada se umjesto pokazivača radi s cijelim brojevima,primjerice

int v = (int) table_symbol_value(table_find(T, 123));

Implementacije ovih funkcija i objekata tipa TABLE i SIMBOL mogu biti različite. Sada će biti pokazana implementacije otvorene hash tablice, a zatim će biti pokazan drugi tip implementacije koji se naziva zatvorena hash tablica.

Implementacija otvorene hash tablice

Page 323: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

323

/* Datoteka htable1.c */ /* Otvorena hash tablica */ #include <stdlib.h> #include <string.h> #include <assert.h> #include "table.h" typedef struct _symbol { /* čvor liste simbola */ struct _symbol *next; /* pokazivač vezane liste */ void *key; /* pokazivač ključa simbola */ void *val; /* pokazivač vrijednosti simbola */ }Symbol; typedef struct _table { unsigned M; /* veličina tablice */ unsigned N; /* broj simbola u tablici */ Symbol **bucket; /* niz pokazivača na listu simbola */ CompareFuncT compare; /* funkcije usporedbe kljuca */ HashFuncT hash; /* hash funkcija */ CopyFuncT copy_key; /* funkcija alociranja ključa */ CopyFuncT copy_val; /* funkcija alociranja vrijednosti */ FreeFuncT free_key; /* funkcija dealociranja ključa */ FreeFuncT free_val; /* funkcija dealociranja vrijednosti*/ } Table, *TABLE; static int compareInternal(void *a, void *b) { /* usporedba dva integera a i b*/ if( (int)a > (int)b ) return 1; else if( (int)a < (int)b ) return -1; else return 0; } static unsigned hash_int(void *p, unsigned M) {/* Vraća hash vrijednost iz intervala [0..M-1] za cjeli broj p */ /* prvi argument će biti int iako je deklariran void *p */ int k = (int)p; return k > 0? k % M : - k % M; } TABLE table_new(unsigned M, CompareFuncT compare, HashFuncT hash) { TABLE T = (TABLE) malloc(sizeof(Table)); if (T == NULL) return NULL; T->M = M; T->N = 0; T->bucket = (Symbol **) calloc(M, sizeof(Symbol *) ); T->hash = hash ? hash : hashInternal; T->compare = compare? compare : compareInternal; T->free_key = T->free_val = NULL; T->copy_key = T->copy_val = NULL; return T; } void table_set_alloc(TABLE T, CopyFuncT copykey, FreeFuncT freekey, CopyFuncT copyval, FreeFuncT freeval) { T->copy_key = copykey; T->copy_val = copyval;

Page 324: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

324

T->free_key = freekey; T->free_val = freeval; } static void free_node(TABLE T, Symbol *n) { assert(T); if(n == NULL) return; /* oslobodi sadržaj čvora: key, val i sam čvor*/ if(T->free_key) T->free_key(n->key); if(T->free_val) T->free_val(n->val); free(n); } static Symbol *new_node(TABLE T, void *key, void *val) { Symbol *np; assert(T); np = (Symbol *)malloc(sizeof(Symbol)); if (np == NULL ) return NULL; np->key =(T->copy_key)? T->copy_key(key) : key; np->val =(T->copy_val)? T->copy_val(val) : val; return np; } void table_free(TABLE T) { unsigned i; assert(T); for(i=0; i<T->M; i++) { Symbol *p, *t; p = T->bucket[i]; /* glava liste */ while (p != NULL) { /* briše listu iz memorije */ t = p; p = p->next; free_node(T, t); } } free(T->bucket); free(T); } unsigned table_size(TABLE T) { assert(T); return T->N; } SYMBOL table_find(TABLE T, void *key) { Symbol *p; assert(T); p = T->bucket[T->hash(key, T->M)]; while( p != NULL) { if (T->compare(key, p->key) == 0) return (void *) p; /* pronađen simbol */ p = p->next; } return NULL; /* nije pronađen */ } void *table_symbol_value(SYMBOL S)

Page 325: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

325

{ if(S) return S->val; else return NULL; } void *table_symbol_key(SYMBOL S) { if(S) return S->key; else return NULL; } int table_insert(TABLE T, void *key, void *val) { SYMBOL s; unsigned h; assert(T); s = table_find(T, key); if (s == NULL) { /* ako ne postoji ključ */ Symbol *np = new_node(T, key, val); if (np == NULL) return 0; h = T->hash(key, T->M); np->next = T->bucket[h]; T->bucket[h] = np; T->N++; return 1: } return 0; } int table_delete(TABLE T, void *key) { Symbol *p , *prev; unsigned h = T->hash(key, T->M); assert(T); prev = 0; p = T->bucket[h]; while (p && T->compare(p->key, key)) { prev = p; p = p->next; } if (!p) return 0; /* p - čvor koji brišemo, prev - prethodni čvor*/ if (prev) prev->next = p->next; else T->bucket[h] = p->next; /* glava buketa */ --T->N; free_node (T, p); return 1; } Testiranje se provodi programom hash_test.c. U njemu se formira tablica simbola, i u nju se unosi nekoliko simbola. Zatim se provjerava da li se simbol "while" nalaze u tablici. Zatim se taj simbol briše i ponovo provjerava da li je u tablici.

/* Datoteka: hash_test.c*/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "table.h" #include "hash.c" int main( ) { char str[20]; SYMBOL s;

Page 326: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

326

TABLE T = table_new(127, strcmp, hash_str); table_set_alloc(T, strdup, free, strdup, free); table_insert(T, "if", "naredba selekcije"); table_insert(T, "while", "petlja"); table_insert(T, "for", "petlja"); table_insert(T, "printf", "C-funkcija"); strcpy(str, "while"); s=table_find(T, str); if(s) printf("\n%s -> %s", str, table_symbol_value(s)); else printf("\n%s -> nije u tablici", str); /*izbriši simbol i provjeri da li je u tablici*/ table_delete(T, str); s = table_find(T, str); if(s) printf("\n%s -> %s", str, table_symbol_value(s)); else printf("\n%s -> nije u tablici\n", str); table_free(T); return 0; }

Nakon izvršenja programa dobije se ispis:

while -> petlja while -> nije u tablici

Hash-tablica s vezanom listom omogućuje realiziranje tablice simbola u kojoj se vrlo brzo izvršavaju operacije traženja i umetanja u tablicu. Kada se radi s vrlo velikim skupom podataka, može se uzeti da u prosječnom slučaju obje operacije imaju složenost O(1). Samo u najgorem slučaju, kada svi ključevi imaju ustu vrijednost, složenost iznosi O(n). Do tog zaključka se dolazi sljedećom analizom složenosti.

Uzmimo da je u tablici s m buketa pohranjeno n simbola. Definirajmo faktor popunjenosti α (eng. load faktor) kao omjer α = n/m. On pokazuje koliko prosječno ima simbola u jednom buketu. Ukupno vrijeme za pretraživanje tablice, uključujući vrijeme za proračun hash funkcije, iznosi O(1+α). Ako uzmemo da je broj buketa m barem proporcionalan broju simbola u tablici n, dobije se n = cm= O(m), i iz toga α = n/m=O(m)/m=O(1). Dakle, pretraživanje u prosjeku ima složenost O(1).

20.2.2 Zatvorena hash tablica Kada otprilike znamo koliko će elemenata sadržavati tablica, tada se ona može efikasno realizirati kao niz koji sadržava simbole, a ne pokazivače na bukete simbola. Ideja je da se simbol upiše u niz na indeksu koji je određen s hash vrijednošću. Pošto više simbola može imati istu hash vrijednost, ta pozicija može biti zauzeta s prethodno upisanim simbolom. U tom slučaju, koji se naziva kolizija, ispituje se da li se simbol može upisati na lokacijama koje sukscesivno, ili po nekom drugom zakonu, slijede iza te lokacije. Na isti način se vrši traženje u tablici. Niz, koji sadrži ovakvu tablicu, mora imati kapacitet m veći od broja simbola n, pa je faktor popunjenosti zatvorene tablice uvijek manji od jedinice (α = n/m <1). Općenito, indeks je funkcija ključa k i broja ispitivanja i. Koriste se tri načina određivanja indeksa:

1. Linerano ispitivanje : indeks(k, i) = (hash(k) +i ) % m

Page 327: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

327

2. Kvadratično ispitivanje: indeks(k, i) = (hash(k) +i2 ) % m 3. Dvostruko "heširanje" : indeks(k, i) = (hash1(k) +i⋅ hash2(k) ) % m

U sva tri slučaja ispitivanje počinje s indeks(k, 0) = hash(k), a operacije ADT TABLE se mogu realizirati na sljedeći način:

insert (k,v) - umeće simbol <k,v> u niz htable, prema algoritmu: Pronađi najmanji i=0,1,.m. za koji htable[indeks(k,i)] nije popunjen

Na tu poziciju upiši simbol <k,v> find(k) - vraća vrijednost simbola koji ima ključ k, ili null, prema algoritmu: Algoritam: Za sve i za koje je redom htable[indeks(k,i)] popunjen,

Ako je ključ simbola htable[indeks(k,i)] jednak k, Vrati vrijednost simbola

Vrati null delete(k) - briše simbol koji ima ključ k, prema algoritmu: Pronađi simbol ključa k Ako je pronađen, označi da je taj simbol izbrisan

Ilustrirajmo proces linearnog ispitivanja: U ovom primeru uzet ćemo niz veličine m=10, ključ je cijeli broj i hash funkcija je oblika hash(k) = k % 10. Početno su sve pozicije prazne, što je označeno simbolom ∅;

0 1 2 3 4 5 6 7 8 9

∅ ∅ ∅ ∅ ∅ ∅ ∅ ∅ ∅ ∅

Ako redom umećemo ključeve 15, 17 i 8, ne postoji preklapanje i rezultat je:

0 1 2 3 4 5 6 7 8 9

∅ ∅ ∅ ∅ ∅ 15 ∅ 17 8 ∅

Umetnimo sada ključ 35. Računamo indeks(k,0) = 35 %10 = 5. Pozicija 5 je zauzeta (s ključem 15), pa moramo ispitati sljedeću poziciju. Indeks(k,1) = (5+1) %10 = 6. Pozicija 6 je slobodna pa u nju umećemo ključ 35.

0 1 2 3 4 5 6 7 8 9

∅ ∅ ∅ ∅ ∅ 15 35 17 8 ∅

Umetnimo sada ključ 25. Najprije probamo poziciju (25 % 10). Ona je zauzeta. Zatim linearno ispitujemo sljedeću poziciju 6 ((5+1) % 10). Ona je također zauzeta. Nastavljamo ispitivati poziciju 7 ((5+2) % 10); ona je zauzeta kao i sljedeća pozicija 8 ((5+3)% 10). Slobodna je tek pozicija 9 ((5+4) % 10) i u nju umećemo ključ 25.

0 1 2 3 4 5 6 7 8 9

∅ ∅ ∅ ∅ ∅ 15 35 17 8 25

Umetnimo sada ključ 75. Ponovo se ispituje počevši od pozicije 5 (75 % 10). Ispitivanje pozicija 6, 7, 8 i 9, pokazuje da su one zauzete. Ključ 75 umećemo tek na poziciji 0 ((5+5) % 10), koja je slobodna.

0 1 2 3 4 5 6 7 8 9

75 ∅ ∅ ∅ ∅ 15 35 17 8 25

Page 328: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

328

Analiza efikasnosti pristupa zatvorenoj hash tablici

Prethodni primjer pokazuje problem kod linearnog ispitivanja: kada više ključeva ima istu hash vrijednost tada oni kumulativno povećavaju grupiranje oko primarnih pozicija (eng. clustering). Problem se može umanjiti upotrebom kvadratičnog ispitivanja ili još bolje dvostukim "heširanjem", ali i tada postoje problemi:

o Problem je kvadratičnog ispitivanja da se njime sigurno pronalazi slobodna pozicija samo ako je tablica ispunjena manje od polovine i ako je kapacitet tablice m prosti broj.

o Problem dvostrukog heširanja dolazi do izražaja kada je ključ tipa string. Tada dvostruko računanje hash vrijednosti često zazima više vremena nego višestruko ispitivanje slobodnog mjesta u tablicu. Kada je ključ cjelobrojnog tipa, dvostruko haširanje daje izvrsne rezultate, jer se može provesti jednostavnim funkcijama hash1(k) = k % m i hash2(k) = 1+ k %(m-1) uz uvjet da je m prosti broj.

Uz pretpostavku da hash funkcija uniformno raspodjeljuje ključeve u tablici, može se izvesti da se pri operaciji insert, u tablici s faktorom popunjenosti α, u prosječnom slučaju vrši 1/(1-α) ispitivanja. To slijedi iz činjenice da faktor popunjenosti α = n/m predstavlja vjerojatnost pronalaženja popunjenog mjesta u tablici. Zbog toga, vjerojatnost nalaženja slobodnog mjesta iznosi (1-α), pa zaključujemo da očekivani broj ispitivanja pri traženju slobodnog mjesta iznosi 1/(1-α). Analogno, kod bacanja kocke vjerojatnost pojave broja tri iznosi 1/6, pa se može očekivati da se taj broj pojavi prosječno u 6 bacanja.

Prethodno razmatranje pokazuje da pri popunjenosti tablice 50% prosječno treba izvršiti 2 ispitivanja, a pri popunjenosti 90% treba izvršiti 10 ispitivanja.

Prije opisani problem grupiranja kod linearnog ispitivanja znatno pogoršava ovu idealiziranu procjenu. Knuth je statitičkom analizom izveo da tada prosječno stvarni broj ispitivanja iznosi (1 + 1/(1 - α)2)/2. To znači da pri popunjenosti tablice 50% prosječno treba izvršiti 2,5 ispitivanja, a pri popunjenosti 90% treba izvršiti 50,5 ispitivanja.

Implementacija hash tablice metodom kvadratičnog ispitivanja

Specifikacija za implementaciju tablice je u prije opisanoj datoteci "table.h", a koriste se prethodno definirani algoritmi. Tablica je definirana kao dinamički niz koji sadrži strukture tipa Symbol. Ta struktura pored vrijednosti ključa (key) i vrijednost (val), sadrži podatak o statusu simbola. Ako je status = 0 (EMPTY) simbol tek treba bit definiran. Ako je status = 1 (FULL) simbol je definiran, a ako je status = -1 (DELETED) simbol je izbrisan. Kod kvadratično ispitivanja indeks se računa prema izrazu

indeks(k, i) = (hash(k) +i2 ) % m

Kvadriranje je spora operacije, pa se za efikasni proračun indeksa koristi iterativni izraz: indeks(k, i) = (indeks(k, i-1) + 2*i - 1 ) % m

Lako je pokazati da su dva izraza ekvivalentna; dovoljno je uvrstiti prvi izraz u desnu stranu dugog izraza (za indeks i-1). Kod kvadratičnog ispitivanja se traži da veličina tablice bude duplo veća od broja elemenata, te da veličina tablice bude prosti broj. Zbog toga se pri inicijalizaciji tablice poziva funkcija get_next_prime(M); koja daje prvi prosti broj koji je veći od M. /* Datoteka htable2.c */ /* Zatvorena hash tablica s kvadratičnim ispitivanjem*/

Page 329: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

329

#include <stdlib.h> #include <string.h> #include <assert.h> #include "table.h" typedef struct _symbol { void *key; /* pokazivač ključa simbola */ void *val; /* pokazivač vrijednosti simbola */ short status; /* status: prazno(0), */ }Symbol; /* popunjeno(1) ili izbrisano (-1) */ typedef struct _table { unsigned M; /* veličina tablice */ unsigned N; /* broj elemenata u tablici */ Symbol *htable; /* dinamièki alocirani niz simbola */ CompareFuncT compare; /* funkcije usporedbe kljuèa */ HashFuncT hash; /* hash funkcija */ CopyFuncT copy_key; /* funkcija alociranja kljuca */ CopyFuncT copy_val; /* funkcija alociranja vrrijednosti */ FreeFuncT free_key; /* funkcija dealociranja kljuca */ FreeFuncT free_val; /* funkcija dealociranja vrijednosti */ } Table, *TABLE; #define KEY(i) (T->htable[(i)].key) #define VAL(i) (T->htable[(i)].val) #define EMPTY(i) (T->htable[(i)].status == 0) #define SET_EMPTY(i) (T->htable[(i)].status = 0) #define FULL(i) (T->htable[(i)].status == 1) #define SET_FULL(i) (T->htable[(i)].status = 1) #define DELETED(i) (T->htable[(i)].status == -1) #define SET_DELETED(i) (T->htable[(i)].status = -1) #define NOT_FOUND -1 static int compareInternal(void *a, void *b) { if( (int)a > (int)b ) return 1; else if( (int)a < (int)b ) return -1; else return 0; } static unsigned hashInternal(void *p, int m) {/* daje hash za integer*/ int k = (int)p; return (k > 0)? (k % m) : (-k % m); } static unsigned prime_list[] = { /*niz prostih brojeva*/ 7u, 11u, 13u, 17u, 19u, 23u, 29u, 31u, 53u, 97u, 193u, 389u, 769u, 1543u, 3079u, 6151u, 12289u, 24593u, 49157u, 98317u, 196613u, 393241u, 786433u, 1572869u, 3145739u, 6291469u, 12582917u, 25165843u, 50331653u, 100663319u, 201326611u, 402653189u, 805306457u, 1610612741u, 3221225473u, 4294967291u }; static unsigned get_next_prime(unsigned M)

Page 330: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

330

{ /*vraca sljedeci prosti broj*/ int i, len = sizeof(prime_list)/sizeof(unsigned); for(i=0; i<len; i++) { if(prime_list[i] >= M) return prime_list[i]; } return prime_list[len-1]; } TABLE table_new(unsigned M, CompareFuncT compare, HashFuncT hash) { TABLE T = (TABLE) malloc(sizeof(Table)); if (!T) return NULL; T->M = get_next_prime(M); T->N = 0; T->htable = (Symbol *)calloc(T->M, sizeof(Symbol) ); T->hash = hash ? hash : hashInternal; T->compare = compare? compare : compareInternal; T->free_key = T->free_val = NULL; T->copy_key = T->copy_val = NULL; return T; } void table_set_alloc(TABLE T, CopyFuncT copykey, FreeFuncT freekey, CopyFuncT copyval, FreeFuncT freeval) { T->copy_key = copykey; T->copy_val = copyval; T->free_key = freekey; T->free_val = freeval; } void table_free(TABLE T) { unsigned i; short status; assert(T); for(i = 0; i < T->M; i++) { if(FULL(i)) { if(T->free_key) (*T->free_key)(KEY(i)); if(T->free_val) (*T->free_val)(VAL(i)); } } free(T->htable); free(T); } int find_sym_idx(TABLE T, void *key) { unsigned i = 0; unsigned idx = T->hash(key, T->M); while (1) { if(EMPTY(idx)) return NOT_FOUND; if(FULL(idx) && T->compare(KEY(idx), key) == 0) return idx; /*pronaðen simbol*/ i++; idx = (idx + (2*i-1)) % T->M; }

Page 331: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

331

return NOT_FOUND; } SYMBOL table_find(TABLE T, void *key) { int i; assert(T); i = find_sym_idx (T, key); if(i != NOT_FOUND) return &T->htable[i]; else return NULL; /* nije pronaðen */ } void *table_symbol_value(SYMBOL s) { if(s) return s->val; else return NULL; } void *table_symbol_key(SYMBOL s) { if(s) return s->key; else return NULL; } int table_insert(TABLE T, void *key, char *val) { unsigned idx, i = 0; assert(T); if(T->N >= T->M/2) return 0; /*ovdje primijeni rehash funkciju*/ idx = T->hash(key, T->M); for(;;) /* probaj sljedeći index*/ { if(!FULL(idx)) { KEY(idx) = (T->copy_key)? T->copy_key(key) : key; VAL(idx) = (T->copy_val)? T->copy_val(val) : val; SET_FULL(idx); T->N++; return 1; } else if(T->compare(KEY(idx), key) == 0) { return 0; /* već postoji simbol*/ } i++; idx = (idx + (2*i-1)) % T->M; } return 0; } int table_delete(TABLE T, void *key) { int i; assert(T); i = find_sym_idx (T, key) ; if(i != NOT_FOUND && !(DELETED(i))) { if(T->free_key) T->free_key(KEY(i)); if(T->free_val) T->free_val(VAL(i)); SET_DELETED(i); --T->N;

Page 332: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

332

return 1; } return 0; /* nije pronađen */ } unsigned table_size(TABLE T) { assert(T); return T->N; } Testiranje ovog ADT provodimo s istim programom kojim je testirana prethodna implementacija otvorene hash tablice. Zadatak: Usporedite brzinu izvršena otvorene i zatvorene hash tablice u programu table_timer_test.c. U tom programu su pomoću funkcije clock() mjeri vrijeme izvršenja petlje u kojoj se umeću i brišu cjelobrojni slučajno odabrani ključevi simbola. Petlja se ponavlja 1000000 puta. /*Datoteka: table_timer_test.c*/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <time.h> #include "table.h" int main() { float sec; long start = 0L, end = 0L; int maxnum, maxrand, status, i; int val= 9; int key; TABLE T; /* procesiraj 100000 slučajnih brojeva */ maxnum = 100000; maxrand = 1501; printf("maxnum = %d\n", maxnum); T = table_new(maxrand*2, 0, 0); start = clock (); for (i = maxnum; i>0; i-- ){ key = rand() % maxrand; if ( table_find(T,(void *)key) ) { status = table_delete(T,(void *)key); if (!status) printf("fail: status = %d\n", status); } else { status = table_insert(T, (void *)key,(void *) val); if (!status) printf("fail: status = %d\n", status); } } end = clock (); sec=(float)(end-start)/(float)CLOCKS_PER_SEC; printf("\n %d kljuceva procesirano u %f ms",maxnum, sec*1000);

Page 333: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

333

table_free(T); return 0; } Napomena: Otvorenu hash tablicu inicijalizirajte na veličinu maxrand/2, a zatvorenu tablicu na veličinu maxrand*2. Mijenjajte veličinu maxrand od 127 do 10000. Primjetit ćete da se operacije s otvorenom hash tablicom, brže izvršavaju od operacija s zatvorenom tablicom. Zadatak: U prethodnoj implementaciji zatvorene hash tablice metodom kvadratičnog ispitivanja izvršte sljedeću izmjenu:

Ukoliko broj simbola u tablice n premaši vrijednost m/2, treba udvostručiti vrijednost tablice. Na taj način tablica može primiti proizvoljan broj simbola. Za tu svrhu koristite funkciju table_rehash(T):

TABLE table_rehash(TABLE T ) /* Vraća pokazivač tablice koja ima dvostuko veći kapacitet * od ulazne tablice T, i koja sadrži simbole it T */ { unsigned int i; TABLE Tnew; /* Formiraj uvećanu tablicu i umetni vrijednosti iz T*/ Tnew = table_new( get_next_prime(2*T->M)); for( i=0; i<T->M; i++ ) if(T->htable[i].key != NULL ) table_insert( Tnew, T->htable[i].key, T->htable[i].val ); free(T->htable ); free(T); return Tnew; }

Analizom programerske prakse vidljivo je da se znatno više koristi otvorene tablice. One daju znatno bolju efikasnost kod visoke popunjenosti tablice.

20.3 BST - binarno stablo traženja BST je kratica za naziv binarno stablo traženja (eng. binary search tree). Temeljna karakteristika binarnog stabla traženja je da uvijek sadrži sortirani raspored čvorova. Jedan od elemenata BST čvora predstavlja jedinstveni ključ. Usporedbom vrijednosti ključa određuje se red kojim se umeću ili pretražuju čvorovi stabla, prema sljedećoj rekurzivnoj definiciji. Definicija: Binarno stablo traženja (BST) je:

1. Prazno stablo, ili 2. Stablo koje se sastoji od čvora koji se naziva korijen i dva čvora koji se nazivaju lijevo

i desno dijete, a oni su također binarna stabla (podstabla). Svaki čvor sadrži ključ čija vrijednost je veća od vrijednosti ključa čvora lijevog podstabla, a manja ili jednaka vrijednosti ključa čvora desnog podstabla.

Page 334: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

334

a) b)

Slika 20.2 Sortirano binarno stablo, koje nastaje kad je redosljed unošenja elemenata: a) 6, 9, 3, 8, 5, 2. b) 3, 2, 5, 6, 8, 9. Najljeviji čvor sadrži najmanji ključ (2), a najdesniji čvor sadrži najveći ključ (9). Strelice pokazuju INORDER obilazak stabla.

Slika 20.2a) prikazuje binarno stablo u kojem su elementi uneseni redom: 6, 9, 3, 8, 5, 2,

prema prethodnoj definiciji BST. Strelice pokazuju da se INORDER obilaskom stabla dobije sortirani raspored elemenata(2, 3, 5, 6, 8, 9). Slika 20.2b) prikazuje binarno stablo u kojem su elementi uneseni redom: 3,2,5,6,8,9.

Iako stabla sa slika 20.2a) i 20.3b) sadrže iste elemente, oblik stabala je različite jer oblik ovisi o redoslijedu elemenata. Najveća visina stabla nastaje kada se unose elementi u sortiranom rasporedu, a najmanja kada se elementi unose po slučajnom uzorku. U najboljem slučaju dobije se potpuno stablo. Tada je potreban minimalan broj operacija za prijeći stazu od korijena do lista, bilo za traženje ili unošenje elementa u stablo. Nešto kompleksnija statistička analiza pokazuje da se u najboljem i u prosječnom slučaju vrši O(log2 n) operacija. U najgorem slučaju, kod sortiranog unosa, vrši se O(n) operacija, jer stablo degenerira u "kosu" listu.

Može se zaključiti da BST predstavlja manje efikasno rješenje za izradu tablica ili rječnika od hash tablice koji nudi efikasnost O(1). Ipak, BST nudi neke mogućnosti koje se ne može ostvariti hash tablicom. To se u prvom redu odnosi na formiranje dinamičkih sortiranih skupova u kojima se lako određuje raspored elemenata. Za tu svrhu definira se skup operacija:

ADT BSTREE insert (T, k, v) find (T, k) delete(T, k) minimum(T) maximum(T) predecessor(T, x) succcessor(T, x)

- umeće simbol, kojem je ključ k i vrijednost v u T, ako već nije u T. - vraća simbol ako u T postoji simbol ključa k, ili NULL - briše simbol kojem je ključ k, ako postoji u T. - vraća simbol iz T koji ima minimalnu vrijednost ključa. - vraća simbol iz T koji ima maksimalnu vrijednost ključa. - vraća simbol iz T koji prethodi simbolu x - vraća simbol iz T koji prethodi simbolu x

Ove operacije definiraju ADT BSTREE, kojem je specifikacija operacija u C jeziku zapisana u datoteci "bst.h". #ifndef _RBT_BST_H_ #define _RBT_BST_H_ /* odstrani komentar ako zelis raditi s RED-BLACK stablom*/ /* #define RED_BLACK */ typedef struct _symbol *SYMBOL; typedef struct bs_tree *BSTREE;

Page 335: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

335

typedef int (*CompareFuncT)(void *, void *); typedef void *(*CopyFuncT)(void *); typedef void (*FreeFuncT)(void *); BSTREE bst_new(CompareFuncT compare); /* Stvara ADT BSTREE. * Argument: compare - pokazivač funkcije za usporedbu dva ključa * Ako je NULL, uzima se da stablo sadrži cijele brojeve * Vraća: pokazivač ADT-a */ void bst_set_alloc(BSTREE T, CopyFuncT copykey, FreeFuncT freekey, CopyFuncT copyval, FreeFuncT freeval); /* Postavlja funkcije za alociranje (kopiranje) i dealociranje simbola * Argumenti: T - ADT stablo (mora biti formiran) * copykey - pokazivač funkcije za kopiranje ključa * copyval - pokazivač funkcija za kopiranje vrijednosti * freekey - pokazivač funkcija za dealociranje ključa * freeval - pokazivač funkcija za dealociranje vrijednosti */ void bst_free(BSTREE T); /* Dealocira cijelo stablo T*/ int bst_size(BSTREE T); /* Vraća broj simbola u stablu T*/ int bst_insert(BSTREE T, void * key, void *val); /* Umeće simbol <key,val> u stablo T, ali samo ako ključ key nije u stablu * Argumenti: T - ADT stablo (mora bit formiran) * key - pokazivač ključa (ili cijeli broj) * val - pokazivač vrijednosti (ili cijeli broj) * Vraća: 1 ako je izvršeno umetanje, inače vraća 0 */ int bst_delete(BSTREE T, void * key); /* Briše simbol koji ima ključ key, iz stabla T * Argumenti: T - ADT stablo (mora bit formiran) * key - pokazivač ključa (ili cijeli broj) * Vraća: 1 ako je izvršeno brisanje, inače vraća 0 */ SYMBOL bst_find(BSTREE T, void * key); /* Vraća pokazivač simbola, ako ključ postoji */ void *bst_symbol_value(SYMBOL S); /* Vraća pokazivač vrijednosti simbola S (ili cijeli broj)*/ void *bst_symbol_key(SYMBOL s); /* Vraća pokazivač ključa simbola S (ili cijeli broj)*/ SYMBOL bst_minimum(BSTREE T); /* Vraća pokazivač simbola koji ima minimalni ključa u stablu T*/ SYMBOL bst_maximum(BSTREE T); /* Vraća pokazivač simbola koji ima najveæi ključ u stablu T*/

Page 336: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

336

SYMBOL bst_succesor(SYMBOL S); /* Vraća pokazivač simbola koji slijedi iza simbola S * PRE: S mora biti pokazivač čvora stabla */ SYMBOL bst_predecessor(SYMBOL S); /* Vraća pokazivač simbola koji prethodi simbolu S * PRE: S mora biti pokazivač čvora stabla */ #endif Primijetite da je na početku ove datoteke, unutar komentara definirano:

#define RED_BLACK

Ako se primijeni ova definicija tada će se navedene operacije provoditi po algoritmu za tzv. crveno-crna stabla (eng. red-black trees). Ideja i algoritmi za crveno-crna stabla bit će opisani u sljedećem odjeljku. Ukratko, kod crveno-crnog stabla svakom se čvoru pridjeljuje boja (eng. color). Taj se podatak koristi u algoritmima za uravnoteženje stabla, s ciljem da sve staze stabla, i u najgorem slučaju, budu dostupne sa složenošću O(log2 n). Prije nego se objasni postupak izgradnje crveno-crnog stabla neće se analizirati dio kôda koji je napisan unutar

#ifdef RED_BLACK ........ /* kod za RED-BLACK stabla */ #endif

Implementacija ADT BSTREE je u dana u datoteci "bst.c". Najprije je izvršeno definiranje struktura za implementaciju ADT BSTREE. Prva struktura, struct _simbol, opisuje čvor binarnog stabla. Također je s typedef definiran i sinonim za ovu strukturu imena Node. Definirana su tri pokazivača: na lijevo dijete, na desno dijete i na roditelja. Pokazivač na roditelja je potreban za implementaciju operacija successor() i predecessor(). Ako ove operacije nisu potrebne, tada se može izvršiti implementacija ADT-a i bez pokazivača na roditelja.

Simboli su određeni ključem (key) i vrijednošću (val). U ovoj implementaciji key i val su definirani tipom void *, što znači da će se dinamičkim alociranjem memorije moći registirati simbole bilo kojeg tipa ključa i vrijednosti. Primijetimo da se u strukturi Node opciono bilježi "boja" čvora u članu Color, koji može imati samo dvije pobrojane vrijednosti: RED i BLACK.

Za potpunu generičku implementaciju pri inicijalizaciji ADT-a bit će potrebno registrirati funkcije koje se koriste za dinamičko alociranje i dealociranje memorije. Pokazivači na ove funkcije zajedno s pokazivačem korijena stabla se bilježe u strukturi bs_tree. Tip pokazivača na ovu strukturu - BSTREE služi za označavanje ADT-a.

/* Datoteka: bst.c * Implementacija bst ili red-black stabla (ako je definirano RED_BLACK) */ #include <stdlib.h> #include <string.h> #include <assert.h> #include "bst.h" typedef enum { BLACK, RED } nodeColor; typedef struct _symbol Node;

Page 337: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

337

struct _symbol { struct _symbol *left; /* lijevo dijete */ struct _symbol *right; /* desno dijete */ struct _symbol *parent; /* roditelj */ void * key; /* ključ simbola */ void * val; /* vrijednost simbola */ #ifdef RED_BLACK /* */ nodeColor color; /* boja (BLACK, RED) */ #endif }; struct bs_tree { int N; Node *root; CompareFuncT compare; /* funkcija za usporedbe ključa */ CopyFuncT copy_key; /* funkcija za alociranje ključa */ CopyFuncT copy_val; /* funkcija za alociranje vrijednosti */ FreeFuncT free_key; /* funkcija za dealociranje ključa */ FreeFuncT free_val; /* funkcija za dealociranje vrijednosti */ };

Slika 20. 3. Standardni i prošireni oblik binarnog stabla. Kod standardnog oblika listovi nemaju djece i pokazuju na NULL. Kod proširenog oblika stabla svi su podaci smješteni u unutarnjim čvorovima. Listovi ne sadrže podatke već su to virtualni čvorovi na koje pokazuje jedinstveni NIL pokazivač.

Zbog lakše izvedbe algoritama koristit će se prošireni oblik binarnog stabla, u kojem se svi podaci nalaze u unutarnjim čvorovima, a vanjski čvorovi su tzv. NIL čvorovi (slika 20.2), tj. čvorovi na koje pokazuje NIL pokazivač. Roditelj korijena stabla također je NIL čvor. Vanjski čvorovi ne sadrže podatke, oni služe jedino kao graničnici u nekim algoritmima, pa se za vezu s vanjskim čvorovima koristi jedinstveni pokazivač - NIL - koji pokazuje na jedan globalni čvor kojeg se naziva sentinel. Sentinel pokazuje na samog sebe, jer mu se svi pokazivači (left, right i parent) inicijaliziraju na vrijednost NIL. To je ostvareno definicijama:

#define NIL &sentinel /* svi listovi su NIL sentinel */ #ifdef RED_BLACK static Node sentinel = { NIL, NIL, NIL, 0, 0, BLACK}; #else static Node sentinel = { NIL, NIL, NIL, 0, 0}; #endif

Zbog preglednijeg zapisa programa koristit će se sljedeće definicije:

#define Left(x) (x)->left

Page 338: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

338

#define Right(x) (x)->right #define Parent(x) (x)->parent #define Color(x) (x)->color #define Key(x) (x)->key #define Val(x) (x)->val #define Root(x) (x)->root

Prve operacije koje treba definirati su operacije kojima se inicijalizira ADT i kojima se briše objekt ADT iz memorije. Funkcija bst_new() prima za argument pokazivač na funkciju za usporedbu ključeva. Ako je ovaj argument jednak nuli postavlja se interna funkcija CompareInternal() kojo se vrši usporedba cijelih brojeva. Korijen stabla je početno prazan i njemu se pridjeljuje vrijednost NIL pokazivača.

static int CompareInternal(void *a, void *b) { /*funkcija usporedbe cijelih brojeva*/ if( (int)a > (int)b ) return 1; else if( (int)a < (int)b ) return -1; else return 0; } BSTREE bst_new(CompareFuncT compare) { BSTREE T =(BSTREE)malloc(sizeof(struct bs_tree)); if(T){ Root(T) = NIL; T->N = 0; T->compare = compare? compare : compareInternal; T->free_key = T->free_val = NULL; T->copy_key = T->copy_val = NULL; } return T; } void bst_set_alloc(BSTREE T, CopyFuncT copykey, FreeFuncT freekey, CopyFuncT copyval, FreeFuncT freeval) { /*registriraj funkcije za kopiranje i dealociranje */ T->copy_key = copykey; T->copy_val = copyval; T->free_key = freekey; T->free_val = freeval; }

Dvije interne funkcije newNode() i freeNode() služe za formiranje i dealociranje novog čvora, Funkcija deletaAll() , koristeći "postorder" obilazak stabla, briše sve čvorove. Nju koristi javna funkcija bst_free() kojom se dealocira cijelo stablo.

static Node *newNode(BSTREE T, void * key, void *val, Node *parent) { Node *x = (Node *) malloc (sizeof(Node)); if (x == 0) return 0; Left(x) = NIL; Right(x) = NIL; Key(x) = (T->copy_key)? T->copy_key(key) : key; Val(x) = (T->copy_val)? T->copy_val(val) : val; Parent(x) = parent; #ifdef RED_BLACK

Page 339: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

339

Color(x) = RED; #endif return x; } static void freeNode(BSTREE T, Node *n) { if(n != NIL && n != NULL) { if(T->free_key) T->free_key(n->key); if(T->free_val) T->free_val(n->val); free(n); } } static void deletaAll(BSTREE T, Node *n ) {/* dealocira čvorove */ if( n != NIL && n != NULL) { deletaAll(T, Left(n)); deletaAll(T, Right(n)); freeNode(T, n); } } void bst_free(BSTREE T) {/* dealocira cijelo stablo*/ assert(T); deletaAll(T, Root(T)); free(T); }

Najednostavnija je operacija size() koja vraća broj simbola u stablu:

int bst_size(BSTREE T) { assert (T); return T->N; }

Operacija traženja se jednostavno implementira funkcijom bst_find(). Polazi se od korijena stabla i ispituje da li čvor sadrži ključ koji je veći, manji ili jednak argumentu key. Ako je jednak, traženje je završeno i funkcija vraća pokazivač čvora. Ako je argument key veći od ključa u čvoru, traženje se nastavlja u desnom stablu, inače se traženje nastavlja u lijevom čvoru. Traženje se ponavlja sve do čvor ne pokazuje NIL. Ako se ne pronađe traženi ključ funkcija vraća NULL.

SYMBOL bst_find(BSTREE T, void * key) {/* Vraca pokazivač simbola ako pronađe key, ili NULL */ Node *n; assert(T); n = Root(T); while(n != NIL) { int cmp = T->compare(key, Key(n)); if(cmp == 0) return n; else if(cmp > 0) n = Right(n); else /*(cmp < 0)*/ n = Left(n); }

Page 340: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

340

return NULL; }

Funkcija bst_find() vraća vrijednost tipa SYMBOL. Iako u implementaciji SYMBOL

predstavlja pokazivač čvora, tom se čvoru ne može pristupiti jer je implementacija skrivena od korisnika. Za dobivanje ključa i vrijednosti simbola definirane su funkcije bst_symbol_key() i bst_symbol_value().

void *bst_symbol_value(SYMBOL s) { if(s) return s->val; else return NULL; } void *bst_symbol_key(SYMBOL s) { if(s) return s->key; else return NULL; }

Pronalaženje maksimalne vrijednosti ključa je trivijalno; počevši od korijena stabla ispituje se desni čvor, sve do lišća stabla, jer najdesniji čvor ima maksimalni ključ. Kod traženja najmanjeg elementa ispituje se lijevi čvor jer najljeviji čvor ima minimalni ključ.

SYMBOL bst_maximum(BSTREE T) {/* vraća simbol s najvećim ključem u sortiranom stablu T */ Node *n; assert(T); n = T->root; while (Right(n) != NIL) n = Right(n); return (n == NIL)? NULL : n; } SYMBOL bst_minimum(BSTREE T) { /* vraća simbol s minimalnim ključem u sortiranom stablu T*/ Node *n; assert(T); n = T->root; while (Left(n) != NIL) n = Left(n); return (n == NIL)? NULL : n; }

Slijedi opis funkcija bst_predecessor(x) i bst_successor(x). Sljednik čvora x (successor) je čvor s najmanjim ključem koji je veći od Key(x). Analogno tome je definiran prethodnik čvora x (predecessor) kao čvor s najvećim ključem koji je manji od Key(x).

SYMBOL bst_succesor(SYMBOL x) { /* Vraća sljednika od čvora x * ili NULL ako sljednik ne postoji*/ /* PRE: x mora biti čvor stabla */ Node *y; if(x == NIL || x == NULL) return NULL; if((y = Right(x)) != NIL){ while (Left(y) != NIL) y = Left(y); return y; /* minimum desnog stabla od x*/ } y = Parent(x); while (y != NULL && x == Right(y)) { x = y;

Page 341: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

341

y = Parent(y); } return y; }

Funkcija successor(x) vrši dvije analize. Prvo ako, čvor x ima desno dijete, znači da postoji veća vrijednost u desnoj stazi. Nju se dobije kao minimum(Right(x)). U drugom slučaju, ako je Right(x) == NULL, tada treba unatrag analizirati roditelja čvora i tražiti njegovo lijevo dijete (ukoliko je x desno dijete). Na analogan način se implementira funkcija predecessor(x).

SYMBOL bst_predecessor(SYMBOL x) {/* Vraća prethodnika od čvora x ili NULL ako ne postoji*/ /* PRE: x mora biti čvor stabla */ Node * y; if(x == NIL || x == NULL) return NULL; if ((y = Left(x)) != NIL) { while (Right(y) != NIL) y = Right(y); return y; /* maksimum lijevog stabla od x*/ } else { y = Parent(x); while(y != NULL && x == Left(y)) { x = y; y = Parent(y); } return(y); } }

Umetanje novog simbola (čvora) stabla se vrši prema sljedećem algoritmu:

1) Analiziraj stablo od korijena do lista. Ako je zadani ključ manji od ključa u čvoru, analiziraj čvor lijevo, inače analiziraj čvor desno. Ako je zadani ključ jednak ključu u čvoru, prekini ispitivanje, jer se simbol već postoji. Ako dođeš do lista, zapamti njegovog roditelja i prekini ispitivanje.

2) Alociraj novi čvor s zadanim ključem, vrijednošću i roditeljem. 3) Ako je roditelj jednak NULL novi čvor postaje korijen stabla inače postaje lijevo ili

desno dijete, ovisno o tome da li je ključ bio manji ili veći od ključa u čvoru roditelja. Ovaj algoritam je implementiran u funkciji bst_insert().

int bst_insert(BSTREE T, void * key, void *val) {/*Vraća 1 ako je umetnut simbol <key, val> u stablo T, ili 0*/ Node *parent = 0; Node *x = Root(T); while (x != NIL) { int cmp = T->compare(key, Key(x)); if(cmp == 0) /*ako postoji ključ završi i vrati 0*/ return 0; parent = x; if(cmp > 0) x = Right(x); else /*(<0)*/ x = Left(x); }

Page 342: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

342

/* stvori novi čvor */ x = newNode(T, key, val, parent); if (x == 0) return 0; /* umeti čvor stablo */ if(parent) { int cmp = T->compare(key, Key(parent)); if(cmp < 0) Left(parent) = x; else Right(parent) = x; } else { Root(T) = x; } #ifdef RED_BLACK insertFixup(T,x); #endif T->N++; return 1; }

Operacija brisanja čvora je implementirana u funkciji bst_delete(). Treba uočiti tri slučaja: 1) čvor koji treba izbrisati je list 2) čvor koji treba izbrisati ima samo jedno dijete 3) čvor koji treba izbrisati ima dvoje djece

Ilustrirani su na slici 20.4. Prva dva slučaja su jednostavna: čvor odstranjujemo na način da roditelju čvora pridijelimo pokazivač na dijete čvora (koje može biti NIL), a zatim dealociramo čvor. U trećem slučaju čvor ima dva djeteta. Njega možemo odstraniti, tako da u njega upišemo sadržaj čvora s prvim većim ključem (to je čvor s najmanjim ključem u desnom podstablu), a da zatim odstranimo taj prvi veći čvor. Pošto je prvi veći čvor najmanji čvor u desnom podstablu, on ne može imati lijevo dijete, pa se njega briše jednostavno, kao u prva dva slučaja.

Slika 20.4 Tri slučaja brisanja čvora (podebljeno je prikazan čvor koji se briše)

Page 343: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

343

int bst_delete(BSTREE T, void * key) { Node *x, *y, *z; z = bst_find(T, key); if (!z) return 0; if (Left(z) == NIL || Right(z) == NIL) { /* y ima NIL djecu - mozeš ga premostiti */ y = z; } else { /* ako postoje oba djeteta - nađi prvog većeg */ /* to je minimalni s desna - on ima samo jedno dijete */ y = Right(z); while (Left(y) != NIL) y = Left(y); } /* y ćemo kasnije odstraniti */ /* y ima jedno dijete, spremi to dijete u x */ x = (Left(y) != NIL)? Left(y) : Right(y); /* odstrani y iz lanca roditelja */ /* tako da x postane lijevo ili desno dijete od Parent(y)*/ Parent(x) = Parent(y); if (Parent(y) == NIL) Root(T) = x; else if (y == Left(Parent(y))) Left(Parent(y)) = x; else Right(Parent(y)) = x; if (y != z) { /*zamijeni sadržaj y i z*/ void *tmp = Key(z); Key(z) = Key(y); Key(y) = tmp; tmp = Val(z); Val(z) = Val(y); Val(y) = tmp; } #ifdef RED_BLACK if (Color(y) == BLACK) deleteFixup (T,x); #endif freeNode(T, y); T->N--; return 1; }

Testiranje ADT BSTREE se može provesti programom "bst_test.c". Demonstrira se umetanje, traženje i brisanje simbola s cjelobrojnim ključem, te ispis u sortiranom rasporedu od manjeg prema većem ključu i obrnuto.

/*Datoteka: bst_test.c*/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include "bst.h" int main() { int maxnum, ct, status; int key, val= 9; SYMBOL s; BSTREE T = bst_new(0);

Page 344: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

344

/* procesiraj 100 slučajnih brojeva */ maxnum = 100; printf("maxnum = %d\n", maxnum); for (ct = maxnum; ct; ct-- ) { key = rand() % 19 + 1; if ( bst_find(T,(void *)key) ) { status = bst_delete(T,(void *)key); if (!status) printf("fail: status = %d\n", status); } else { status = bst_insert(T, (void *)key,(void *) val); if (!status) printf("fail: status = %d\n", status); } } printf("\nU stablu ima %d simbola.\n", bst_size(T)); s = bst_minimum(T); while(s) { printf("%d ", (int)bst_symbol_key(s) ); s = bst_succesor(s); } printf("\nReverzni ispis:\n"); s = bst_maximum(T); while(s){ printf("%d ", (int)bst_symbol_key(s) ); s = bst_predecessor(s); } bst_free(T); return 0; }

Nakon izvršenja programa dobije se ispis:

U stablu ima 8 simbola. 1 5 6 7 12 16 17 19 Reverzni ispis: 19 17 16 12 7 6 5 1

20.4 Crveno-crna stabla Crveno-crno stablo (eng. red-black tree) je modificirano binarno stablo traženja kojem

svaki čvor ima jedan dodatni atribut: može biti crven ili crn. Crveno-crno stablo je određeno sljedećim svojstvima:

Definicija: Crveno-crno stablo je binarno stablo sa svojstvima: 1. Svaki čvor je ili crn ili crven. 2. Korijen je crn 3. Svaki list (NIL) je crn. 4. Ako je čvor crven, njegova oba djeteta su crni (to znači da roditelj crvenog čvora ne

smije biti crven). 5. Svaka prosta staza od nekog čvora do listova sadrži jednak broj crnih čvorova.

Page 345: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

345

Posljedica ove definicije je a) da ni jedna staza od korijena do NIL čvorova nije duplo dulja od ostalih staza i b) visina crveno-crnog stabla s n čvorova iznosi najviše 2log2(n+1). Ovo svojstvo se lako dokazuje. Dokaz: Prvo ćemo dokazati da bilo koje podstablo čvora x sadrži barem 2bh(x)-1 unutarnjih čvorova. Funkcija bh(x) daje broj crnih čvorova u bilo kojoj stazi od čvora x (ne uključujući x) do listova. Ako je visina 0, x je list (NIL) - tada ima 2bh(x)-1 = 20-1= 0 unutarnjih čvorova. Razmotrimo sada unutarnji čvor s dva djeteta. Njegova visina je bh(x) ili bh(x)-1 ovisno o tome da li je dijete crveno ili crno. Pošto je to dijete od x ima manju visinu od x, možemo zaključiti da dijete ima barem 2bh(x)-1-1 unutarnjih čvorova, pa podstablo od x ima barem (2bh(x)-1-1)+ (2bh(x)-1-1)+1 = 2bh(x)-1 unutarnjih čvorova, čime je dokazan početni stav.

Ako je visina stabla h, a prema svojstvu 4 bar pola čvorova mora biti crno, tada bh visina korijena mora biti barem h/2, pa broj čvorova ispod korijena iznosi n ≥ 2h/2-1. Logaritmiraući ovaj izraz dobijemo da je h ≤ 2 log2(n+1).

Ovaj dokaz pokazuje da operacije traženja u crveno-crnom stablu uvijek ima složenost

O(logn). Postavlja se pitanje, kako izvršiti operacije umetanja i brisanja, uz uvjet da se uvijek zadrži crveno-crno svojstvo stabla. Ideja je jednostavna, nakon umetanja čvora (i nakon brisanja čvora) treba provjeriti raspored crnih i crvenih čvorova. Ako raspored ne zadovoljava prethodnu definiciju svojstava crveno-crnog stabla, treba izvršiti transformaciju stabla.

Transformacije stabla se vrše pomoću dva temeljna postupka - lijeve i desne rotacije čvorova. Ti postupci su ilustrirani na slici 20.5. Primjetite da nakon obje rotacije raspored čvorova ostaje nepromijenjen: Ključ(A) < Ključ(B) < Ključ (C), dakle rotacije mijenjaju oblik stabla, ali ne mijenjaju "inorder" raspored čvorova.

Slika 20.5 Desna i lijeva rotacija čvorova

Rotacije čvorova sa slike 20.5 vrše se pomoću funkcija rotateLeft() i rotateRight().

#ifdef RED_BLACK static void rotateLeft(BSTREE T, Node *x) { Node *y; /* rotira čvor x u lijevo */ assert (x); assert(Right(x)); y = Right(x); /* veza s Right(x) */ Right(x) = Left(y); if (Left(y) != NIL) Parent(Left(y)) = x; /* poveži s Parent(y) */ if (y != NIL) Parent(y) = Parent(x); if (Parent(x)) { if (x == Left(Parent(x))) Left(Parent(x)) = y; else

Page 346: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

346

Right(Parent(x)) = y; } else { Root(T) = y; } /* poveži x i y */ Left(y) = x; Parent(x) = y; } static void rotateRight(BSTREE T, Node *x) { Node *y; /* rotira čvor x u desno */ assert (x); y = Left(x); /* poveži s Left(x) */ Left(x) = Right(y); if (Right(y) != NIL) Parent(Right(y))= x; /* poveži s Parent(y) */ if (y != NIL) Parent(y) = Parent(x); if (Parent(x)) { if (x == Right(Parent(x))) Right(Parent(x)) = y; else Left(Parent(x)) = y; } else { Root(T) = y; } /* poveži x i y */ Right(y) = x; Parent(x) = y; } #endif

Ove funkcije će biti korištene u funkciji insertFixup(), kojom se ispravlja ravnoteža stabla nakon umetanja čvora, i u funkciji deleteFixup(), kojom se ispravlja ravnoteža stabla nakon brisanja čvora. Funkcija insertFixup() se poziva na završetku funkcije bst_insert(), a funkcija deleteFixup() se poziva na završetku funkcije bst_delete(). Najprije će biti opisan algoritam koji se koristi u funkciji insertFixup().

Uravnoteženje stabla nakon umetanja čvora

Prilikom umetanja čvora, u funkciji bst_insert(), uvijek se umeće crveni čvor. Ako je i roditelj umetnutog čvora crven, nastaje neuravnoteženo stablo jer više nije zadovoljeno svojstvo 4 iz definicije crveno-crnog stabla. Ta se neuravnoteženost ispravlja pozivom funkcije insertFixup().

Slike 20.6 i 20.7 omogućuju objašnjenje algoritma iz ove funkcije. Ukoliko umetnuti čvor nije korijen stabla, funkcija se izvršava unutar petlje sve dok je roditelj umetnutog čvora crven. Analizira se i ispravlja uravnoteženost stabla. Prvo se analiziraju ujaci umetnutog čvora: (Left(Parent(Parent(x))) i Right(Parent(Parent(x))). Ako su oni crveni, dovoljno je promijeniti boju čvorova, kao na slici 20.6, a ako su crni tada se vrši jednostruka ili dvostruka rotacija, uz promjenu boje čvorova, prema slici 20.7. U analizi nas ne zanimaju djeca umetnutog čvora, jer ako je roditelj umetnutog čvora crven, tada je, prema svojstvu 4, sigurno da su mu djeca crna.

Page 347: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

347

Slika 20.6 Prvi slučaj analize umetanja čvora x. Crni čvorovi su prikazani tamnije. Nije zadovoljeno svojstvo 4 jer su crveno obojani i x i njegov roditelj. Ujak od x, odnosno y=Right(Parent(Parent(x))) je također crven. Svako podstablo A,B,C,D i E ima crni korijen i jednaku crnu-visinu. Nisu nužne rotacije već samo izmjena boje čvorova. Nakon prikazane promjene boje čvorova while petlja nastavlja s čvorom "Novi x" i ispituje mogući problem, tj. da li je njegov roditelj crven.

#ifdef RED_BLACK static void insertFixup(BSTREE T, Node *x) { /* stvara Red-Black ravnotežu nakon umetanja čvora x */ /* podrazumjeva se da je Color(x) = RED; */ while (x != Root(T) && Color(Parent(x)) == RED) { if (Parent(x) == Left(Parent(Parent(x)))) { Node *y = Right(Parent(Parent(x))); if (Color(y) == RED) {/* ujak je RED - slučaj 1 */ Color(Parent(x)) = BLACK; Color(y) = BLACK; Color(Parent(Parent(x))) = RED; x = Parent(Parent(x)); } else { /* ujak je BLACK - slučaj 2*/ if (x == Right(Parent(x))) { /* postavi x za lijevo dijete */ x = Parent(x); rotateLeft(T, x); } /* oboji i rotiraj - slučaj 3 */ Color(Parent(x)) = BLACK; Color(Parent(Parent(x)))= RED; rotateRight(T, Parent(Parent(x))); } } else {/* slika u zrcalu prethodnog slučaja*/ Node *y = Left(Parent(Parent(x))); if (y->color == RED) {/* ujak je RED - slučaj 1 */ Color(Parent(x)) = BLACK;

Page 348: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

348

y->color = BLACK; Color(Parent(Parent(x))) = RED; x = Parent(Parent(x)); } else { /* ujak je BLACK - slučaj 2*/ if (x == Left(Parent(x))) { x = Parent(x); rotateRight(T, x); } /* oboji i rotiraj - slučaj 3 */ Color(Parent(x)) = BLACK; Color(Parent(Parent(x))) = RED; rotateLeft(T, Parent(Parent(x))); } } } Color(Root(T))= BLACK; } #endif

u

A B

C

rotateRight(z)v

z

D

u

A B C

v

z

D

A

B C

u

z

D

A

B C

z

v

D

A

B

C

u

D

u

z

v

v

rotateLeft(u)

rotateRight(z)

Slučaj 2

Slučaj 2

Slučaj 3 Slučaj 3

x

x

x

x

rotateLeft(u)

y

y

y

y

x

Slika 20.7 Drugi i treći slučaj analize umetanja čvora x. Sada su nužne jednostruke i dvostruke rotacije. Svako podstablo A,B,C,D i E ima crni korijen i jednaku crnu-visinu. Slovom x je označen čvor koji se analizira unutar while petlje (to je umetnuti čvor), a y označava njegovog lijevog ili desnog ujaka. Uočite da se slučaj 2 i 3 razlikuju od slučaja 1 po boji ujaka. U slučaju 2 vrši se rotacija Parent(x) i dobije se slučaj 3. Zatim se vrši rotacija Parent(Parent(x) u konačni oblik.

Vremensku složenosti umetanja čvora procjenjujemo na sljedeći način:

Page 349: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

349

1. Funkcija bst_insert() se izvršava u O(lg n) vremena jer se mjesto za umetanje čvora traži u uravnoteženom stablu.

2. U funkciji insertFixup() svaka iteracija uzima O(1) vremena, i pri svakoj iteraciji se pomičemo dvije razine naviše. Pošto ima O(log2n) razina, vrijeme izvršenja insertFixup() je također O(log2n)

Ukupno, umetanje u crveno-crno stablo ima vremensku složenost O(log2n).

Uravnoteženje stabla nakon brisanja čvora

Nešto kompliciraniji zahtjevi za uravnoteženjem stabla se javljaju kada se u funkciji bst_delete() izbriše čvor y. Problem nastaje ako se izbriše crni čvor jer tada može nastati slučaj da su čvor i njegovo dijete crveni. To je u suprotnosti sa svojstvom 3 crveno-crnog stabla. Zbog toga se na kraju funkcije bst_delete(T, y) poziva funkcija deleteFixup(T,x) koja vrši uravnoteženje stabla. Čvor x je jedino dijete izbrisanog čvora y (možda jednak NIL). Unutar ove funkcije se prvo analizira boja od x, sa sljedećim posljedicama:

1. Ako je boja od x crvena, ili ako je x korijen stabla, tada se ne izvršava petlja, već se jedino boja od x promijeni u crnu boju. U tom slučaju nije potrebno analizirati uravnoteženost stabla, jer ako smo izbacili crni čvor (y), sada sada povratili crni čvor (x) Time smo osigurali da je crna-visina nepromijenjena. Pošto smo izbacili i crveni čvor (x) osigurali smo se da neće biti dva crvena čvora u odnosu dijete-roditelj.

2. Ako je boja od x crna, i ako x nije korijen stabla izvršava se petlja u kojoj se analizira uravnoteženost stabla. Ideja je da tražimo uzlaznom stazom,od čvora x, crveni čvor koji bi mogli pretvoriti u crni čvor. Na taj način bi se održala ravnoteža crnih čvorova (jer je prije izbrisan crni čvor y).

Slika 20. 8 pokazuje četiri slučaja kod kojih treba izvršiti promjenu boje i/ili izvršiti potrebne rotacije u stablu, pri traženju tog crvenog čvora. Analiziraju se brat od x, to je čvor w, i roditelj od čvora x. Moguće je 8 slučajeva, 4 kad je brat desno od roditelja i 4 kad je brat lijevo od roditelja. Na slici 20.8 prikazana su 4 slučaja kada je brat desno dijete roditelja x. Slučajeve razlikujemo po boji djece od w. Čvor w je zgodan za analizu jer ne može biti NIL, inače bi imali odstupanje od svojstva 5 za čvor Parent(x) koji je i roditelj od w. (Ako je x crne boje, on mora imati brata, jer staza od Parent(x) do lista, na lijevo i na desno mora imati bar jedan crni čvor koji nije NIL).

Page 350: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

350

x

d

E F

e

A

b

a c

B C D

s

S'

A B

C

d

c e

a

b

D E F

w

d

E F

e

A

b

a c

B C D

xNovi w

Slučaj 1

A B

C

d

c e

a

b

D E F

x

s

w

A B

C

d

c e

a

b

D E F

Novi x s

Slučaj 2

A B

C

d

c e

a

b

D E F

x w

s

A B

c

d

a

b

D

C

E

e

F

s

xNovi w

Slučaj 3

A B

C

d

c e

a

b

D E F

s

wx

S'

Novi x = Root(T)

Slučaj 4

Slika 20.8 Slučajevi koji se obrađuju u while-petlji funkcije deleteFixup().Crni čvorovi su prikazani tamnije, crveni čvorovi s zadebljanim rubom,a ostali čvorovi, koji mogu biti ili crni ili crveni prikazani su s tanjim rubom i označeni slovima s i s'. Slova A,B,C,.. označavaju podstabla. Petlja se ponavlja jedino u drugom slučaju

#ifdef RED_BLACK static void deleteFixup(BSTREE T, Node *x) { /* stvara Red-Black ravnotežu nakon brisanja čvora*/ while (x != Root(T) && Color(x) == BLACK) { if (x == Left(Parent(x))) { Node *w = Right(Parent(x)); if (Color(w) == RED) { /*slučaj 1*/ Color(w) = BLACK; Color(Parent(x)) = RED; rotateLeft(T, Parent(x)); w = Right(Parent(x)); } if (Color(Left(w)) == BLACK && Color(Right(w)) == BLACK) { Color(w) = RED; /* slučaj 2*/

Page 351: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

351

x = Parent(x); } else { if (Color(Right(w)) == BLACK) { Color(Left(w)) = BLACK; /*slučaj 3*/ Color(w) = RED; rotateRight(T, w); w = Right(Parent(x)); } Color(w) = Color(Parent(x)); /*slučaj 4*/ Color(Parent(x)) = BLACK; Color(Right(w)) = BLACK; rotateLeft(T, Parent(x)); x = Root(T); } } else {/*slika u zrcalu (zamijeni lijevi <-> desni)*/ Node *w = Left(Parent(x)); if (Color(w) == RED) { Color(w) = BLACK; Color(Parent(x)) = RED; rotateRight(T, Parent(x)); w = Left(Parent(x)); } if (Color(Right(w)) == BLACK && Color(Left(w)) == BLACK) { Color(w) = RED; x = Parent(x); } else { if (Color(Left(w)) == BLACK) { Color(Right(w)) = BLACK; Color(w) = RED; rotateLeft(T, w); w = Left(Parent(x)); } Color(w) = Color(Parent(x)); Color(Parent(x)) = BLACK; Color(Left(w)) = BLACK; rotateRight(T, Parent(x)); x = Root(T); } } } Color(x) = BLACK; } #endif

Slučaj 1: w je crven

o w mora imati crnu djecu (prema svojstvu 5) o Postavi boju w crno i Parent(x) crveno (to kasnije može značiti kraj ispitivanja) o Lijevo rotiraj Parent(x)

Page 352: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

352

o Novi brat od x je bio dijete od w prije rotacije, pa mora bit crn o Nastavi razmatrati stanje prelaskom na slučajeve 2,3 ili4, u kojima će se, ovisno o

položaju Novog w odrediti da li se Parent(x), koji je crven, pretvara u crni čvor.

Slučaj 2: w je crn i oba djeteta od w su crna

[Napomena: Čvor nepoznate boje označen tankim rubom i slovom s]

o Promijeni boju od w u crveno o Pomakni promatranje na viši čvor tako da Parent(x) postane Novi x o Ponovi iteraciju. Iteracija prestaje ako je Novi x = Parent(x) crven (to je uvijek slučaj

ako smo došli iz slučaja 1) Tada se Novi x konačno oboji u crno. Kraj! Primjetite da je zadržan broj crnih čvorova u stazi od x i u stazi od w. U obje staze najprije je smanjen broj crnih čvorova, a zatim je na kraju dodan crni čvor.

Slučaj 3: w je crn, lijevo dijete od w je crveno, a desno dijete od w je crno

o Pomijeni boju od w u crveno i boju lijevog djeteta od w u crno o Desno rotiraj w o Novi w, tj. novi brat od x je crn s crvenim desnim djetetom o Proslijedi analizu na slučaj 4

Slučaj 4: w je crn, lijevo dijete od w je crno, a desno dijete od w je crveno

[Napomena: Dva čvora nepoznate boje označeni su slovima s i s'.]

o Postavi boju w jednaku boji Parent(x) o Postavi boju Parent(x) crno i boju desnog djeteta od w crno o Zatim lijevo rotiraj Parent(x) o Pošto je jedan crveni čvor pretvoren u crni (Right(w)), cilj je ispunjen. Završi petlju na

način da postaviš da je x jednak Root(T). Kraj! Vremensku složenost ovih operacija određuje složenost tri procesa:

Page 353: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

353

1. Složenost bst_delete() je O(log2n), jer se traženje vrši u zravnoteženom stablu. 2. Složenost deleteFixup() je određena slučajem 2 jer se jedino u njemu može javiti

potreba za više iteracije. U svakoj iteraciji se analiza podiže jednu razina više, pa je maksimalno moguće O(log2n) iteracija

3. Slučajevi 1, 3 i 4 imaju samo 1 rotaciju, pa ukupno ne može biti više od 3 rotacije. Ukupna vremenska složenost je O(log2n) i to s malim konstantnim faktorom. Upravo brzina operacije brisanja daje crveno-crnom stablu malu prednost nad drugim metodama uravnoteženja stabla (primjerice, AVL metoda, u najgoram slučaju, kod brisanja vrši O(log2n) rotacija). Zadatak: Napišite specijaliziranu verziju ADT binarnog stabla koja se od ADT BSTREE razlikuje po tome da simboli nisu određeni parom <ključ, vrijednost>, već samo sadrže ključ. Rezultirajući ADT nazovite imenom MULTISET, čime se aludira na skup podataka koji može sadržavati više istih objekata. Definirajte sve operacije koje postoje kod BST, ali promijenite imena operacija tako da sva imena počinju prefiksom mset_. Testirajte ADT MULTISET na problemu sortiranja niza pomoću stabla traženja. Koristite algoritam za sortiranje stablom koji se obično naziva Treesort, a sastoji se od sljedećih koraka. Problem: Sotiraj niz A koji sadrži od n objekta tipa T, tako da vrijedi A[i] < A[i+1] Algoritam:

Inicijaliziraj ADT MULTISET imena Skup, za tip podataka T Ponavljaj za sve elemente niza A[i], i=0,1, n-1

mset_insert(Skup, A[i]) SYMBOL s ← mset_minimum(Skup); Ponavljaj za i=0,1, n-1

A[i] ← mset_symbol_key(s) s ← mset_sucessor(s)

Kraj! Primjetite da umetanje jednog podatka u stablo traje O(log2n). Pošto ukupno postoji n podataka, unos u stablo traje O(n log2n). Ispis iz stabla traje O(n). Zaključujemo da sortiranje stablom (treesort) ima vremensku složenost O(n log2n) + O(n) = O(n log2n). Teorijski ovo je isti rezultat kao kod Mergesort ili Heapsort metode, ili kao prosječni slučaj složenosti kod Quicksort metode. U praksi, zbog manjeg konstantnog faktora, preferira se Quicksort metoda.

Page 354: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

354

Literatura

[1] American National Standards Institute: American national standard for information systems—Programming language - C, ANSI X3.159-1989, 1989. [2] International Standard ISO/IEC 9899: Programming Language C, 1999. [3] B. W. Kernighan and D. M. Ritchie: The C programming language (2nd ed.), Englewood Cliffs, NJ: Prentice -Hall, 1988. [4] D. M. Ritchie: The development of the C language. Second ACM HOPL Conference, Cambridge, MA. 1993. [5] R. Sedgewick: Algorithms in C, Addison-Wesley, 1998. [6] M. A. Weiss: Data Structures and Algorithm Analysis in C, Addison-Wesley, 1997. [7] T. H. Cormen, C. E. Leiserson and R. L. Rivest: Introduction to Algorithms, MIT Press, 1990. [8] D. E. Knuth: Sorting and Searching, volume 3 of The Art of Computer Programming, Addison-Wesley, 1973. [9] A. V. Aho, J. E. Hopcroft and J. D. Ullman: Data Structures and Algorithms, Addison-Wesley, 1983. [10] A. V. Aho, J. E. Hopcroft and J. D. Ullman: Compilers, Addison-Wesley, 1985. [11] E. Horowitz and S. Sahni: Fundamentals of Computer Algorithms, Computer Science Press, 1978. [12] N. Wirth: Algorithms and Data Structures, Prentice Hall, 1986. [13] A. M. Berman: Data Structures via C++, Oxford Press, 1997. [13] W. J. Collins: Data Structures and Standard Template Library, McGraw-Hill, 2003.

Page 355: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

355

Dodatak

Dodatak A - Elementi dijagrama toka

Page 356: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

356

Dodatak B - Gramatika C jezika Sintaktička i leksička pravila C-jezika su zapisana na sljedeći način:

Neterminalni simboli su zapisani kurzivom. Terminalni simboli su zapisani na isti način kao u ciljnom jeziku Opcioni simboli su označeni indeksom opt (Simbolopt ili Simbolopt). Pravila imaju oblik:

neterminalni-simbol : niz-terminalnih-i-neterminalnih-simbola

Alternativna pravila su zapisana u odvojenim redovima. Iznimno, kada su zapisana u jednom retku, odvojena su okomitom crtom

Sintaksa deklaracija i definicija C jezika kompilacijaska-jedinica: definicija-funkcije deklaracija kompilacijaska-jedinica deklaracija kompilacijaska-jedinica definicija-funkcije definicija-funkcije: deklaracija-specifikatoraopt deklarator lista-deklaracijaopt složena-naredba lista-deklaracija: deklaracija lista-deklaracija deklaracija deklaracija: deklaracija-specifikatora lista-init-deklaratoraopt; deklaracija-specifikatora: specifikacija-klase-spremanja deklaracija-specifikatoraopt specifikacija-tipa deklaracija-specifikatoraopt specifikacija-kvalifikacije deklaracija-specifikatoraopt specifikacija-klase-spremanja : auto | register | static | extern | typedef specifikacija-tipa: void | char | short | int | long float | double | signed | unsigned struct-ili-union-specifikator enum-specifikator typedef-ime specifikacija-kvalifikacije: const | volatile struct-ili-union-specifikator: struct-ili-union identifikatoropt { struct-lista-deklaracija } struct-ili-union identifikator struct-ili-union: struct | union struct-lista-deklaracija: struct-deklaracija struct-lista-deklaracija struct-deklaracija lista-init-deklaratora: init-deklarator lista-init-deklaratora, init-deklarator init-deklarator: deklarator deklarator = inicijalizacija struct-deklaracija: lista-specifikatora lista-struct-deklaratora; lista-specifikatora: specifikacija-tipa lista-specifikatoraopt

Page 357: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

357

specifikacija-kvalifikacije lista-specifikatoraopt lista-struct-deklaratora: struct-deklarator lista-struct-deklaratora , struct-deklarator struct-deklarator: deklarator deklaratoropt : konstanti-izraz enum-specifikator: enum identifikatoropt { enumerator-lista } enum identifikator enumerator-lista: enumerator enumerator-lista , enumerator enumerator: identifikator identifikator = konstanti-izraz deklarator: pokazivačopt direktni-deklarator direktni-deklarator: identifikator (deklarator) direktni-deklarator [ konstanti-izrazopt ] direktni-deklarator ( parametari-funkcije ) direktni-deklarator ( lista-identifikatoraopt ) pokazivač: * lista-specifikacija-kvalifikacijeopt * lista-specifikacija-kvalifikacijeopt pokazivač lista-specifikacija-kvalifikacije: specifikacija-kvalifikacije lista-specifikacija-kvalifikacije specifikacija-kvalifikacije parametari-funkcije: lista-parametara lista-parametara , ... lista-parametara: deklaracija-parametra lista-parametara , deklaracija-parametra deklaracija-parametra: deklaracija-specifikatora deklarator deklaracija-specifikatora apstraktni-deklaratoropt lista-identifikatora: identifikator lista-identifikatora , identifikator inicijalizacija: izraz-pridjele { lista-inicijalizacije } { lista-inicijalizacije , } lista-inicijalizacije: inicijalizacija lista-inicijalizacije , inicijalizacija ime-tipa: lista-specifikatora apstraktni-deklaratoropt apstraktni-deklarator: pokazivač pokazivačopt direktni-apstraktni-deklarator direktni-apstraktni-deklarator: ( apstraktni-deklarator ) direktni-apstraktni-deklaratoropt [konstanti-izrazopt ] direktni-apstraktni-deklaratoropt ( parametari-funkcijeopt ) typedef-ime: identifikator

Page 358: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

358

Sintaksa naredbi naredba: naredbeni izraz složena-naredba naredba-selekcije naredba-iteracije naredba-skoka označena-naredba naredbeni izraz : izrazopt ; složena-naredba : { lista-deklaracijaopt niz-naredbiopt } lista-deklaracija : deklaracija lista-deklaracija deklaracija niz-naredbi: naredba niz-naredbi naredba

naredba-iteracije: while ( izraz ) naredba do naredba while ( izraz ) ; for ( izrazopt ; izrazopt ; izrazopt ) naredba naredba-selekcije: if ( izraz ) naredba if ( izraz ) naredba else naredba switch ( izraz ) naredba naredba-skoka : goto identifikator ; continue; break; return izrazopt ; označena-naredba: identifikator: naredba case konstanti-izraz : naredba default : naredba

Sintaksa izraza primarni-izraz:

identifikator konstanta literalni-string (izraz )

postfiks-izraz: primarni-izraz postfiks-izraz [ izraz ] postfiks-izraz ( lista-argumenata-opt ) postfiks-izraz . identifikator postfiks-izraz -> identifikator postfiks-izraz ++ postfiks-izraz – ( ime-tipa ) { lista-inicijalizacije } ( ime-tipa ) { lista-inicijalizacije , } lista-argumenata: izraz-pridjele lista-argumenata , izraz-pridjele unarni-izraz:

postfiks-izraz ++ unarni-izraz -- unarni-izraz unarni-operator cast-izraz sizeof unarni-izraz sizeof ( ime-tipa )

unarni-operator: & | * | + | - | ~ | !

cast-izraz: unarni-izraz ( ime-tipa ) cast-izraz

multiplikativni-izrazr: cast-izraz multiplikativni-izrazr * cast-izraz multiplikativni-izrazr / cast-izraz multiplikativni-izrazr % cast-izraz aditivni-izraz:

multiplikativni-izraz aditivni-izraz + multiplikativni-izraz aditivni-izraz - multiplikativni-izraz

posmačni-izraz: aditivni-izraz posmačni-izraz << aditivni-izraz posmačni-izraz >> aditivni-izraz

relacijski-izraz: posmačni-izraz relacijski-izraz < posmačni-izraz relacijski-izraz > posmačni-izraz relacijski-izraz <= posmačni-izraz relacijski-izraz >= posmačni-izraz

izraz-jednakosti: relacijski-izraz izraz-jednakosti == relacijski-izraz izraz-jednakosti != relacijski-izraz

AND-izraz: izraz-jednakosti AND-izraz & izraz-jednakosti

XOR-izraz: AND-izraz XOR-izraz ^ AND-izraz

OR-izraz: XOR-izraz OR-izraz | XOR-izraz

logički-AND-izraz: OR-izraz logički-AND-izraz && OR-izraz

logički-OR-izraz: logički-AND-izraz logički-OR-izraz || logički-AND-izraz

uvjetni-izraz: logički-OR-izraz logički-OR-izraz ? expr : uvjetni-izraz

izraz-pridjele: uvjetni-izraz unarni-izraz operator-pridjele izraz-pridjele

operator-pridjele: = | *= | /= | %= | += | -= | <<= | >>= | &= | ^= | |=

izraz: izraz-pridjele izraz , izraz-pridjele

konstantni-izraz: uvjetni-izraz

Page 359: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

359

Regularna gramatika za zapis literalnih konstanti konstanta: integer-konstanta float-konstanta char-konstanta enum-konstanta literalni-string integer-konstanta: 0 decimalna-konstanta int-sufiksopt oktalna-konstanta int-sufiksopt hexadecimalna-konstanta int-sufiksopt decimalna-konstanta: nenulta-znamenka decimalna-konstanta znamenka oktalna-konstanta: 0oktalna_znamenka oktalna-konstanta oktalna-namenka heksa-konstanta: 0xheksa-znamenka 0Xheksa-znamenka heksa-konstanta heksa-znamenka int-sufiks: unsigned-sufiks long-sufiksopt long-sufiks unsigned-sufiksopt unsigned-sufiks: u | U long-sufiks: l | L float-konstanta: floating-konstanta float-sufiksopt float-sufiks: L | l | F | f floating-konstanta: frakciona-konstanta exponent frakciona-konstanta niz-znamenki eksponent frakciona-konstanta: niz-znamenki.niz-znamenki .niz-znamenki niz-znamenki. eksponent: e predznak niz-znamenki E predznak niz-znamenki e niz-znamenki E niz-znamenki predznak: +|-

nenulta-znamenka: 1|2|3|4|5|6|7|8|9 znamenka : 0|nenulta-znamenka heksa-znamenka: znamenka |A|B|C|D|E|F|a|b|c|d|e|f oktalna-znamenka : 0|1|2|3|4|5|6|7 niz-znamenki: znamenka niz-znamenki znamenka char-konstanta: 'char' L 'char' char: reg-char escape-sequence reg-char: bilo koji ASCII znak osim apostrofa ('), backslash(\) i znaka za novu liniju. escape-sekvenca: \' | \" | \\ | \d | \dd | \ddd | \xd | \xdd | \xddd | \a | \b | \f | \n | \r | \t | \v (d je znamenka) enum-konstanta: identifikator literalni_string: "" "char-sekvenca" L"char-sekvenca" char-sekvenca: char char-sekvenca char Napomena: Znak navodnika(") se ne može direktno unositi u string, već kao escape-sekvenca \". Prefiks L označava prošireni znakove (wchar_t)

Page 360: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

360

Klučne riječi C - jezika

auto double int struct break else long switch case enum register typedef char extern return union const float short unsigned continue for signed void default goto sizeof volatile do if static while

Tip operatora Operator Asocijativnost

primarni [] . -> () s lijeva na desno postfiks –unarni ++ -- s lijeva na desno prefiks – unarni ++ -- & * + - ~ ! sizeof cast s desna na lijevo multiplikativni * / % s lijeva na desno aditivni + - s lijeva na desno posmačni << >> s lijeva na desno relacijski < > <= >= s lijeva na desno jednakost == != s lijeva na desno bitznačajni "i" & s lijeva na desno ekskluzivni "ili" ^ s lijeva na desno bitznačajni "ili" | s lijeva na desno logički "i" && s lijeva na desno logički "ili" || s lijeva na desno ternarni uvjetni op. ?: s desna na lijevo pridjela vrijednosti = *= /= %= += -= <<= >>= &= ^= |= s desna na lijevo zarez , s lijeva na desno

Prioritet i asocijativnost operatora

Page 361: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

361

Dodatak C - Standardna biblioteka C jezika Standardna biblioteka C jezika definirana je dokumentom: American national standard for information systems - Programming language C, ANSI X3.159-1989, American National Standards Institute. 1989. Sadrži niz korisnih funkcija, konstanti (makro naredbi), typedef tipova i globalnih varijabli (pr. stdin, stdout). Deklaracija tih objekata zapisana je u datotekama koje se nazivaju zaglavlja ili h-datoteke (eng. headers), a koriste se kao početne #include datoteke u gotovo svakom izvornom programu.

Funkcionalna podjela h-datoteke (zaglavlja)

temeljne definicije <stddef.h> rad s ulazno izlaznim uređajima i datotekama <stdio.h> radnje s znakovima i stringovima <string.h>, <ctype.h>, <stdlib.h>, <stdio.h> alociranje memorije <stdlib.h> matematičke funkcije <math.h> datum i vrijeme <time.h> promjenljivi broj argumenata <stdarg.h> globalni skokovi <setjmp.h> asertacija <assert.h> prihvat signala stanja računalnog procesa <signal.h> prihvat i dojava pogreške <errno.h>, <stdio.h>, <string.h> minimalne i maksimalne vrijednosti tipova <limits.h>, <float.h> lokalizacija znakovnih zapisa <locale.h>, <stdlib.h>, <wchar.h>, <wctype.h> sučelje s operativnim sustavom <stdlib.h> Slijedi opis funkcija definiranih standardom. Za svaku funkciju navodi se deklaracija, opis argumenata funkcije i tip vrijednosti koju funkcija vraća.

C 1 <stdio.h> U zaglavlju <stdio.h>, definiran je niz funkcija za rad s ulazno/izlaznim tokovima. Svakom toku je pridijeljen pokazivač na strukturu FILE koja je također definirana u <stdio.h>. Standardni ulaz, standardni izlaz i standardni tok dojave greške se automatski inicijaliziraju pri pokretanju programa, a njihov pokazivač na strukturu FILE je u globalnim varijablama:

FILE *stdin; /* pokazivač toka standardnog ulaza */ FILE *stdout; /* pokazivač toka standardnog izlaza */ FILE *stderr; /* pokazivač toka dojave greške */

Iniciranje pokazivača datotečnih tokova vrši se pomoću funkcije fopen(). Kada se završi rad s datotekom treba zatvoriti njen tok pomoću funkcije fclose(). U zaglavlju <stdio.h> definirana je i simbolička konstanta EOF koja služi kao oznaka kraja datoteke.

Otvaranje i zatvaranje toka

fopen

FILE *fopen(const char *name, const char *mode);

Page 362: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

362

fopen() otvara tok za datoteku imena name. Ako je operacija uspješna, funkcija vraća pokazivač toka, a ako je neuspješna tada vraća NULL. String mode označava način na koji će biti otvorena datoteka. Prvi znak tog stringa mora biti 'r', 'w', ili 'a', što označava da se datoteka otvara za čitanje, pisanje ili dopunjavanje. Ako se nakon prvog znaka napiše znak '+' tada se datoteka otvara za čitanje i pisanje. Dodatno se može napisati i znak 'b'. To je oznaka da se na MS-DOS sustavima datoteka tretira kao binarna datoteka. U suprotnom datoteka se otvara u tekstualnom modu. MS-DOS /Windows sustavi znak za kraj retka '\n' u tekstualnoj datoteci pretvaraju u dva znaka '\r\n', također se znak Ctrl-Z (ASCII vrijednost 26) tretira kao oznaka kraja datoteke. Ove pretvorbe ne postoje na Unix sustavima. freopen

FILE *freopen(const char *name, const char *mode, FILE *fp);

freopen() služi kao i fopen(), ali na način da se datoteka imena name poveže s postojećim pokazivačem toka fp. Tipično se koristi za redirekciju sa standardnih tokova, primjerice, naredbom

freopen("output.log", "w", stdout);

budući izlaz, koji generiraju funkcije koje djeluju na stdin (primjerice putchar i printf), bit će preusmjeren u datoteku output.log. fclose

int fclose(FILE *fp); Funkcija fclose() zatvara tok fp. U slučaju greške vraća EOF, inače vraća 0. Funkcija fclose() se automatski primjenjuje na sve otvorene tokove pri normalnom završetku programa. fflush

int fflush(FILE *fp); Ako je otvoren tok fp, funkcijom fflush() svi se podaci iz privremenog datotečnog spremnika zapišu u datoteku. Funkcija vraća 0, ili EOF ako nastupi greška.

Ulazno izlazne opracije

getchar, getc, fgetc

int getchar(); int getc(FILE *fp); int fgetc(FILE *fp);

Funkcija getc() vraća sljedeći dobavljivi znak iz toka fp, ili EOF ako je kraj datoteke. Obično EOF ima vrijednost -1, kako bi se razlikovao od ostalih znakova. Zbog toga ova funkcija vraća tip int, a ne tip char. Funkcija getchar() je ekvivalentna getc(stdin). Funkcija fgetc() je ekvivalentna getc(), ali može biti implementirana kao makro naredba. putchar, putc, fputc

int putchar(int c);

Page 363: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

363

int putc(int c, FILE *fp); int fputc(int c, FILE *fp);

Funkcija putc() upisuje znak c u tok fp. Funkcija putchar(c) je ekvivalentna putc(c, stdout). Funkcija fputc() je ekvivalentna putc(),ali može biti implementirana kao makro naredba. Sve tri funkcije vraćaju upisani znak, ili EOF u slučaju greške. printf, fprintf

int printf(const char *format, ...); int fprintf(FILE *fp, const char *format, ...);

Funkcija fprintf() upisuje tekstualno formatirane argumente u tok fp. Tri točkice označavaju listu argumenata. Format se određuje u stringu format, koji sadrži znakove koji se direktno upisuju na izlazni tok i specifikatore formatiranog ispisa argumenata.

printf(format, …) je ekvivalentno fprintf(stdout, format, …).

Specifikator formata se sastoji od znaka %, iza kojeg slijede oznake za širinu i preciznost ispisa te tip argumenta, u sljedećem obliku:

%[prefiks][širina_ispisa][. preciznost][veličina_tipa]tip_argumenta Specifikator formata mora započeti znakom % i završiti s oznakom tipa argumenta. Sva ostala polja su opciona (zbog toga su napisana unutar uglatih zagrada). U polje širina_ispisa zadaje se minimalni broj kolona predviđenih za ispis vrijednosti. Ako ispis sadrži manji broj znakova od zadane širine ispisa, na prazna mjesta se ispisuje razmak. Ako ispis sadrži veći broj znakova od zadane širine, ispis se proširuje. Ako se u ovo polje upiše znak * to znači da će se broj kolona indirektno očitati iz slijedećeg argumenta funkcije, koji mora biti tipa int. Polje prefiks može sadržavati jedan znak koji ima sljedeće značenje: - Ispis se poravnava prema lijevoj granici ispisa određenog poljem širina_ispisa. (inače se

poravnava s desne strane) U prazna mjesta se upisuje razmak + Pozitivnim se vrijednostima ispisuje i '+' predznak. razmak Ako je vrijednost positivna, dodaje se razmak prije ispisa (tako se može poravnati kolone s

pozitivnim i negativnim brojevima). 0 Mjesta razmaka ispunjuju se znakom 0. # Alternativni stil formatiranja Polje . preciznost određuje broj decimalnih znamenki iza decimalne točke kod ispisa realnog broja ili minimalni broj znamenki ispisa cijelog broja ili maksimalni broj znakova koji se ispisuje iz nekog stringa. Ovo polje mora započeti znakom točke, a iza nje se navodi broj ili znak *, koji znači da će se preciznost očitati iz slijedećeg argumenta tipa int. Ukoliko se ovo polje ne koristi, tada se podrazumijeva da će realni brojevi biti ispisani s maksimalno šest decimalnih znamenki iza decimalne točke. Polje tip_argumenta može sadržavati samo jedan od sljedećih znakova znakova:

c Argument se tretira kao int koji se ispisuje kao znak iz ASCII skupa. d, i Argument se tretira kao int, a ispisuje se decimalnim znamenkama. e, E Argument je float ili double, a ispis je u eksponentnom formatu. f Argument je float ili double, a ispis je prostom decimalnom formatu. Ako je prefiks # i

Page 364: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

364

preciznost .0, tada se ne ispisuje decimalna točka. g, G Argument je float ili double, a ispis je prostom decimalnom formatu ili u

eksponencijalnom formatu, ovisno o tome koji daje precizniji ispis u istoj širini ispisa. o Argument je unsigned int, a ispisuje se oktalnim znamenkama. p Argument se tretira kao pokazivač tipa void *, pa se na ovaj način može ispisati adresa bilo

koje varijable. Adresa se obično ispisuje kao heksadecimalni broj. s Argument mora biti literalni string odnosno pokazivač tipa char *. u Argument je unsigned int, a ispisuje se decimalnim znamenkama. x, X Argument je unsigned int, a ispisuje se heksadecimalnim znamenkama. Ako se zada

prefiks # , ispred heksadecimalnih znamenki se ispisuje 0x ili 0X. % Ne odnosi se na argument, već znači ispis znaka % n Ništa se ne ispisuje, a odgovarajući argument mora biti pokazivač na int, u kojega se upisuje

broj do tada ispisanih znakova

Polje veličina_tipa može sadržavati samo jedan znak koji se upisuje neposredno ispred oznake tipa. h Pripadni argument tipa int tretira se kao short int ili unsigned short int.

l Pripadni argument je long int ili unsigned long int.

L Pripadni argument realnog tipa je long double.

Funkcije printf() i fprintf() vraćaju broj ispisanih znakova. Ako nastupi greška, vraćaju negativan broj. scanf, fscanf

int scanf(const char *format, ...); int fscanf(FILE *fp, const char *format, ...);

Funkcijom fscanf() dobavljaju se vrijednosti iz tekstualnog toka fp po pravilima pretvorbe koji su određeni u stringu format. Te vrijednosti se zapisuju na memorijske lokacije određene listom pokazivačkih argumenata. Lista argumenata (...) je niz izraza odvojenih zarezom, čija vrijednost predstavlja adresu postojećeg memorijskog objekta. Tip objekta mora odgovarati specifikaciji tipa prethodno zapisanog u stringu format.

scanf(format, …) je ekvivalentno fscanf(stdin, format, …). String format se formira od proizvoljnih znakova i specifikatora formata oblika:

%[prefiks][širina_ispisa][veličina_tipa]tip_argumenta

Ako se pored specifikatora formata navedu i proizvoljni znakovi tada se očekuje da i oni budu prisutni u ulaznom tekstu (osim tzv. bijelih znakova: razmak, tab i nova linija) . Osim u slučaju specifikatora %c, %n, %[, i %% , za sve pretvorbe se podrazumijeva da se iz ulaznog teksta odstrane bijeli znakovi. Jedini opcioni prefiks je znak '*' koji znači da se pretvorena vrijednost ne dodjeljuje ni jednom argumentu. U polju širina_ispisa zadaje se maksimalni broj kolona predviđenih za dobavu vrijednosti.

Page 365: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

365

Polje veličina_tipa može sadržavati samo jedan znak koji se upisuje neposredno ispred oznake tipa pokazivačkog argumenta. To su sljedeći znakovi: h Pripadni argument tipa int tretira se kao short int ili unsigned short int.

l Pripadni argument je long int ili unsigned long int.

L Pripadni argument realnog tipa je long double.

Polje tip_argumenta može sadržavati samo jedan od sljedećih znakova:

c Dobavlja se jedan znak, a argument je tipa char *. Ako je definirano polje širina_unosa, tada

se unosi onoliko znakova kolika je vrijednost širina_unosa. d Dobavlja se cijeli broj, a argument je tipa int * ( ili short int * ili long int *, ako

je zadana veličina tipa h ili l). i Dobavlja se cijeli broj, a argument je tipa int * ( ili short int * ili long int *, ako

je zadana veličina tipa h ili l). Pretvorba se vrši i ako je broj zapisan oktalno (počinjeznamenkom 0) ili heksadecimalno (počinje s 0x ili 0X),

e, E, f, g, G Dobavlja se realni broj. Argument je tipa float * ili double *.

o Dobavlja se cijeli broj zapisan oktalnim znamenkama. Argument je tipa unsigned int* (ili unsigned short int * ili unsigned long int *, ako je zadana veličina tipa h ili l).

p Dobavlja se pokazivač, a argument mora biti tipa void **. s Dobavlja se string koji ne sadrži bijele znakove. Argument mora biti tipa char *. u Dobavlja se cijeli broj, a argument je tipa unsigned int * ( ili unsigned short int

* ili unsigned long int *, ako je zadana veličina tipa h ili l). x, X Dobavlja se cijeli broj zapisan heksadecimalno. Argument je tipa unsigned int*

Argument je tipa unsigned int* (ili unsigned short int * ili unsigned long int *, ako je zadana veličina tipa h ili l).

% Ne odnosi se na argument, već znači dobavu znaka % n Ne vrši se pretvorba. Odgovarajući argument mora biti tipa int *. Upisuje se broj do tada

učitanih znakova [ Za format oblika %[...] dobavlja se string koji sadrži znakove koji su unutar zagrada, prema

sljedećem obrascu: %[abc] znači da se dobavlja string koji može sadržavati znakove a,b ili c. %[a-d] znači da se dobavlja string koji može sadržavati znakove a,b,c ili d. %[^abc] znači da se dobavlja string koji može sadržavati sve znakove osim a,b ili c. %[^a-d] znači da se dobavlja string koji može sadržavati znakove sve znakove osim a,b,c ili d. Argument je tipa char *.

Funkcije scanf()i fscanf() vraćaju broj uspješno izvršenih pretvorbi (bez %n i %*). Ako se ne izvrši ni jedna konverzija, zbog dosegnutog kraja datoteke, vraća se vrijednost EOF. gets, fgets

char *fgets(char *buf, int n, FILE *fp); char *gets(char *buf);

Funkcije fgets() čita liniju teksta (koja završava znakom '\n') iz toka fp, i sprema taj tekst (uključujući '\n' i zaključni znak '\0') u znakovni niz buf. Veličina tog niza je n znakova. Ako u liniji ima više od n-2 znakova, tada neće biti dobavljeni svi znakovi. Funkcije gets() je ekvivalentna funkciji fgets(), ali isključivo služi za dobavu stringa s tipkovnice.

Page 366: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

366

Ona ne prenosi znak nove linije i ne vrši kontrolu broja unesenih znakova. Obje funkcije vraćaju pokazivač na dobavljeni string ili NULL ako je greška ili kraj datoteke. puts, fputs

int puts(char *str); int fputs(char *str, FILE *fp);

Funkcija fputs() ispisuje string str u tok fp. Funkcija puts() ispisuje string str na stdout i dodaje znak nove linije. Obje funkcije vraćaju pozitivnu vrijednost ili EOF ako nastane greška. fread

size_t fread(void *buf, size_t elsize, size_t n, FILE *fp); Funkcija fread() iz toka fp čita blok veličine n x elsize bajta i upisuje u spremnik buf. Za dobavu n elemenata nekog tipa, elsize treba biti jednak sizeof(tip elementa). Kod čitanja znakova je elsize = 1. Funkcija vraća broj dobavljenih elemenata ili EOF u slučaju greške. fwrite

size_t fwrite(void *buf, size_t elsize, size_t n, FILE *fp); Funkcija fwrite() upisuje u tok fp blok veličine n x elsize bajta iz spremnika buf. Za upis n elemenata nekog tipa, elsize treba biti jednak sizeof(tip elementa). Kod upisa n znakova elsize = 1. Funkcija vraća broj upisanih elemenata ili EOF u slučaju greške. ungetc

int ungetc(int c, FILE *fp); Funkcija ungetc() umeće u tok fp znak c, tako da će pri sljedećem čitanju taj znak biti prvi očitan. Ova operacija se može obaviti samo nakon operacije čitanja iz toka. Funkcija vraća znak c ili EOF u slučaju greške.

Pozicioniranje toka

Mjesto na kojem se vrši čitanje/pisanje u tok naziva se trenutna pozicija toka (eng. stream position). U radu s binarnim datotekama može se kontrolirati i postavljati trenutnu poziciju toka. ftell

long int ftell(FILE *fp);

Funkcija ftell() vraća long int vrijednost koja je jednaka trenutnoj poziciji toka fp (kod binarnih datoteka to je pomak u bajtima od početka datoteke). Ako se radi s velikom datotekama, u kojima pozicija može biti veće od long int, tada treba koristiti funkciju fgetpos(). fseek, rewind

int fseek(FILE *fp, long int pos, int from); void rewind(FILE *fp);

Page 367: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

367

Funkcija fseek() postavlja trenutnu poziciju toka fp na poziciju pos, relativno prema vrijednosti from argumenta, koji može imati tri vrijednosti: SEEK_SET (početak datoteke) , SEEK_CUR (trenutna pozicija) i SEEK_END (kraj datoteke). Argument pos može biti negativna vrijednost. Funkcija vraća 0, ako je operacija uspješna.

Ako je argument from jednak SEEK_SET, nova_pozicija = pos;

Ako je argument from jednak SEEK_CUR,

nova_pozicija = trenutna_pozicija + pos;

Ako je argument from jednak SEEK_END nova_pozicija = pozicija_kraja_datoteke + pos;

Nova pozicija mora biti veća ili jednaka nuli, i može biti veća od trenutne pozicije. Funkcija rewind(fp) postavlja poziciju na 0, što je ekvivalentno fseek(fp,0,SEEK_SET). Ovu funkciju se može koristiti i s tekstualnim datotekama. fgetpos, fsetpos

int fgetpos(FILE *fp, fpos_t *pos); int fsetpos(FILE *fp, const fpos_t *pos);

Funkcija fgetpos() zapisuje trenutnu poziciju toka fp u fpos_t objekt na kojega pokazuje pos. Funkcija fsetpos() postavlja trenutnu poziciju toka fp na vrijednost fpos_t objekta na kojeg pokazuje pos (to mora biti vrijednost prethodno dobivena funkcijom fgetpos). Obje funkcije vraćaju 0 ako je operacija uspješna.

Kontrola ulazno/izlaznog spremnika setbuf, setvbuf

void setbuf(FILE *fp, char *buf); void setvbuf(FILE *fp, char *buf, int mode, size_t size);

Pomoću ovih funkcija postavlja se korisnički definirani spremnik buf kao spremnik ulazno/izlaznih operacija. Primjenjuju se prije poziva ulazno/izlaznih operacija na otvoreni tok fp. Kod funkcije setbuf() veličina spremnika mora biti jednaka BUFSIZE (definiran u stdio.h). Ako je buf==NULL tada se ne koristi spremnik. Kod funkcije setvbuf() veličina spremnika se postavlja argumentom size, a način korištenja spremnika se postavlja argumentom mode, koji može imati tri predefinirane vrijednosti: _IONBF Ne koristi se spremnik, već se vrši neposredan pristup datoteci. _IOFBF Spremnik se koristi potpuno, tj. spremnik se prazni tek kada je popunjen. _IOLBF Spremnik se koristi djelomično (uvijek se prazni kada se ispisuje znak nove linije '\n' ).

Dojava i prihvat greške

feof

int feof(FILE *fp);

Funkcija feof() vraća nenultu vrijednost ako je tok u poziciji kraj datoteke, inače vraća 0.

Page 368: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

368

ferror

int ferror(FILE *fp);

Funkcija ferror() vraća nenultu vrijednost ako je nastala greška u radu s tokom, inače vraća 0. clearerr

void clearerr(FILE *fp);

Funkcija clearerr() briše indikatore greške ili kraj datoteke za tok fp. perror

void perror(const char *prefix);

Funkcija perror() ispisuje poruku na stderr o trenutno nastaloj greški. Tip greške se bilježi u globalnoj varijabli errno. Poruka je ista kao i poruka koja se dobije pozivom funkcije strerror(errno). Drugim riječima, perror(p) je otprilike ekvivalentna pozivu

fprintf(stderr, "%s: %s", p == NULL ? "" : p, strerror(errno));

Argument prefix je proizvoljni string koji se ispisuje ispred poruke.

Operacije s formatiranim stringovima

sprintf, sscanf

int sprintf(char *buf, const char *format, ...); int sscanf(const char *buf, const char *format, ...);

Funkcije sprintf() i sscanf() su varijante od printf() i scanf() koje umjesto ulazno/izlaznog toka koriste proizvoljno odabrani string buf. Znak kraja stringa '\0' se tretira kao znak za kraj datoteke. Korisnik mora voditi računa o tome da veličina stringa buf bude dovoljno velika, da se može izvršiti sve pretvorbe formata u sprintf() funkciji.

Operacije s promjenjljivom listom argumenata vprintf, vfprintf, vsprintf

int vprintf(const char *format, va_list argp); int vfprintf(FILE *fp, const char *format, va_list argp); int vsprintf(char *buf, const char *format, va_list argp);

Ove funkcije omogućuju definiranje funkcija s promjenljivim brojem argumenata, koje imaju funkcionalnost kao printf(), fprintf() i sprintf() funkcije. Posljednji argument ovih funkcija argp je pokazivač tipa va_list. To omogućuje rad s promjenljivim brojem argumenata. U sljedećem programu definirana je funkcija za dojavu greške, koju se može koristiti za dojavu greške pri analizi izvornog programa. Ona koristi dvije globalne varijable koje sadrže ime datoteke (filename) i broj linije izvornog programa (lineno). U dojavi greške se uvijek prije izvještaja o tipu greške ispisuje to ime i broj linije.

#include <stdio.h>

Page 369: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

369

#include <stdarg.h> extern char *filename; /* current input file name */ extern int lineno; /* current input line number */

void error(char *msg,...) { va_list argp; va_start(argp, msg); fprintf(stderr, "%s:, line %d: ",filename, lineno); vfprintf(stderr, msg, argp); fprintf(stderr, "\n"); va_end(argp); }

Manipuliranje s datotekama

rename

int rename(const char *origname, const char *newname);

Funkcija rename() vrši promjenu imena datoteke origname, u ime newname. Ako je operacija uspješna funkcija vraća 0, inaće vraća nenultu vrijednost. remove

int remove(const char *name);

Funkcija remove() briše datoteku imena name. Ako je operacija uspješna funkcija vraća 0, inaće vraća nenultu vrijednost. tmpfile, tmpnam

FILE *tmpfile(void); char *tmpnam(char *buf);

Funkcija tmpfile() stvara privremenu datoteku i otvara je u "wb+" modu. Po izlasku iz programa ova se datoteka automatski briše. Funkcija tmpnam() generira jedinstveno ime u string buf, koji mora biti duljine L_tmpnam (predefinirana konstanta). To se ime može koristiti za stvaranje datoteke. Ako je buf==0, ime se generira u internom statičkom spremniku. Funkcija vraća pokazivač na taj spremnik.

C 2 <string.h> Funkcije koje su deklarirane u <string.h> uglavnom služe za rad s ASCIIZ stringovima. Pored njih, definirano je nekoliko funkcija, čije ime počinje s mem, za rad s memorijskim blokovima (nizovima bajta).

Rad sa stringovima

size_t strlen(const char *s)

Vraća duljinu stringa s.

char *strcpy(char *s, const char *t)

Kopira string t u string s, uključujući '\0'; vraća s.

Page 370: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

370

char *strncpy(char *s, const char *t, size_t n)

Kopira najviše n znakova stringa t u s; vraća s. Dopunja string s sa znakovima '\0', ako t ima manje od n znakova. Napomena: ako u stringu t ima n ili više znakova, tada string s neće biti zaključen s '\0'.

char *strcat(char *s, const char *t)

Dodaje string t na kraj stringa s; vraća s.

char *strncat(char *s, const char *t, size_t n)

Dodaje najviše n znakova stringa t na string s, i znak '\0'; vraća s.

int strcmp(const char *s, const char *t)

Uspoređuje string s sa stringom t, vraća <0 ako je s<t, 0 ako je s==t, ili >0 ako je s>t. Usporedba je leksikografska, prema ASCII rasporedu.

int strncmp(const char *s, const char *t, size_t n)

Uspoređuje najviše n znakova stringa s sa stringom t; vraća <0 ako je s<t, 0 ako je s==t, ili >0 ako je s>t.

int strcoll(const char *s, const char *t);

Uspoređuje dva stringa s1 and s2, poput strcmp(), ali se usporedba vrši prema multinacionalnom znakovnom rasporedu (koji je određen konstantom LC_COLLATE ). Vraća <0 ako je s<t, 0 ako je s==t, ili >0 ako je s>t.

size_t strxfrm(char *s, const char *t, size_t n);

Stvara modificiranu kopiju n znakova stringa t u stringu s (uključujući '\0') , tako da strcmp(s,t) daje istu ocjenu kao i strcoll(s,n) na originalnom stringu. Vraća broj znakova u stringu s.

char *strchr(const char *s, int c)

Vraća pokazivač na prvu pojavnost znaka c u stringu s, ili NULL znak ako c nije sadržan u stringu s.

char *strrchr(const char *s, int c)

Vraća pokazivač na zadnju pojavnost znaka c u stringu s, ili NULL znak ako c nije sadržan u stringu s.

char *strstr(const char *s, const char *t)

Vraća pokazivač na prvu pojavu stringa t u stringu s, ili NULL ako string s ne sadrži string t.

size_t strspn(const char *s, const char *t)

Vraća duljinu prefiksa stringa s koji sadrži znakove koji čine string t.

size_t strcspn(const char *s, const char *t)

Vraća duljinu prefiksa stringa s koji sadrži znakove koji nisu prisutni u stringu t.

Page 371: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

371

char *strpbrk(const char *s, const char *t)

Vraća pokazivač na prvu pojavu bilo kojeg znaka iz string t u stringu s, ili NULL ako nije prisutan ni jedan znak iz string t u stringu s.

char *strerror(int n)

Vraća pokazivač na string koji se interno generira, a služi za dojavu greške u nekim sistemskim operacijama. Argument je obično globalna varijabla errno, čiju vrijednost određuje izvršenje funkcija iz standardne biblioteke.

char *strtok(char *s, const char *sep)

strtok() je funkcija kojom se može izvršiti razlaganje stringa na niz leksema koji su razdvojeni znakovima-separatorima. Skup znakova-separatora se zadaje u stringu sep. Funkcija vraća pokazivač na leksem ili NULL ako nema leksema. Korištenje funkcije strtok() je specifično jer u stringu može biti više leksema, a ona vraća pokazivač na jedan leksem. Da bi se dobili slijedeći leksemi treba iznova pozvati ovu funkciju, ali s prvim argumentom jednakim NULL. Primjerice, za string

char *s = "Prvi drugi,treci";

ako odaberemo znakove separatore: razmak, tab i zarez, tada sljedeći iskazi daju ispis tri leksema (Prvi drugi i treci):

char *leksem = strtoken(s, " ,\t"); /* dobavi prvi leksem */ while( leksem != NULL) { /* ukoliko postoji */ printf("", leksem); /* ispiši ga i */ lexem = strtok(NULL," ,\t"); /* dobavi sljedeći leksem */ } /* pa ponovi postupak */

Operacije s memorijskim blokovima (nizovima)

memcpy, memmove

void *memcpy(void *dest, const void *src, size_t n); void *memmove(void *dest, const void *src, size_t n);

Ove funkcije kopiraju točno n bajta s lokacije na koju pokazuje src na lokaciju koju pokazuje dest. Ukoliko se blokovi preklapaju tada treba koristiti funkciju memmove(). Funkcije vraćaju pokazivač dest. memcmp

int memcmp(const void *p1, const void *p2, size_t n);

Uspoređuje točno n znakova s lokacija na koje pokazuju p1 i p2, na isti način kao strcnmp(), ali se usporedba ne prekida ako je dostignut znak '\0'. memchr

void *memchr(const void *p, int c, size_t n);

Page 372: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

372

Traži prvu pojavu znaka c u n znakova bloka na koji pokazuje p. Vraća pokazivač na pronađeni znak ili NULL ako znak nije pronađen. memset

void *memset(void *p, int c, size_t n);

Postavlja n bajta bloka na koji pokazuje p na vrijednost znaka c, i vraća p.

C 3 <ctype.h> Funkcije iz <ctype.h> omogućuju klasifikaciju znakova te pretvorbu velikih u mala slova i obratno.

Klasifikacija znakova

int isupper(int c);

vraća vrijednost različitu od nule ako je znak c veliko slovo, inače vraća 0.

int islower(int c);

vraća vrijednost različitu od nule ako je znak c malo slovo, inače vraća 0.

int isalpha(int c);

vraća vrijednost različitu od nule ako je znak c veliko ili malo slovo, inače vraća 0.

int iscntrl(int c);

vraća vrijednost različitu od nule ako je znak c kontrolni znak, inače vraća 0.

int isalnum(int c);

vraća vrijednost različitu od nule ako je znak c slovo ili znamenka, inače vraća 0.

int isdigit(int c);

vraća vrijednost različitu od nule ako je znak c decimalna znamenka, inače vraća 0.

int isxdigit(int c);

vraća vrijednost različitu od nule ako je znak c heksadecimalna znamanka, inače vraća 0.

int isgraph(int c);

vraća vrijednost različitu od nule ako je znak c tiskani znak osim razmaka, inače vraća 0.

int isprint(int c);

vraća vrijednost različitu od nule ako je znak c tiskani znak uključujući razmak, inače vraća 0.

int ispunct(int c);

vraća vrijednost različitu od nule ako je znak c tiskani znak osim razmaka, slova ili znamanke, inače vraća 0.

Page 373: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

373

int isspace(int c);

vraća vrijednost različitu od nule ako je znak c razmak, tab, vert. tab, nova linija, povrat ili nova stranica, inače vraća 0.

Pretvorba znaka

int toupper(int c); int tolower(int c);

Funkcija toupper() pretvara malo slovo u ekvivalentno veliko slovo, ostala slova ostaju nepromijenjena. Slično, tolower() pretvara veliko slovo u ekvivalentno malo slovo.

C 4 <stdlib.h> U zaglavlju <stdlib.h> definirano je nekoliko temeljnih funkcija za alokaciju memorije, pretvorbu stringa u brojeve, manipuliranje s multibajtnim znakovnim skupom, itd.

Alokacija memorije

malloc, calloc

void *malloc(size_t n); void *calloc(size_t n, size_t elsize);

Funkcija malloc() alocira se n bajta slobodne memorije. Ako je alociranje uspješno funkcija vraća pokazivač na tu memoriju, u suprotnom vraća NULL pokazivač. Primjerice, naredbom

double *dp = malloc(10 * sizeof(double));

dobije se pokazivač dp, koji pokazuje na niz od 10 elemenata tipa double. Funkcija calloc(n, elsize) je ekvivalentna malloc(n * elsize), uz dodatni uvjet da calloc() inicijalizira sve bitove alocirane memorije na vrijednost nula. free

void free(void *p);

Funkcija free() prima kao argument pokazivač p. Uz pretpostavku da p pokazuje na memoriju koja je prethodno alocirana funkcijom malloc(), calloc() ili realloc(), ova funkcija dealocira tu memoriju. realloc

void *realloc(void *oldptr, size_t newsize);

Funkcija realloc() vrši promjenu veličine prethodno alocirane memorije, koja je pridijeljena pokazivaču ptr, na veličinu newsize. Funkcija realloc() vraća pokazivač na tu memoriju. Vrijednost toga pokazivača može biti ista kao i vrijednost od ptr, ako memorijski alokator može prilagoditi veličinu zahtijevanog području slobodne memorije veličini newsize. Ukoliko se to ne može ostvariti funkcija realloc() alocira novo područje memorije pa u njega kopira i zatim oslobađa dio memorije na koju pokazuje ptr. Ukoliko se ne može izvršiti alokacija memorije funkcija realloc() vraća NULL. Napomena: poziv realloc(p, 0) je ekvivalentan pozivu free(p), a poziv realloc(0, n) je ekvivalentan pozivu malloc(n).

Page 374: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

374

Pretvorba stringa u numeričku vrijednost

atoi, atol, strtol, strtoul

int atoi(const char *s); long int atol(const char *s); long int strtol(const char *s, char **endp, int baza); unsigned long int strtoul(const char *s, char **endp, int baza);

Ove funkcije pretvaraju numerički string s u odgovarajuću numeričku vrijednost. Funkcija strtol() u stringu s preskače bijele znakove, pretvara s u broj i vraća vrijednost tipa long int. Ona omogućuje pretvorbu iz sustava različite baze. Ako je baza 10 tada se iz ulaznog stringa s prihvaćaju znakovi od 0 do 10, a ako je baza 16 prihvaćaju se i znakovi a-f, A-F. Ako je baza manja od 10 prihvaćaju se znakovi od 0 do baza-1. Ako je baza 0, tada se koristi pravilo da oktalni brojevi počinju s nulom, a heksadecimalni s 0x ili 0X.

atoi(s) je ekvivalntno strtol(s,NULL,0);

Argument endp ja pokazivač na pokazivač na char. Njega se koristi za tako da ako endp != NULL, tada strtol() sprema u *endp pokazivač na prvi znak koji nije obrađen. Ako je to znak '\0', pretvorba je uspješna. Ako je jednak s, pretvorba uopće nije izvršena. Funkcija vraća vrijednost koja je pretvoreni broj ili 0 ako pretvorba nije izvršena ili konstanta LONG_MAX ili LONG_MIN ako se broj ne može predstaviti kao long int. U slučaju prekoračenja postavlja se errno = ERANGE. Funkcija strtoul() je slična strtol() osim što vraća tip unsigned long int, ili vrijednost ULONG_MAX kod prekoračenja. Poziv atol(s) je ekvivalentan pozivu strtoul(s,NULL,0). atof, strtod

double atof(const char *s); double strtod(const char *s, char **endp);

Ove funkcije pretvaraju numerički string s u odgovarajuću numeričku vrijednost realnog broja. Funkcija strtod() u stringu s preskače bijele znakove, pretvara s u broj i vraća vrijednost tipa double. Prihvaća prosti i eksponentni zapis realnog broja. Argument endp ja pokazivač na pokazivač na char. Njega se koristi za tako da ako endp != NULL, tada strtod() sprema u *endp pokazivač na prvi znak koji nije obrađen. Ako je to znak '\0', pretvorba je uspješna. U slučaju prekoračenja vraća konstantu HUGE_VAL, i postavlja globalnu varijablu errno = ERANGE. Napomena: atof(s) je ekvivalentno strtod(s, NULL), osim što rezultat nije definiran u slučaju prekoračenja.

Generator slučajnih brojeva

rand

int rand(void);

Funkcija rand() vraća slučajni cijeli broj iz intervala 0 do RAND_MAX ( RAND_MAX je konstanta definirana u <stdlib.h>). srand

Page 375: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

375

void srand(unsigned int seed);

Funkcija srand() postavlja početnu vrijednost seed generatora slučajnih brojeva.

Sortiranje i traženje

qsort

void qsort(void *a, size_t n, size_t elsize, int (*cmpfunc)());

Funkcija qsort() sortira niz a, koji ima n elemenata veličine elsize (u bajtima), prema kriteriju koji je određen funkcijom na koju pokazuje cmpfunc. Ta funkcija mora biti deklarirana u obliku:

int name(const void *p1, const void *p2);

i mora vratiti cijeli broj koji je manji, veći ili jedanak nuli, ovosno o tome da li je objekt na kojeg pokazuje p1 manji, veći, ili jednak objektu na kojeg pokazuje p2. bsearch

bsearch(const void *pObj, const void *a, size_t n, size_t elsize, int (*cmpfunc)());

Funkcija bsearch() vrši binarno traženje u sortiranom nizu a, koji ima n elemenata veličine elsize (u bajtima), tražeći element koji je jednak objektu na kojeg pokazuje pObj. Pri traženju se za usporedbu koristi funkcija na koju pokazuje cmpfunc. Deklaracija te funkcije je ista kao kod qsort().

Interakcija s operativnim sustavom

getenv

char *getenv(const char *name);

"Environment" sadrži postavke operativnog sustava u sistemskim varijablama (pr. path). Funkcija getenv() traži "environment varijablu" imena name i vraća njenu vrijednost u obliku stringa. Ako ne može pronaći tu varijablu, tada vraća NULL. atexit

int atexit(void (*func)(void));

Funkcija atexit() prima kao argument pokazivač na funkciju func, koja će biti pozvana pri normalnim završetku programa. Vraća 0 ako je funkcija uspješno registrirana. Može se registrirati do 32 funkcije, koje će biti pozvane u obrnutom redoslijedu od reda registriranja. exit

void exit(int status);

Funkcija exit() vrši normalni završetak programa, poziva sve funkcije koje su registrirane s atexit() funkcijom i zatvara sve tokove. Argument status se prosljeđuje operativnom sustavu. Mogu se koristiti i dvije simboličke konstante EXIT_SUCCESS i EXIT_FAILURE. Po dogovoru, status=0 (EXIT_SUCCESS) znači uspješan završetak programa.

Page 376: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

376

system

int system(const char *s);

Funkcija system() prima kao argument string koji sadrži komandu operativnog sustava. Funkcija vraća vrijednost koju vrati operativni sustav po završetku procesa. abort

void abort(void);

Funkcija abort() predstavlja zahtjev za neposrednim prekidom programa, na isti način kao da je izvršen poziv raise(SIGABRT).

Cjelobrojne aritmetičke funkcije

abs, labs

int abs(int x); long int abs(long int x);

Obje funkcije vraćaju apsolutnu vrijednost argumenta x.. div, ldiv

div_t div(int num, int denom); ldiv_t div(long int num, long int denom);

Ove funkcije vrše dijeljenje num/denum na način da se istovremeno dobije rezultat dijeljenja i ostatak cjelobrojnog dijeljenja. Rezultat se vraća u vrijednost koja je tipa sljedeće strukture:

typedef struct { int quot; /* rezultat dijeljenja */ int rem; /* ostatak dijeljenja */ }div_t;

Multibajt znakovne sekvence i stringovi

Za zapis znakova po ASCII standardu koristi se tip char. Za zapis znakova se također može koristiti prošireni znakovni tip wchar_t, koji podržava 16-bitni Unicode standard, i multibajt znakovne sekvence MBCS (za jezike poput japanskog). mblen

int mblen(const char *s, size_t n);

Funkcija mblen() vraća broj znakova u stringu koji sadrži multibajt znakovne sekvence. Analizira se maksimalno n znakova. mbtowc, wctomb

int mbtowc(wchar_t dest, const char *src, size_t n); int wctomb(char *dest, wchar_t src);

Page 377: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

377

Ove funkcije pretvaraju multibajt znakovnu sekvencu u tip wchar_t i obrnuto. Funkcija mbtowc() analizira maksimalno n bajta iz stringa src i pretvara ih u wchar_t i sprema u dest, te vraća broj bajta ili -1 ako pretvorba nije uspješna. Funkcija wctomb() pretvara prošireni znak src u multibajt sekvencu dest, i vraća broj bajta u toj sekvenci.. (Ovaj broj neće nikada biti veći od konstante MB_CUR_MAX, definirane u <stdlib.h>.) mbstowcs, wcstombs

size_t mbstowcs(wchar_t *dest, const char *src, size_t n); size_t wcstombs(char *dest, wchar_t src);

Ove funkcije vrše pretvorbu višestruke multibajt sequence i niza proširenih znakova. Funkcija mbtowcs() pretvara multibajt sekvencu src i u niz proširenih znakova i sprema u dest, ali maksimalno n znakova. Vraća broj pretvorenih znakova. Funkcija wcstombs() vrši obrnutu radnju.

C 5 <math.h> Standardne matematičke funkcije su deklarirane u zaglavlju <math.h>. Njihov opis je dan sljedećoj tablici: Funkcija Vraća vrijednost double sin(double x); sin(x) , sinus kuta x u radijanima double cos(double x); cos(x) , kosinus kuta x u radijanima double tan(double x); tg(x) , tangens kuta x u radijanima double asin(double x); arcsin(x), vraća vrijednost [-π/2, π/2], za x ∈ [-1,1]. double acos(double x); arccos(x), vraća vrijednost [0, π], za x ∈ [-1,1]. double atan(double x); arctg(x), vraća vrijednost [-π/2, π/2]. double atan2(double y, double x);

arctan(y / x), vraća vrijednost [-π,π].

double sinh(double x); sh(x), sinus hiperbolni kuta x double cosh(double x); ch(x) , kosinus hiperbolni kuta x double tanh(double x); th(x) , tangens hiperbolni kuta x double exp(double x); ex , x potencija broja e = 2,781 double log(double x); prirodni logaritam ln(x), x>0. double log10(double x); logaritam baze 10, log10(x), x>0. double pow(double x, double y);

xy, potenciranje x s eksponentom y. Nastaje greška ako je x=0 i y <= 0, ili ako je x<0, a y nije cijeli broj.

double sqrt(double x); √x, za x>=0, dugi korijen pozitivnog broja double ceil(double x); najmanji cijeli broj (tipa double) koji nije manji od x double floor(double x); najveći cijeli broj (tipa double) koji nije veći od x double fabs(double x); |x|, apsolutna vrijednost od x double ldexp(double x,int n); x*2n double frexp(double x, int *exp);

rastavlja x na frakcioni dio i eksponent broja 2. Vraća normaliziranu frakciju od x (iz intervala [1/2,1)] i exponent od 2 u *exp. Ako je x jednak nuli, oba dijela su jednaka nuli. Primjerice, frexp(2.5, &i) vraća 0.625 (odnosno 0.101 baze 2) i postavlja i na 2, tako da ldexp(0.625, 2) ponovo daje vrijednost 2.5.

double modf(double x, double *ip);

rastavlja x na integralni i frakcioni dio, tako da oba imaju predznak od x. Vraća frakcioni dio, a integralni dio smješta u *ip.

double fmod(double x, double y);

ostatak dijeljenja realnih brojeva x/y, s predznakom od x.

Page 378: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

378

C 6 <time.h> U zaglavlju <time.h> definirane su funkcije i strukture za rad s datumima i vremenom. Posebni typedef tip time_t, služi za bilježenje vremena. Obično se uzima da ovaj tip predstavlja broj sekundi počevši od 01.01.1970. godine. Za detaljniji zapis vremena i datuma koristi se struktura tm, koja je definirana na sljedeći način:

struct tm /* opisuje vrijeme i datum */ { int tm_sec; /* sekunde 0..61 */ int tm_min; /* minute 0..59 */ int tm_hour; /* sat 0..23 */ int tm_mday; /* dan 1..31 */ int tm_mon; /* mjesec 0..11 */ int tm_year; /* broj godina nakon 1900 */ int tm_wday; /* dan u sedmici 0..6 */ int tm_yday; /* dan u godini 0..365 */ int tm_isdst; /* da li je dan promjene sata 0..1 */ };

Napomena: ako dan promjene sata nije implementiran, tada tm_isdst ima negativnu vrijednost.

Broj sekundi može biti veći od 59 u slučaju prestupnog vremena. Mjeseci su kodiranu tako da 0 označava siječanj, 1 veljaču itd. Dani u sedmici su kodirani tako da 0 označava nedjelju, 1 ponedjeljak itd. Stvarna godina se dobije tako da se članu tm_year doda vrijednost 1900 (primjerice u godini 2002. godini član tm_year sadrži vrijednost 102).

time

time_t time(time_t *tp);

Funkcija time() vraća time_t vrijednost – kardinalni broj koji predstavlja trenutno vrijeme (obično je to broj sekundi od 1.1.1970.). Parametar tp, ako nije NULL, također prihvaća trenutno vrijeme u *tp. localtime, gmtime

struct tm *localtime(const time_t *t); struct tm *gmtime(const time_t *t);

Pretvorbu vremena iz formata time_t u struct tm vrši se funkcijom localtime(), kada se želi dobiti lokalno vrijeme, ili funkcijom gmtime() za univerzalno vrijeme u nultom meridijanu. Obje funkcije primaju adresu varijable koja sadrži vrijeme u formatu time_t, a vraćaju pokazivač na statičku strukturu tipa tm (sadržaj se obnavlja pri svakom pozivu ovih funkcija) . ctime, asctime Ako se želi dobiti zapis vremena u obliku stringa, mogu se koristiti funkcije

char *ctime(const time_t *t); char *asctime(const struct tm *tp);

Funkcija ctime() za argument koristi adresu varijable koja sadrži vrijeme u formatu time_t, a funkcija asctime() za argument koristi pokazivač na strukturu tm. Obje funkcije vraćaju

Page 379: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

379

pokazivač statičkog stringa koji sadrži zapis vremena u standardnom formatu. Primjerice, sekvenca naredbi

time_t t = time(NULL); char *s = ctime(&t); puts(s);

generira ispis:

Sat May 11 14:21:20 2002

Uočite da je rezultat poziva ctime(&t) ekvivalentan pozivu asctime(localtime(&t)) . Standardna verzija je prilagođena američkim standardima. Ako se želi napisati vrijeme u formatu

11.05.2002 14:21

tada se može koristiti sljedeće iskaze:

/* ispisuje datum i vrijeme u formatu 11.05.2002 14:21 */ time_t t = time(NULL); struct tm *p = localtime(&t); printf("%.2d.%.2d.%.2d %2d:%.2d\n", p->tm_mday, p->tm_mon + 1, p->tm_year +1900, p->tm_hour, p->tm_min);

strftime

size_t strftime(char *buf, size_t bufsize, const char *fmt, const struct tm *tp);

Funkcija strftime() se koristi za formatirani ispis vremena. Format se zadaje kao kod printf() funkcije. Prvi argument je string str u koji se vrši formatirani zapis. Drugi argument (bufsize) ograničava broj znakova stringa. Treći parametar je string u kojem se zapisuje format ispisa nizom specifikatora oblika %x (kao kod printf() funkcije). Posljednji argument je pokazivač strukture tm. Funkcija vraća broj znakova u stringu ili 0 ako nije moguće generirati formatirani string. Specifikatori formata su:

%a kratica od tri slova za ime dana u sedmici (eng. Sun, Mon, Tue,..) %A puno ime dana u sedmici (eng.) %b kratica od tri slova za ime mjeseca (eng. Jan, Feb, Mar,...) %B puno ime mjeseca (eng.) %c kompletni zapis vremena i datuma %d dan u mjesecu (1..31) %H sat u formatu (1..24) %I sat u formatu (1..12) %j dan u godini (1..365) %m mjesec u godini (1..12) %M minute %p AM/PM (eng.) string koji označava jutro ili popodne %S sekunde %U broj za sedmicu u godini (1..52) - 1 određen prvom nedjeljom %w broj za dan u sedmici (0-nedjelja) %W broj za sedmicu u godini (1..52) - 1 određen prvim ponedjeljkom

Page 380: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

380

%x kompletni zapis datuma %X kompletni zapis vremena %y zadnje dvije znamenke godine %Y godina u 4-znamenkastom formatu %Z ime vremenske zone (ako postoji ) %% znak %

mktime

time_t mktime(struct tm *tp);

Funkcija mktime() pretvara zapisa iz strukture tm u time_t format. Korisna je u tzv. kalendarskim proračunima. Kada je potrebno dodati nekom datumu n dana, tada se može upisati datum u tm strukturu, povećati član tm_mday za n, zatim pozivom mktime() se dobije time_t vrijednost koja odgovara novom datumu.

difftime

double difftime(time_t t1, time_t t2);

Funkcija difftime() vraća realnu vrijednost koja je jednaka razlici vremena t1 i t1 u sekundama. clock

clock_t clock(void);

Funkcija clock() služi za preciznije mjerenje vremena nego je to moguće sa prethodnim funkcijama. Ona vraća vrijednost procesorskog mjerača vremena, koji starta na početku programa, u jedinicama koje su znatno manje od sekunde (nekoliko milisekundi). Koliko je tih jedinica u jednoj sekundi određeno je konstantom CLOCKS_PER_SEC. To znači da izraz:

(double)clock()/CLOCKS_PER_SEC

daje vrijednost koja je jednaka vremenu (u sekundama) od pokretanja programa.

C 7 <signal.h> U zaglavlju <signal.h> deklarirane su dvije funkcije (signal() i raise()) za prihvat i generiranje asinkronih prekida programa ili "signala". Za identificiranje nekoliko mogućih signala, u ovom zaglavlju su definirane simboličke cjelobrojne konstante sa sljedećim imenima: SIGABRT Signal kojeg generira funkcija abort(). SIGFPE Signal koji se generira kad nastane greška kod matematičkih operacija primjerice pri

dijeljenju s nulom. SIGILL Signal koji se generira ako se pokušava izvršiti nepostojeća ili nedozvoljena instrukcija

procesora. SIGINT Signal koji se generira s tipkovnice (primjerice, Ctrl-C tipkom). SIGSEGV Signal koji se generira ako se pristupa zaštićenoj ili nepostojećoj memoriji

("segmentation violations"). SIGTERM Signal koji se generira kada je proces prekinut nekim vanjskim događajem. Ovisno o implementaciji kompilatora, moguće su i dodatne definicije identifikatora signala.

Page 381: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

381

raise

int raise(int sig);

Funkcija raise() šalje izvršnom programu signal sig. signal

void (*signal(int sig, void (*func)(int)))(int);

Funkcija signal() se koristi za definiranje akcije koja se treba izvršiti kada se pojavi neki signal. Ukoliko nije definirana radnja koja se vrši nakon pojave signala, prekida se program. Argument sig je signal kojeg treba prihvatiti (jedna od SIGxxx konstanti). Argument func je ili konstanta SIG_IGN (kojom se zahtijeva ignoriranje signala) ili konstanta SIG_DFL (kojom se postavlja predodređeni postupak prihvata signala) ili pokazivač na korisnički definiranu funkciju koja će se izvršiti pojavom signala. Ta funkcija mora imati prototip oblika

void signalhandler(int sig);

Argument ove funkcije tipa int je broj signala koji se prihvaća.

Funkcija signal() vraća prethodni oblik prihvata signala; SIG_DFL, SIG_IGN, ili pokazivač na funkciju. Zbog navedenih svojstava, deklaracija funkcije signal() je kompleksna. To je funkcija koja vraća pokazivač na funkciju koja prima jedan argument tipa int i vraća void. Prvi argument je tipa int, a drugi argument je pokazivač na funkciju koja prima jedan argument tipa int i vraća void.

Primjer: u sljedećem programskom odsječku pokazano je kako se postavlja poziv funkcije exithandler() u slučaju pojave prekida (interrupt signal - SIGINT), ali samo ako taj signal nije prethodno ignoriran:

extern void exithandler(int); if(signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, exithandler);

C 8 <setjmp.h> U zaglavlju <setjmp.h> deklarirane su funkcije setjmp() i longjmp(), pomoću kojih se može izvršiti skok u program i izvan funkcije. Mjesto u programu, na koju se vrši skok, označava se funkcijom setjmp(), koja pamti stanje stoga, registre procesora i trenutnu adresu programa u objektu tipa jmp_buf. Kasnije se s bilo kojeg mjesta u programu može skočiti na ovu poziciju pozivom funkcije longjmp(). setjmp

int setjmp(jmp_buf context);

Funkcija setjmp() sprema trenutnu programsku adresu i stanje procesora u objekt context, koji je tipa jmp_buf, i vraća vrijednost 0. Kasnije, nakon poziva funkcije longjmp(), povratna vrijednost se može promijeniti. longjmp

Page 382: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

382

void longjmp(jmp_buf context, int retval)

Funkcija longjmp() vrši skok na stanje opisano u objektu context, koji je prethodno spremljen pozivom funkcija setjmp(). Skok se vrši na mjesto gdje je prethodno pozvana funkcija setjmp(), pa se sada vrši povrat iz funkcije setjmp()s vrijednošću retval.

C 9 <locale.h> U zaglavlju <locale.h> deklarirane su dvije funkcije za lokalizirane postavke. Početno, program počinje u "C" lokalizaciji, koja se zatim može promijeniti sa setlocale() funkcijom. Lokalno-specifične informacije se dijele u nekoliko kategorija, koje se označavaju sljedećim konstantama:

LC_COLLATE Usporedba stringova se vrši pomoću funkcija strcoll() i strxfrm() LC_CTYPE Klasiofikacija znakova pomoću funkcija iz <ctype.h> LC_MONETARY Monetarne postavke se dobiju funkcijom localeconv() LC_NUMERIC Koristi decimalnu točku u funkcijama printf(), scanf(), strtod(),

itd. LC_TIME Lokalizirani format strftime() funkcije LC_ALL Unija prethodnih postavki

setlocale

char *setlocale(int cat, const char *locale);

Funkcija setlocale() ima dva argumenta. Prvi argument je oznaka kategorije koja se postavlja, a drugi parametar locale je string za oznaku lokalizacije. Ako taj string je jednak "C", tada se koristi predodređena lokalizacija. Prazni string "" također označava predodređenu lokalizaciju. Sve ostale oznake su specifične za pojedinu implementaciju kompilatora. Funkcija vraća pokazivač na string koji sadrži prethodnu locale postavku. Ako se setlocale() pozove s locale == NULL tada funkcija vraća trenutnu postavku. localeconv

struct lconv *localeconv(void);

Funkcija localeconv() vraća pokazivač na strukturu lconv koja sadrži lokalno-specifične informacije. Ta struktura je otprilike definirana ovako:

struct lconv { char *decimal_point; char *thousands_sep; char *grouping; char *int_curr_symbol; char *currency_symbol; char *mon_decimal_point; char *mon_thousands_sep; char *mon_grouping; char *positive_sign, *negative_sign; char int_frac_digits; char frac_digits; char p_cs_precedes, p_sep_by_space; char n_cs_precedes, n_sep_by_space; char p_sign_posn, n_sign_posn;

Page 383: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

383

};

decimal_point je oznaka koja se koristi za decimalni zarez. thousands_sep je separator koji se koristi između grupe znamenki grouping je string koji definira veličinu grupe (primjerice "\3" označava da se ponavlje grupa od 3 znaka ).

Ostali članovi opisuju monetarno-specifične informacije. Ukratko, int_curr_symbol i currency_symbol su verzije (internacionalne i lokalne) za lokalnu valutu, mon_decimal_point je decimalna točka, mon_thousands_sep i mon_grouping dopisuju grupiranje znamenki (analogno s thousands_sep i grouping), positive_sign i negative_sign su znakovi pozitivnog i negativnog predznaka, int_frac_digits i frac_digits opisuju broj decimalnih znamenki koje se prikazuju. Ostali članovi opisuju oznaku valute i indikatore predznaka.

C 10 <stdarg.h> U zaglavlju <stdarg.h> su definirani makro naredbe pomoću kojih se omogućuje definiranje funkcija s promjenjljivim brojem parametara. Koristi se ideja da se argumentima neke funkcije pridijeli lista pokazivača koja ima apstraktni tip va_list. U tu svrhu koristi se makro va_start. Zatim se iz ove liste mogu dobiti svi argumenti pomoću makroa va_arg. Na kraju rada, unutar iste funkcije, treba pozvati makro va_end. va_start

va_start(va_list argp, Lastarg);

va_start inicijalizira argp tako da se njime mogu dohvatiti argumenti. Lastarg je ime posljednjeg fiksnog argumenta funkcije. va_arg

argtype va_arg(va_list argp, argtype);

va_arg dobavlja vrijednost sljedećeg argumenta koji je tipa argtype. Tip argtype se specificira na isti način kako se definira argument sizeof operatora. Tip mora odgovarati tipu sljedećeg argumenta. va_end

va_end(va_list argp);

va_end označava da je završen pristup promjenjljivoj listi argumenata. Primjer: u sljedećem programu definirana je funkcija miniprintf(), kojom je pokazano kako je implementirana printf() funkcija.

#include <stdio.h> #include <stdarg.h> void miniprintf(const char *format, ...) { va_list argp;

Page 384: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

384

const char *p; char tmpbuf[25]; int i; va_start(argp, format); for(p = format; *p != '\0'; p++) { if(*p != '%') { putchar(*p); continue; } switch(*++p) { case 'c': i = va_arg(argp, int); putchar(i); break; case 'd': i = va_arg(argp, int); sprintf(tmpbuf, "%d", i); fputs(tmpbuf, stdout); break; case 'o': i = va_arg(argp, int); sprintf(tmpbuf, "%o", i); fputs(tmpbuf, stdout); break; case 's': fputs(va_arg(argp, char *), stdout); break; case 'x': i = va_arg(argp, int); sprintf(tmpbuf, "%x", i); fputs(tmpbuf, stdout); break; case '%': putchar('%'); break; } } va_end(argp); }

C 11 <stddef.h> U zaglavlju <stddef.h> definirano je nekoliko tipova i makro naredbi. NULL Makro koji označava konstantu za nul pokazivač (vrijednost mu je 0 ili (void *)0). size_t Cjelobrojni unsigned tip koji se koristi za označavanje veličine memorijskog

objekta. ptrdiff_t Cjelobrojni tip koji označava vrijednosti koji nastaju kod oduzimanja pokazivača. wchar_t Tip “wide character” koji može imati znatno veći interval vrijednosti od tipa char.

Koristi se za multinacionalni skup znakova (Unicode). offsetof() Makro kojim se računa pomak (eng. offset) u bajtima nekog elementa strukture,

primjerice offsetof(struct tm, tm_year). Korištenje ovih tipova osigurava prenosivost programa.

C 12 <assert.h> U zaglavlju <assert.h> definar je makro assert, koji omogućuje testiranje pograma.

Page 385: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

385

void assert(int test_izraz)

Ako je test_izraz jednak nuli, tada

assert(test_izraz)

šalje na stdderr poruku, poput ove:

Assertion failed: test_izraz, file filename, line nnn

i vrši poziv funkcije abort(), čime se prekida izvršenje programa. Ime izvorne datoteke (filename) i broj linije u kojoj je assert, su dobijeni pretprocesorskih makroa: __FILE__ i __LINE__. Ako se pri kompiliranju definira makro NDEBUG (s bilo kojom vrijednošću) tada se ignorira makro assert.

C 13 <errno.h> U zaglavlju <errno.h> deklarirana je globalna varijable errno u kojoj se bilježi kôd greške koja nastaje pri korištenju funkcija standardne biblioteke. Također, definirane su simboličke konstante EDOM and ERANGE koje označavaju kôd pogreške kod matematičkih operacija.

C 14 <limits.h> U zaglavlju <limits.h> definirane su simboličke konstante koje označavaju interval standardnih tipova. To su:

CHAR_BIT broj bitova u tipu char CHAR_MAX maksimalna vrijednost char tipa CHAR_MIN maksimalna vrijednost char tipa INT_MAX maksimalna vrijednost int tipa INT_MIN minimalna vrijednost int tipa LONG_MAX maksimalna vrijednost long tipa LONG_MIN minimalna vrijednost long tipa SCHAR_MAX maksimalna vrijednost signed char tipa SCHAR_MIN minimalna vrijednost signed char tipa SHRT_MAX maksimalna vrijednost short tipa SHRT_MIN minimalna vrijednost short tipa UCHAR_MAX maksimalna vrijednost unsigned char tipa UINT_MAX maksimalna vrijednost unsigned int tipa ULONG_MAX maksimalna vrijednost unsigned long tipa USHRT_MAX maksimalna vrijednost unsigned short tipa MB_LEN_MAX broj bajta u multibajt znakovnoj sekvenci

C 15 <float.h> U zaglavlju <float.h> definirane su simboličke konstante koje označavaju implementaciju realnih brojeva s pomičnom točkom. To su:

FLT_RADIX FLT_ROUNDS FLT_MANT_DIG DBL_MANT_DIG LDBL_MANT_DIG FLT_DIG DBL_DIG LDBL_DIG FLT_MIN_EXP DBL_MIN_EXP LDBL_MIN_EXP FLT_MIN_10_EXP DBL_MIN_10_EXP LDBL_MIN_10_EXP FLT_MAX_EXP DBL_MAX_EXP LDBL_MAX_EXP

Page 386: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

386

FLT_MAX_10_EXP DBL_MAX_10_EXP LDBL_MAX_10_EXP FLT_MAX DBL_MAX LDBL_MAX FLT_EPSILON DBL_EPSILON LDBL_EPSILON FLT_MIN DBL_MIN LDBL_MIN

FLT_RADIX je baza “floating-point” modela (pr. 2, 16). FLT_ROUNDS je konstanta koja pokazuje kako se zaokružuje rezultat pri zbrajanju: 0 ako je prema 0, 1 ako je prema najbližoj vrijednosti, 2 ako je prema +∞, 3 ako je –∞, i -1 znači da nije definirano. Ostali makroi daju svojstva tipova: float (FLT_), double (DBL_), i long double (LDBL_). MANT_DIG je broj znamenki (baze FLT_RADIX) u mantisi. DIG daje približnu preciznost u ekvivalentnoj bazi 10. MIN_EXP i MAX_EXP daju maksimalni i minimalni eksponent (MIN_10_EXP i MAX_10_EXP daju njihov ekvivalent u bazi 10). MIN i MAX daju minimalnu i maksimalnu vrijednost realnog broja. EPSILON je razlika između 1.0 i sljedećeg većeg broja.

C 16 <iso646.h> U zaglavlju <iso646.h> definirani su makroi za zamjenu operatora koji možda nisu implementirani na nekom mikro računalima. To se sljedeće definicije:

#define and && #define and_eq &= #define bitand & #define bitor | #define compl ~ #define not ! #define not_eq != #define or || #define or_eq |= #define xor ^ #define xor_eq ^=

C 17 <wchar.h> U zaglavlju <wchar.h> definirane su gotovo sve funkcije za rad s znakovima i stringovima koji su tipa wchar_t. Obično se ASCII znakove naziva prostim znakovima, a znakove tipa wchar_t proširenim znakovima (eng. wide character). Evo kako se inicijalizira prošireni znakovni tip i string:

wchar_t c = L'A'; wchar_t *s = L"Hello";

Pored wchar_t tipa definiran wint_t, integralni tip koji može sadržavati vrijednost wchar_t tipa, te makro WEOF kao oznaka za kraj datoteke.

Operacije sa stringovima proširenih znakova

size_t wcslen(const wchar_t *s); wchar_t *wcscpy(wchar_t *dest, const wchar_t *src); wchar_t *wcscat(wchar_t *dest, const wchar_t *src); wchar_t *wcsncpy(wchar_t *dest, const wchar_t *src, size_t n); wchar_t *wcsncat(wchar_t *dest, const wchar_t *src, size_t n); int wcscmp(const wchar_t *s1, const wchar_t *s2); int wcsncmp(const wchar_t *s1, const wchar_t *s2, size_t n); int wcscoll(const wchar_t *s1, const wchar_t *s2); size_t wcsxfrm(wchar_t *dest, const wchar_t *src, size_t n);

Page 387: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

387

wchar_t *wcschr(const wchar_t *s, wchar_t c); wchar_t *wcsrchr(const wchar_t *s, wchar_t c); wchar_t *wcsstr(const wchar_t *s, const wchar_t *pat); size_t wcsspn(const wchar_t *s, const wchar_t *set); size_t wcscspn(const wchar_t *s, const wchar_t *set); wchar_t *wcspbrk(const wchar_t *s, const wchar_t *set);

Ove funkcije (wcsxxx) su ekvivalentne funkcijama za rad s ASCII stringovima (strxxx), osim što se umjesto pokazivača na char koristi pokazivač na wchar_t, a broj n se intepretira kao broj wchar_t znakova.

Operacije s nizovima proširenih znakova

wchar_t *wmemcpy(wchar_t *dest, const wchar_t *src, size_t n); wchar_t *wmemmove(wchar_t *dest, const wchar_t *src, size_t n); int wmemcmp(const wchar_t *p1, const wchar_t *p2, size_t n); wchar_t *wmemchr(const wchar_t *p, wchar_t c, size_t n); wchar_t *wmemset(wchar_t *p, wchar_t c, size_t n);

Ove funkcije (wmemxxx) su ekvivalentne funkcijama za rad s ASCII nizovima (memxxx), osim što se umjesto pokazivača na char koristi pokazivač na wchar_t, a broj n se intepretira kao broj wchar_t znakova.

Pretvorba stringa proširenih znakova u numeričku vrijednost

long int wcstol(const wchar_t *s, wchar_t **endp, int base) unsigned long int wcstoul(const wchar_t *s, wchar_t **endp, int base); double wcstod(const wchar_t *s, wchar_t **endp);

Ove funkcije (wcsxxx) su ekvivalentne funkcijama za rad s ASCII stringovima (strxxx), osim što se umjesto pokazivača na char koristi pokazivač na wchar_t,

Pretvorba vremena

size_t wcsftime(wchar_t *buf, size_t bufsize, const wchar_t *format, const struct tm *tp);

wcsftime() izvršava operaciju analognu izvršenju strftime().

Rastav stringa proširenih znakova na lekseme

wchar_t *wcstok(wchar_t *s, const wchar_t *sep, wchar_t **state);

Funkcija wcstok() vrši rastav stringa s ne lekseme koji su odvojeni znacima separatora (sep) analogno funkciji strtok(), osim što je temeljni tip wchar_t, i dodan je treći argument state, koji je pokazivač na objekt tipa wchar_t *; wcstok() koristi ovaj objekt za pohranu stanja između uzastopnih poziva funkcije.

Ulazno izlazne operacije s proširenim znakovima

getwchar, getwc, fgetwc

Page 388: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

388

wint_t getwchar(void); wint_t getwc(FILE *fp); wint_t fgetwc(FILE *fp);

Ove funkcije čitaju znakove iz toka fp ili stdin (implicitno se vrši pretvorba multibajt-znakovnih sekvenci, kao da je pozvana funkcija mbrtowc).Ako je kraj datoteke funkcije vraćaju WEOF. Funkcionalnost im je ista kao kod funkcija getchar(), getc(), i fgetc(). putwchar, putwc, fputwc

wint_t putwchar(wchar_t c); wint_t putwc(wchar_t c, FILE *fp); wint_t fputwc(wchar_t c, FILE *fp);

Ove funkcije upisuju wchar_t znakove u toka fp ili stdin (implicitno se vrši pretvorba multibajt-znakovnih sekvenci, kao da je pozvana funkcija mbrtowc). Funkcionalnost im je ista kao kod funkcija putchar(), putc(), i fputc(). wprintf, fwprintf

int wprintf(const wchar_t *, ...); int fwprintf(FILE *fp, const wchar_t *, ...);

Ove funkcije su ekvivalentne funkcijama printf() i fprintf(), osim što se u tok zapisuje multibajt znakovna sekvenca, kao da je pozvan fputwc(). U format stringu specifikatori %c i %s i dalje znače da se očekuje prosti znakovi, a da bi se ispisali prošireni znakovi treba koristiti specifikatore %lc i %ls. wscanf, fwscanf

int wscanf(const wchar_t *, ...); int fwscanf(FILE *fp, const wchar_t *, ...);

Ove funkcije su ekvivalentne funkcijama scanf() i fscanf(), osim što se format string tretira kao niz proširenih znakova, a tok koji se očitava tretira se kao multibajt znakovni niz. U format stringu specifikatori %c, %s i %[ znače da se očekuje prosti znakovi, a da bi se unijeli prošireni znakovi treba koristiti specifikatore %lc , %ls i %l[. fgetws, fputws

wchar_t *fgetws(wchar_t *, int, FILE *fp); int fputws(const wchar_t *, FILE *fp);

Ove funkcije služe čitanju ili zapisu linije teksta analogno funkcijama fgets() i fputs(). ungetwc

wint_t ungetwc(wint_t c, FILE *fp);

Funkcija ungetwc() vraća prošireni znak c natrag u ulazni tok fp, analogno ungetc() funkciji. swprintf, swscanf

Page 389: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

389

int swprintf(wchar_t *buf, size_t bufsize, const wchar_t *format, ...); int swscanf(const wchar_t *buf, const wchar_t *format, ...);

Funkcija swprintf() generira string buf, maksimalne veličine bufsize, a funkcija swscanf() dobavlja podatke iz stringa buf, prema zadanom formatu, analogno funkcijama sprintf() i sscanf(). vwprintf, vfwprintf, vswprintf

int vwprintf(const wchar_t *format, va_list argp); int vfwprintf(FILE *fp, const wchar_t *format, va_list argp); int vswprintf(wchar_t *buf, size_t bufsize, const wchar_t *format, va_list argp);

Ove funkcije su analogne funkcijama vprintf(), vfprintf(), and vsprintf(). Argument vswprint() bufsize omogućuje kontrolu maksimalne duljine stringa kao kod swprintf(). fwide

int fwide(FILE *fp, int mode);

Svaki tok ima "orijentaciju" koja pokazuje da li se on koristi s normalnim ili multibajt znakovima (pomoću funkcija iz ove sekcije) Početno je tok “neorijentiran”, ali se nakon prve upotrebe prebacuje u "bajt-orijentirani" ili "prošireno-orijentirani" mod obrade teksta. Funkcijom fwide() može se postaviti orijentacija toka fp, tako da se argument mode postavi na vrijednost veću od nule za "prošireno-orijentirani" mod, ili na vrijednost manju od nule za "bajt-orijentirani" mod. Funkcija vraća vrijednost trenutne orijentacije (0 znači da je tok neorijentiran).

Dodatne pretvorbe

btowc, wctob

wint_t btowc(int c); int wctob(wint_t wc);

Funkcija btowc() pretvara normalni znak c u prošireni znak. Funkcija wctob() pretvara prošireni znak wc u normalni znak. Vraća znak ili EOF ako pretvorba nije moguća. mbrlen

size_t mbrlen(const char *s, size_t n, mbstate_t *state);

Funkcija mbrlen() je slična funkciji mblen(). Njome se može dobiti i duljinu prekinute multibajt sekvence. Početni se dio referira u objektu state, a na preostali dio pokazuje pokazivač s. Vraća vrijednost kao funkcija mbrtowc(). mbrtowc, wcrtomb

size_t mbrtowc(wchar_t *dest, const char *src, size_t n, mbstate_t *state); size_t wcrtomb(char *dest, wchar_t src, mbstate_t *state);

Page 390: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

390

Ove su funkcije slične funkcijama mbtowc() i wctomb(), osim što mogu obraditi i dio multibajt sekvence koja je prekinuta, uz uvjet da je stanje pretvorbe zabilježeno u objektu na kojeg pokazuje state. Funkcija mbrtowc() pretvara multibajt sekvencu src u proširen znak u *dest i vraća broj bajta na koje pokazuje src koji tvore ispravnu multibajt sekvencu, ili 0 ako src==NULL, ili -1 ako nastane greška, ili -2 ako nije pronađena kompletna multbajt sekvenca (upotrebljena za *state). Funkcija wcrtomb() pretvara prošireni znak src u multibajt sekvencu dest i vraća broj bajta zapisanih u dest, ili -1 ako nastane greška. mbsrtowcs, wcsrtombs

size_t mbsrtowcs(wchar_t *dest, const char **srcp, size_t n, mbstate_t *state); size_t wcsrtombs(char *dest, const wchar_t **srcp, size_t n, mbstate_t *state);

Ove su funkcije slične funkcijama mbtowcs() i wctombs(), osim što mogu obraditi i dio multibajt sekvence koja je prekinuta, uz uvjet da je stanje pretvorbe zabilježeno u objektu na kojeg pokazuje state. String koji se pretvara prenosi se po referenci *srcp, kako bi se mogao ažurirati da pokazuje na preostali dio nepretvorenog stringa. Ako je broj n nedovoljan (kao broj proširenih znakova za mbsrtowcs() ili bajta za wcsrtombs()) za kapacitet odredišnog stringa rezultata, *srcp se postavlja tako da pokazuje na nepretvoreni ulaz, a *state se ažurira da odrazi prekinuto stanje pretvorbe. mbsinit

int mbsinit(const mbstate_t *p);

Funkcija mbsinit() vraća nenultu vrijednost ako je objekt stanja na kojeg pokazuje p u početnom stanju, ili ako je p==NULL.

C 18 <wctype.h> U zaglavlju <wctype.h> deklarirano je nekoliko funkcija za klasificiranje i pretvorbu znakova tipa wchar_t, analognih funkcijama iz <ctype.h>.

Klasifikacija proširenih znakova

int iswupper(wint_t c); int iswlower(wint_t c); int iswalpha(wint_t c); int iswdigit(wint_t c); int iswalnum(wint_t c); int iswxdigit(wint_t c); int iswspace(wint_t c); int iswpunct(wint_t c); int iswprint(wint_t c); int iswgraph(wint_t c); int iswcntrl(wint_t c);

Funkcije (iswxxx) su analogne funkcijana (isxxx), osim što je argument ovih funkcija tipa wint_t.

Page 391: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

391

Dodatne funkcije za klasifikacija proširenih znakova

wctype_t wctype(const char *classname); int iswctype(wint_t c, wctype_t classtok);

Funkcija wctype() prihvaća argument classname u kojem se indicira klasifikacija i vraća token tipa wctype_t (definiran je u <wctype.h>). Funkcija wctype() prihvaća stringove: "upper", "lower", "alpha", "digit", "alnum", "xdigit", "space", "punct", "print", "graph", i "cntrl" (koji odgovaraju predefiniranoj klasifikaciji ), plus korisnički definirani string za klasifikaciju. Funkcija iswctype() prihvaća argumente znak c i token classtok koji je prethodno dobiven funkcijom wctype(), te vraće nenultu vrijednost ako znak ne pripada klasifikaciji

Funkcije za pretvorbu proširenih znakova i stringova

wint_t towupper(wint_t c); wint_t towlower(wint_t c);

Ove funkcije su ekvivalentne funkcijana toupper() i tolower(), za normalne znakove.

wctrans_t wctrans(const char *convname); wint_t towctrans(wint_t c, wctrans_t convtok);

Funkcija wctrans() prihvaća argument convname u kojem se indicira znakovna pretvorba i vraća token tipa wctrans_t koji se koristi za izvršenje pretvorbe (wctrans_t je definiran u <wctype.h>). Funkcija towctrans() vrši pretvorbu proširenog znaka c prema tokenu convtok koji je prethodno dobiven funkcijom wctrans(), i vraća pretvoreni prošireni znak. Funkcija wctrans() prihvaća stringove "toupper" i "tolower" (koji označavaju predefinirani način pretvorbe), plus korisnički definirani string za pretvorbu.

Page 392: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

392

Index

#define, 47, 186 #elif, 190 #else, 190 #endif, 190 #if, 190 #ifdef, 190 #ifndef, 190 #include, 186 #pragma pack, 177 #undef, 189 <assert.h>, 380 <ctype.h>, 368 <errno.h>, 381 <float.h>, 381 <iso646.h>, 382 <limits.h>, 381 <locale.h>, 378 <math.h>, 373 <setjmp.h>, 377 <signal.h>, 376 <stdarg.h>, 379 <stddef.h>, 380 <stdio.h>, 357 <stdlib.h>, 369 <string.h>, 365 <time.h>, 374 <wchar.h>, 382 <wctype.h>, 386 abort(), 372 abs(), labs(), 372 adresni operator &, 50 ADT, 208 apstrakcija, 12 apstraktni dinamički tipovi, 208 argumenti komandne linije, 157 aritmetički izrazi, 72 ASCII, 39 asctime(), 374 asocijativnost i prioritet operatora, 73 assert, 210, 380 atexit(), 371 atof(), 370 atoi(), atol(), 370 automatske varijable, 114 bajt, 7

biblioteke funkcija, 120 binarne i tekstualne datoteke, 193 binarni brojevni sustav, 18 binarno pretraživanje, 229 binarno stablo, 278, 279 bit, 7 bit polja strukture, 179 bitznačajni operatori, 75 BNF notacija, 289 Booleova logika, 16 break, 100 brojevni sustavi, 17 bsearch, 371 bsearch(), 232 calloc(), 369 centralna procesorska jedinica, 8 char, 43 clearerr(), 364 clock(), 376 continue, 100 COUNTER, 209 ctime(), 374 De Morganov teorem, 16 decimalni brojevni sustav, 17 deklaracija funkcije, 27 deklaracija varijable, 44 dereferencirani pokazivač, 53 difftime(), 376 digitalna logika, 14 dinamičke matrice, 167 dinamičko alociranje memorije, 159 disjunkcija, 15 div(), ldiv(), 372 doseg identifikatora, 113 double, 43 do-while petlja, 99 dvostruko vezana lista, 266 egzistencijalni kvantifikator, 14 eksterne varijable, 117 ekvivalencija, 15 enum, 180 errno, 381 exit(), 371 fclose(), 195, 358 feof(), 363

Page 393: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

393

ferror(), 364 fflush(), 195, 358 fgetc(), 358 fgetpos(), fsetpos(), 363 fgets(), 361 FIFO, 218 FILE, 192 float, 43 fopen(), 194, 357 for petlja, 99 fprintf(), 359 fputc(), 358 fputs(), 362 fread(), 201, 362 free(), 369 freopen(), 358 fscanf(), 360 fseek(), 204, 362 ftell(), 203, 362 funkcije, 64 fwrite(), 201, 362 generator slučajnih brojeva, 156 getchar(), getc(), 358 getenv, 371 gets(), 361 globalne varijable, 116 gmtime(), 374 goto, 90 hash tablica, 316 heksadecimalni brojevni sustav, 20 identifikatori, 82 if, 91 if-else, 92 implikacija, 15 inicijalizacija varijable, 55 INORDER, 284 int, 43 Integrirana razvojna okolina, 22, 28 intepreter, 22 interpreter prefiksnih izraza, 288 invertor, 16 izjavna logika, 14 izlazne jedinice, 8 izrazi, 72 izvedivi program, 22 izvorni program, 6 jednodimenzionalni nizovi, 103 klasifikacija znakova, 368 kodiranje, 34 kodiranje realnih brojeva, 37 kodiranje znakova, 39 komentar, 83 kompilator, 21

komplement dvojke, 35 komplement jedinice, 35 konjunkcija, 15 korijen stabla, 279 kule Hanoia, 226 leksemi, 84 LIFO, 213 linker, 22 lista stringova, 258 listovi stabla, 279 literalne konstante, 82 LL(1) analiza, 293 localeconv(), 378 localtime(), 374 lokalne varijable, 114 long, 43 longjmp(), 377 LSB, 7 makefile, 33 makro, 187 malloc(), 369 memchr(), 367 memcmp(), 367 memcpy(), 367 memmove(), 367 memset(), 368 merge sort, 240 metajezik, 84 mktime(), 376 MSB, 7 multibajt znakovne sekvence, 372 naredbe, 88 negacija, 15 neterminalni simboli, 85 nibl, 7 nizovi, 103 nizovi znakova, 146 obilazak stabla, 283 oktalni brojevnim sustav, 20 općenito stablo, 278 operativni sustav, 9 operator indirekcije, 51 parser, 294 perror(), 364 petlje, 97 pobrojani tip, 180 podijeli pa vladaj, 228 pointeri, 51 pokazivači, 51 postfiksna notacija izraza, 215 POSTORDER, 284 povezivač, 22 poziv funkcije, 26

Page 394: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

394

pozivna funkcija, 26 pozvana funkcija, 26 predikat, 14 PREORDER, 284 pretprocesor, 186 pretvorba tipova, 79 pretvorba znaka, 369 printf(), 48, 359 privremene datoteke, 206 produkcije C-jezika, 85 program, 6 proste naredbe, 88 prototip funkcije, 27 punjač, 22 putchar(), putc(), 358 puts(), 362 qsort(), 371 quicksort, 243 računalo, 6 Računarski algoritam, 9 radna memorija, 8, 41 raise(), 376 RAM, 8 rand(), 156, 370 realloc(), 369 red (queue), 218 referenca, 44 rekurzivne funkcije, 223 rekurzivno silazni parser, 294 relacijski i logički izrazi, 74 remove(), 206, 365 rename(), 206, 365 repna rekurzija, 231 rewind(), 203, 362 rezidentni programi, 9 riječ, 7 rječnici, 314 ROM, 8 samoreferentne strukture, 247 scanf(), 53, 360 selekcijsko sortiranje, 237 semantičke akcije, 297 separatori, 82 setbuf(), setvbuf(), 363 setjmp(), 377 setlocale(), 378 short, 43 signal(), 377 sintaksa i leksika, 82 sintaktički analizator, 294 sizeof, 46 sklop-I, 16 sklop-ILI, 16

složeni operatori, 77 složenost algoritama, 233 sortirane liste, 258 sortiranje, 237 sortiranje umetanjem, 239 sprintf(), 364 srand(), 156, 370 sscanf(), 364 stablo (tree), 278 statičke globalne varijable, 118 stderr, 192 stdin, 192 stdout, 192 Stog ADT (stack), 213 strcmp(), 150 strcpy(), 149 strdup(), 162 strftime(), 375 string, 146 string operator, 188 strlen(), 147 strojni jezik, 21 strtod(), 370 strtol(), strtoul(), 370 struktura, 171 switch-case, 95 system(), 372 tablice simbola, 314 terminalni simboli, 84 ternarni uvjetni izraz, 78 Tic-Tac-Toe, 121 tijelo funkcije, 26, 66 time(), 374 tipovi podataka, 43 tmpfile(), 365 tmpnam(), 365 točka operator, 172 tokeni, 84 tokovi, 192 typedef, 81 ulazne jedinice, 8 ungetc(), 362 union, 178 univerzalni kvantifikator, 14 unsigned, 43 unutarnji čvorovi - podstabla, 279 uvlačenje redova, 93 va_arg, 379 va_end, 379 va_start, 379 vanjska memorija, 8 vanjski čvorovi, 279 veliki-O notacija, 233

Page 395: PROGRAMIRANJE C JEZIKOM -  · PDF file3.2 Prvi program u C jeziku ... 5 Uvod u programiranje C jezikom.....55 5.1 Postupak izrade programa

395

vezana lista, 248 Visual Studio, 28 višedimenzionalni nizovi, 111 void, 53 void funkcije, 68 vprintf(), vfprintf(), vsprintf(), 364 vrijeme i datum, 181

while petlja, 97 zaglavlje funkcije, 26, 66 zakon asocijacije, 16 zakon distribucije, 16 zakon idempotentnosti, 16 zakon komutacije, 15 znakovni ulaz/izlaz, 198