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

Size: px
Start display at page:

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

Transcription

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

2 Sadržaj 1 Uvod Matematički i elektronički temelji računarstva Izjavna i digitalna logika Brojevni sustavi i računska sposobnost računala Izrada prvog C programa Strojni, asemblerski i viši programski jezici Prvi program u C jeziku Struktura i kompiliranje C programa Integrirana razvojna okolina (IDE) Usmjeravanje procesa kompiliranja programom nmake Kodiranje i tipovi podataka Kodiranje i zapis podataka Memorija Prosti tipovi podataka Direktiva #define Specifikatori printf funkcije Pristup podacima pomoću pokazivača Unos podataka u memoriju računala Inicijalizacija varijabli Uvod u programiranje C jezikom Postupak izrade programa Algoritamska struktura C programa? Funkcije C jezika Zaključak Izrazi i sintaksa C jezika Izrazi Automatska i explicitna pretvorba tipova Definiranje sinonima tipa pomoću typedef Formalni zapis sintakse C-jezika Proste i strukturalne naredbe C jezika Proste naredbe Strukturalne naredbe Nizovi Jednodimenzionalni nizovi Prijenos nizova u funkciju Višedimenzionalni nizovi Blokovi, moduli i dekompozicija programa Blokovska struktura programa Funkcionalna dekompozicija programa "od vrha prema dolje" Zaključak Rad s pokazivačima

3 10.1 Tip pokazivača Operacije s pokazivačima Pokazivači kao argumenti funkcije Pokazivači i nizovi Pokazivači i argumenti funkcije tipa niza Patrametri funkcije tipa void pokazivača Pokazivači na funkcije Kompleksnost deklaracija Polimorfne funkcije Zaključak Nizovi znakova - string Definicija stringa Standardne funkcije za rad sa stringovima Ulazno-izlazne operacije sa stringovima Korisnički definirane ulazne operacije sa stringovima Pretvorba stringa u numeričku vrijednost Nizovi stringova Generator slučajnih brojeva Argumenti komandne linije operativnog sustava Dinamičko alociranje memorije Funkcije za dinamičko alociranje memorije Kako se vrši alociranje memorije Alociranje višedimenzionalnih nizova Standardne funkcije za brzi pristup memoriji Korisnički definirane strukture podataka Struktura (struct) Union zajednički memorijski objekt za različite tipova podataka Bit-polja Pobrojanji tip (enum) Strukture i funkcije za očitanje vremena Leksički pretprocesor Direktiva #include Direktiva #define za makro-supstitucije String operatori # i ## Direktiva #undef Direktive za uvjetno kompiliranje Rad s datotekama i tokovima Ulazno-izlazni tokovi Binarne i tekstualne datoteke Pristup datotekama Formatirano pisanje podataka u datoteku Formatirano čitanje podataka iz datoteke Znakovni ulaz/izlaz Direktni ulaz/izlaz za memorijske objekte Sekvencijani i proizvoljni pristup datotekama Funkcije za održavanje datoteka Apstraktni tipovi podataka - ADT

4 16.1 Koncept apstraktnog dinamičkog tipa podataka Stog i STACK ADT Primjena stoga za proračun izraza postfiksne notacije Red i QUEUE ADT Zaključak Rekurzija i složenost algoritama Rekurzivne funkcije Matematička indukcija Kule Hanoja Metoda - podijeli pa vladaj (Divide and Conquer) Pretvorba rekurzije u iteraciju Standardna bsearch() funkcija Složenost algoritama - "Veliki - O" notacija Sortiranje Zaključak Samoreferentne strukture i liste Samoreferentne strukture i lista Operacije s vezanom listom Što može biti element liste Lista sa sortiranim redoslijedom elemenata Implementacija ADT STACK pomoću linearne liste Implementacija ADT QUEUE pomoću vezane liste Dvostruko vezana lista Generički dvostrani red - ADT DEQUEUE Zaključak Razgranate strukture - stabla Definicija stabla Binarno stablo Interpreter prefiksnih izraza Stabla s proizvoljnim brojem grana Prioritetni redovi i hrpe Zaključak Strukture za brzo traženje podataka Tablice simbola i rječnici Hash tablica BST - binarno stablo traženja Crveno-crna stabla Literatura Dodatak Dodatak A - Elementi dijagrama toka Dodatak B - Gramatika C jezika Dodatak C - Standardna biblioteka C jezika Index

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 5

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 Nibl Bajt Riječ je naziv za binarnu znamenku je naziv za skupinu od četiri bita (eng. nibble) s kojom se operira kao s cjelinom. ili oktet je naziv za skupinu od osam bita (eng. byte) s kojom se operira kao s cjelinom. 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 položaj bita 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) = 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 2 n 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. 6

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 2 n 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. 7

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, 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 8

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 uzmi najmanji od tri ključa gurni ključ u bravu i lagano ga okreni na desno podigni vrata prtljažnika 9

10 1.2. izvadi rezervni točak podigni tapetu ako je točak pričvršćen vijkom onda odvij vijak izvadi točak 1.3. uzmi kutiju s alatom 1.4. ispitaj razinu tlaka izvadi mjerač tlaka iz kutije alata postavi ga na zračnicu točka očitaj razinu tlaka 1.5. ako je tlak manji od 0,1 onda provjeri da li je guma probušena ako je guma probušena onda odnesi točak na popravak inače, ako je tlak manji od 1,6 onda otvori prednji poklopac motora uzmi zračnu pumpu 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. 10

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 11

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. 12

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). 13

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 14

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 15

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: bit = A = B A & B = = 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 bitznač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 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 Binarni brojevni sustav Sasvim općenito, numerička vrijednost broja Z, koji je u pozicionoj notaciji zapisan znamenkama: z n-1...z 1 z 0, u brojevnom sustavu baze x, računa se prema izrazu: (... ) n 1 Z = z z z = z x n x i i= 0 i Decimalni brojevni sustav je definiran bazom x=10 i znamenkama z i ε0,1,2,3,4,5,6,7,8,9, primjerice iznos broja 765 je jednak

17 Binarni brojevni sustav je definiran bazom x=2 i binarnim znamenkama z i 0,1.Primjerice, iznos binarnog broja 1011 odgovara iznosu broja 11 u decimalnom sustavu, jer je (1011) 2 = = = (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 = = = = = = 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 Tablica 2.2. Tablica istinitosti za polu-zbrajalo A B Donos Zbroj prijenos 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. 17

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 = (5) 101 (5) 101 (5) 1010 (10) (25) (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. 18

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 odgovara 3 * 2 = SHL odgovara 3 * 4 = SHR odgovara 14 / 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: , a baza brojevnog sustava je x=2 3 =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 bit bit znamenke oktalnog brojevnog sustava To omogućuje pregledniji zapis većih binarnih nizova, primjerice = , a koristi se pravilo grupiranja po 3 bita: 100=4, 100=4, 010=2, 111=7. U heksadecimalnom brojevnom sustavu koristi se 16 znamenki: ABCDEF, 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 bit bit bit A B C D E F heksadecimalne znamenke To omogućava pregledniji zapis većih binarnih nizova, primjerice: = 917E 16, 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. 19

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 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 20

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!. 21

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 c:> cl hello.c c:> hello Hello world! - poziv editora edit ( je tipka enter) i unos izvornog programa u datoteku hello.c - poziv kompilatora (Microsoft-program cl.exe) koji stvara izvršnu datoteku hello.exe - komanda za izvršenje programa hello.exe - 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 22

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"); Return 0; 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. 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", 23

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 for 80x86 Copyright (C) Microsoft Corp 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; 24

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). 25

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". 26

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

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 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). 28

29 Slika 3.4 Dijalog za postavljanje novog projekta Slika 3.5 Dijalog za postavljanje tipa Win32-Console projekta 29

30 Slika 3.6 Dijalog koji izvještava o postavkama novog projekta Slika 3.7 Izgled radne okoline nakon formiranja novog projekta hello 30

31 Slika 3.9 Dijalog za umetanja datoteka u projekt Slika 3.10 Izgled IDE s aktivnim editorom 31

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. 32

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 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 u C jeziku su: decimalna konstanta 27 oktalna konstanta 033 heksadecimalna konstanta 0x1B 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 33

34 brojevni sustav. Puni komplement n-znamenkastog broja N x, u brojevnom sustavu baze x matematički se definira izrazom: N = x N x n x primjerice, u decimalnom sustavu komplement troznamenkastog broja 733 je = 277. Ako se n-znamenkasti broj i njegov komplement zbroje vrijedi da će n znamenki biti jednako nuli. U prijašnjem primjeru =1000, dakle tri znamenke su jednake nuli. U binarnom se sustavu puni komplement naziva komplement dvojke i vrijedi: n 2 2 N = 2 N Komplement dvojke se koristi za označavanje negativnih brojeva. Primjerice, komplement dvojke broja +1 za n=4 iznosi: 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) 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 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 2 n-1-1. Zbog toga, najveći pozitivni broj koji se može predstaviti u sustavu komplementa dvojke iznosi: max_int = ( ) = 2 n-1-1, a najveći iznos negativnog broja iznosi: min_int = ( ) = -2 n-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 n 2 2 = 2 N 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 On služi za jednostavno izračunavanje punog komplementa, jer vrijedi: N = N

35 Puni komplement se izračunava tako da se običnom komplementu pribroji jedinica, primjerice, komplement dvojke broja 6 u 8-bitnoj notaciji iznosi: (+6) (obični komplement od +6) 1 (dodaj 1) (-6 u komplementu dvojke) Izračunajmo puni komplement od 0: (0) (komplement od 0) 1 (dodaj 1) (-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 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, 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, , ili , e-2 ili E-2-0, e2 ili E2 ili e+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 10 eksponent 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 35

36 označavanje cijelih vrijednosti, a n znamenki za označavanje razlomljenih vrijednosti (po bazi 2: 1/2, 1/4, 1/8 itd.) glasi: b b... b b b 2... b N 1 N n, a iznos mu se računa prema izrazu: N N. n2 = bn b0 2 + b b b n 2, bi 2 n ( 0,1) Ako se ovaj broj pomnoži s 2 n 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 = ( 1) s m2 e 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: xxxxxxxxx 2 e 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.xxxxxxxxx e-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

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 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 Emax Emin 0 0 minreal (za nenorm.) (-1) s (-1) s minreal (za norm.) (-1) s (-1) s maxreal (-1) s (-1) s 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 E min je iskorištena za kodiranje nule, a vrijednost E max 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. 37

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 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 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 dec hex ASCII HR-ASCII 64 Ž 91 5B [ Š 92 5C \ Đ 93 5D ] Ć 94 5E ^ Č ` ž 123 7B š 124 7C đ 125 7D ć 126 7E ~ č IBM EBCDIC-852 standard Dec hex EBCDIC A6 Ž 230 E6 Š 209 D1 Đ 172 AC Ć 143 8F Č 167 A7 ž 231 E7 š 208 D0 đ ć 159 9F č Tablica 4.4. HR_ASCII i EBCDIC-852 standard 38

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 zapadnoevropskih 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 39

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 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 > x-y = x-y < 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 40

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 = 2 velič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čkologičke operacije. 5. Memorijski objekti, s kojima procesor operira kao s cjelinom, nazivaju se varijable. 41

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 Re( z) Im( z) = + j x x x 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 znakovni tip cjelobrojni tip kardinalni tip realni tip (jednostruk i format) realni tip (dvostruki format) Oznaka u C jeziku [signed] char Unsigned char [signed] int [signed] short [signed] long unsigned [int] unsigned short unsigned long float double Interval vrijednosti min ± e-38 maks ± e+38 min ± e-308 maks ± e+308 logički tip različito od nule - zauzeće Memorije 1 bajt 1 bajt 4 bajta 2 bajta 4 bajta 4 bajta 2 bajta 4 bajta 4 bajta 8 bajta 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 42

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 = 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 r 2 π. Ovu formulu se ne može izravno primijeniti jer u C jeziku ne postoji operator potenciranja. 43

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 44

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 'A' 'a' ili '\035' '\x29' int '\n' Int x9c unsigned 156U 0234U 0x9cU float 15.6F 1.56e1F double E1L znakovna konstanata A znakovna konstanata a znakovna konstanata 35 u oktalnoj notaciji znakovna konstanata 29 u heksadecimalnoj notaciji znak za novu liniju decimalna notacija oktalna notacija cjelobrojne konstante heksadecimalna notacija Decimalno oktalno (prefiks U određuje kardinalni broj) heksadecimalno realni broj jednostruki format određen primjenom sufiksa F ili f 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; int x; /* deklaracija varijable c tipa char*/ /* 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 45

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 to ima efekt da kada se u tekstu programa, koji slijedi iza ove direktive, zapiše PI, vrijedi kao da je zapisano 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 #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 = Kasnije će biti detaljnije pojašnjen rad s pretprocesorskim direktivama. 46

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 #include #include <stdio.h> <limits.h> <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 short int int long int float e e+038 double e e+308 long double e e+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 47

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 l L Pripadni argument tipa int tretira se kao short int ili unsigned short int. Pripadni argument je long int ili unsigned long int. 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() 48

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, ab AB xab e Hello, world! 03, , 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 49

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 */ 50

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 0x09A8 0x09AC 0x09B x1000 0x1004 sadržaj memorije x09AC... ime varijable... 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; 51

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. 52

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 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 53

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: Z 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: 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. 54

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 o definiranje postupaka obrade (apstraktni i programski algoritmi), 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). 55

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. 56

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: 1 n! = n k k = 1 za n = 0 za n > 0 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; 57

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 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: 58

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! 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. 59

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: Ako korisnik unese vrijednost 18 dobije se rezultat: Vrijednost 18! iznosi: 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 ). 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. 60

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 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() 61

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 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 62

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 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() 63

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;... 64

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 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(). 65

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; 66

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 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 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"); 67

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 Funkcija za proračun e x Zadatak je napisati funkciju kojom se približno određuje vrijednost funkcije e x (e = ) 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: e x = 1 + x/1! + x 2 /2! + x 3 /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 = trebat će zbrojiti 10 članova reda. Specifikacija funkcije: double my_exp(double x, double eps); Parametri: Rezultat: x - vrijednost za koju se računa e x, tipa double eps - zadana preciznost proračuna, tipa double vrijednost tipa double, jednaka vrijednosti e x Algoritam: Razvoj u red funkcije e x 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; 68

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: e^ = ; (tocno: ) c:>ex.exe Enter x and the eps: e^ = ; (tocno: ) 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). 69

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, ifnaredba, 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 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. 70

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? 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 71

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 a 3 se realizira izrazom a*a*a a -3 se realizira izrazom 1/(a*a*a) 72

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 x y. Ova funkcija je deklarirana u <math.h> 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 Aritmetički operatori a + b < max max == 0 && a == b niži prioritet <, <=, >, >= ==,!= && 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) 73

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 int isupper(int c); int islower(int c); int isalpha(int c); int iscntrl(int c); int isalnum(int c); int isdigit(int c); int isxdigit(int c); int isgraph(int c); int isprint(int c); int ispunct(int c); int isspace(int c); vraća vrijednost različitu od nule (true), ako je znak c veliko slovo malo slovo veliko ili malo slovo kontrolni znak slovo ili znamenka decimalna znamenka heksadecimalna znamanka tiskani znak osim razmaka tiskani znak uključujući razmak tiskani znak osim razmaka, slova ili znamanke 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" Bitznačajni operatori U C jeziku se koristi 6 bitznačajnih operatora, koji se mogu primijeniti na integralne tipove (char, short, int i long). 74

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; n & x000F 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; n x000F 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 */ 75

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: ili Otkucaj cijeli broj: 67 Binarni kod je: 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 izraz 1 i izraz 2 neki izrazi, tada izraz 1 op= izraz 2 76

77 je ekvivalentno izraz 1 = (izraz 1 ) op (izraz 2 ) pri tome izraz 1 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 znači a ne x *= y + 1; x = x * (y + 1); 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 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: 77

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 u izrazu: int j=5, k=7; float x=2.1; 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. 78

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= 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= 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) 79

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: 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 Stoga, ako bi se zbrojile dvije short int kodirane vrijednosti iznosa rezultat bi bio veći od Operator (long) ispred jednog operanda osigurava da će se zbrajanje izvršiti na način kao da su operandi tipa long. 80

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"). 81

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 i, sum sadrže vrijednosti nekog tipa Izrazi zapis proračuna vrijednosti kombiniranjem sum + i varijabli, funkcija, konstanti i operatora 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. 82

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; 83

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 (Simbol opt ili Simbol opt ). Primjerice, zapis produkcije if-else iskaza glasi IskazIf : if (Izraz) Iskaz if (Izraz) Iskaz else Iskaz Ovo se pravilo može se napisati i na sljedeći način: IskazIf : if (Izraz) Iskaz ElseIskaz opt ElseIskaz : else Iskaz 84

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 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 : heksa_znamenka : A B C D E F a b c d e f. oktalna_znamenka : 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 _ 85

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. 86

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 : izraz opt ; 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 */ 87

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 ; 88

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 gotonaredba, 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 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-deklaracija opt niz-naredbi opt 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;... 89

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 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. 90

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 91

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. 92

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; 93

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 switchcase 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 N a. Ako izraz sel ima vrijednost konstante b izvršava se naredba N b. Ako izraz sel ima vrijednost iz skupa z1,z2,..z3 izvršava se naredba N z, a ako vrijednost selektorskog izraza nije iz skupa a,b,..,z1,z2,..z3 izvršava se naredba N x. 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; 94

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: 95

96 switch-case-naredba: switch ( izraz ) niz-deklaracija opt niz-case-naredbi default-naredba opt niz-case-naredbi: case-naredba niz-case-naredbi case-naredba case-naredba: case konstanti-izraz : niz-naredbi opt prekid opt default-naredba: default: niz-naredbi opt prekid: break ; 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. 96

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 whilepetlje u obliku: iniciraj_kontrolne_varijable while ( izraz_s_kontrolnim_varijablama ) niz_naredbi opt naredba_promjene_vrijednosti_kontrolnih_varijabli opt niz_naredbi opt 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 ( izraz opt ; izraz opt ; izraz opt ) naredba 97

98 Primjerice, segment programa u kojem se računa n faktorijela, se može zapisati pomoću forpetlje 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 izraz opt ; while ( izraz opt ) naredba izraz opt ; 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 ) ; 98

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 L1: početak petlje... C jezik početak petlje... 99

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 L1: početak petlje... if (L) goto L1;... kraj petlje C jezik 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". 100

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; 101

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: a i označava i-ti element niza u matematici a[i] označava i-ti element niza u C jeziku i=0,1, Jednodimenzionalni nizovi 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. 102

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 4 bajta zauzima 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++) 103

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: Suma unesenih brojeva je element je najveci (vrijednost mu je ) 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; 104

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: 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 105

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: 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"); 106

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 ***** 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 107

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. 108

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) 109

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. 110

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. 111

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 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, 112

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) int i; double ex = 1.0, preth_ex = 0.0, ;... doseg ex return ex; doseg x 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 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> 113

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; 114

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 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; 115

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); 116

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) 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); 117

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; /* */ 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); 118

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 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). 119

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. o X o X o X o X Označavanje pozicije kvadratića na igračkoj ploči 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 120

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; 121

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 \n"); printf("\t \n"); printf("\t \n"); printf("\t \n"); printf("\t \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()); 122

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: 123

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 124

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)) 125

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; 126

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

128 TIc-tac-Toe main() Upute OcistiPlocu PostaviPrvogIgraca IgrajIgru Ponovi Igru NacrtajPlocu DobaviPotez PostajeDobitnik PotezKompjutera PotezCovjeka 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. 128

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 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 = /* 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 0x0F x1000 0x x09AC 0x0F18 sum arr[0] arr[1] arr[2]... p q Slika 10.1 Adresa i vrijednost varijabli 129

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 */ 130

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. 131

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= 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: 132

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: tada vrijedi: p = array; 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 133

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 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); 134

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; 135

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; 136

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 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)); 137

138 se može koristiti za ispis vrijednosti matematičkih funkcija. Print(sin, 3.1); Print(cos, 1.7) /* ispisuje se vrijednost sinusne funkcije za vrijednost argumenta 3.1*/ /* 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 138

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 139

140 T (*x)[]; T x(); T *x() T (*x()); T (*x)(); T (*x[n])(); pokazivač na niz objekata tipa T funkcija koja vraća objekt tipa T funkcija koja vraća pokazivač na objekt tipa T funkcija koja vraća pokazivač na objekt tipa T pokazivač na funkciju koja vraća objekt tipa T 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...*/ 140

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 */ 141

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); 142

143 for(i=0; i <numel; i++) printf(" %d", A[i]); printf("\n"); return 0; Nakon izvršenja dobije se ispis: Nakon sortiranja 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 *)) 143

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: 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() 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 144

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. 145

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 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 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; 146

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!"; 147

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 =" ABCDEF"; /* kompilator inicijalizira pokazivač */ /* na adresu od " ABCDEF"; */ 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", " ABCDEF"[n]); ispisuje n-ti znak stringa " ABCDEF" 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. 148

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: 149

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++; 150

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 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 */ 151

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() 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: 152

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 153

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,... ); 154

155 koja ima isto djelovanje kao printf() funkcija, ali se ispis vrši u string, a ne na standardni izlaz 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 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*/ 155

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

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; x i = (a * x i-1 + b) % c u kojoj se vrijednost i-tog broja dobije iz prethodno izračunate vrijednosti (x i-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= , b=12345 i c= 2 32, 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 */ seed = seed * ; 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 * ; Uočite da se ne vrši operacija mod 2 32, jer maksimalna veličina unsigned long je , 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 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: 157

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 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: jer je: Ime programa: C:\CMDLINE.EXE 1. argument: Hello 2. argument: World 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: 158

159 C:>cmdsum 6.5 4e-2 program treba dati ispis Suma: = /* 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; 159

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 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)); 160

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. 161

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 ; 162

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 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: 163

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 - 164

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 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

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"; 166

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 **. 167

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; 168

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) 169

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 ; 170

171 /* definiraj potrebne funkcije */ Dobije se ispis: 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) 171

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;... ; 172

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; Slika 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 173

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 ; 174

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; 175

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: 176

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 ); 177

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 raspored elemenata u memoriji računala elementima se pristupa pomoću indeksa niza u nizu jedan do drugog Tablica Usporedba nizova i struktura članovima se pristupa pomoću imena u nizu, ali ne nužno jedan do drugog 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. 178

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 : 00406B BA0 Adresa od i : 00406B BA1 Adresa od d : 00406B BA3 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. 179

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 = 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 = 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; 180

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. 181

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 primjerice, deklaracija_varijable: enum ime_tipa lista_varijabli; 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: 182

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 ). 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 */ int tm_min; /* minute */ int tm_hour; /* sat */ int tm_mday; /* dan */ int tm_mon; /* mjesec */ int tm_year; /* broj godina nakon 1900 */ int tm_wday; /* dan u sedmici 0..6 */ int tm_yday; /* dan u godini */ 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 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); 183

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: 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 :21 tada se može koristiti sljedeće iskaze: /* ispisuje datum i vrijeme u formatu :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 184

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: : :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 185

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); 186

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 sekundi Funkcija sin se izvrsava u e-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%. 187

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: 188

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 Y = KVADRAT(a+b) Y = POSTOTAK(a+b,c+d) rezultira kodom y = ((a+b)*(a+b)) y = ((a+b)*100.0/(c+d)) 189

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 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: 190

191 sin = cos = 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]= 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 */

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 192

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. 193

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. 194

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 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 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 195

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 mod "r" "w" "a" "r+" značenje Otvori datoteku za čitanje (eng. read). Ako datoteka ne postoji fopen() vraća NULL. 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). 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. 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+" "b" 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.. 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 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". 196

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 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 */ 197

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 Otipkaj ime datoteke brojevi.txt data[0] = data[1] = data[2] = data[3] =

199 data[4] = 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: /* 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: , , , ,

200 Uočite da je posljednji broj u datoteci pročitan kao 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 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 */ 200

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 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 201

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 ) ) 202

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) 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. 203

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*/ 204

205 for (i = 0; i < SIZE; i++) printf("%d\t%d\n", niz1[i], niz2[i]); return(0); Dobije se ispis: 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

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: 206

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); 207

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 208

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. 209

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. 210

211 Objekt apstrakcije ADT - model podataka i operacija kojima se opisuje objekt apstrakcije Izrada programa Izvršni program 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 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). 211

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 ) 212

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> #include <stdlib.h> #include <assert.h> #include "counter-adt.h" /* zbog definicija INT_MAX*/ /* zbog definicija malloc i free*/ 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) free(pc); assert(pc!= NULL); void reset_counter(counter pc, int mod) if(mod <= 1) mod = INT_MAX; pc->mod = mod; pc->count=0; assert(pc!= NULL); int incr_count(counter pc) assert(pc!= NULL); pc->count++; if(pc->count >= pc->mod) pc->count = 0; return pc->count; 213

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= ) itd

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 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 */ Slika Stog - operacije #endif 215

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; unsigned size; ; /* indeks poviše stvarnog vrha stoga*/ /* 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; 216

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); 217

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 ) + / * + 1 ( ( 3 * 2 ) / * * / daje notaciju: * * / 218

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: * * / Proračun ovog izraza pomoću stoga ilustriran je na slici 16.3: Neobrađeni ulazni niz Operacija Sadržaj stoga * * / push * * / push * * / push * * / pop(b), pop(a),push (a*b) * / pop(b), pop(a),push(a+b) * / push * / push * / push * / pop(b), pop(a),push (a*b) / pop(b), pop(a),push(a+b) / push / pop(b), pop(a),push(a-b) / pop(b), pop(a),push(a/b) 6 Slika 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 * * /" U izrazu se smiju koristiti cijeli brojevi i operatori zbrajanja, oduzimanja, množenja i dijeljenja. Kada se program izvrši dobije se ispis: Rezultat: * * / = 6 219

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 \" / 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); 220

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: 221

222 Početno je red prazan Front = back; Nakon operacija put() povećava se indeks back za 1 Operacija get() dobavlja element kojem je indeks jednak front, a zatim se front povećava za jedan. Što napraviti kada, nakon višestrukog unosa, back postane jednak krajnjem indeksu niza? 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 Front=back (red prazan) put('a'); put('b') a b front back x = get() /* x sadrži vrijednost a */ a b front back put('c'); put('d'); put('e'); - b c d E - front back 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); 222

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() 223

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. 224

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: 225

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) 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 stanje stoga koji se koristi za prijenos argumenata funkcije Slika 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 226

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: 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 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 227

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 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: 228

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 229

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 2 n -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 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 x 2 ) 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 < x 2 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). 230

231 /* Program korijen.c */ #include <stdio.h> #include <math.h> #define EPSILON /* 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 Dobije se ispis: Drugi korijen ( ) = (treba biti ) Drugi korijen ( ) = (treba biti ) Drugi korijen ( ) = (treba biti ) 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 231

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 D i g d i g 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 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 232

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) 233

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; 234

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 ; 235

236 Dobije se ispis: Ante Ivo Marko Jure Bozo Nakon sortiranja: Ante Bozo Ivo Jure Marko Pronadjen string "Ivo" na adresi 0022FF 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 S = 0 ; for(i=0; i<n; i++) for(j=0; j<n; j++) S = S + M[i][j] BROJ PONAVLJANJA. n n*n n*n Ako se uzme da prosječno vrijeme izvršenja jedne naredbe iznosi t 0, dobije se da ukupno vrijeme izvršenja algoritma iznosi: T(n) = (2n² + n)*t 0 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) 236

237 ako postoje pozitivne konstante C i n 0, takove da je 0 T(n) C * f(n), za sve vrijednosti n n 0. 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 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 n k (k>2) Eksponencijalni k n (k>1) Faktorijelni n! Tablica 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 Obaviti završnu obradu s problemom jedinične veličine. Ukupan broj razlaganja k dobije se iz uvjeta: n / 2 k = 1, odnosno k=log 2 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. 237

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 max = a[0] for(i=1; i<n; i++) if (a[i] > max) max = a[i] BROJ PONAVLJANJA 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 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. 238

239 Kvadratni i stupanjski algoritmi Kvadratni algoritmi, složenosti O(n 2 ), 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(n k ). Eksponencijalni algoritmi Eksponencijalni algoritmi O(k n ) 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!) 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(n 2 ). Zatim će biti analizirane dvije napredne metode, quicksort i mergesort, koje koriste metodu "podijeli pa vladaj". Njihova je složenost O(n log 2 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 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. 239

240 3. Zatim pronađi najmanji element, počevši od indeksa 1. Kada ga pronađeš, zamijeni ga s elementom indeksa 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: > min od A[0..5] zamijeni sa A[0] > min od A[1..5] zamijeni sa A[1] > min od A[2..5] zamijeni sa A[2] > min od A[3..5] zamijeni sa A[3] > min od A[4..5] zamijeni sa A[4] > 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]. */ 240

241 swap(&a[i], &A[imin]); Analiza selekcijskog sortiranja Svaka iteracija vanjske petlje (indeks i) traje konstantno vrijeme t 1 plus vrijeme izvršenja unutarnje petlje (indeks j). Svaka iteracija u unutarnjoj petlji traje konstantno vrijeme t 2. Broj iteracija unutarnje petlje ovisi u kojoj se iteraciji nalazi vanjska petlja: Ukupno vrijeme je: Broj operacija u i unutarnjoj petlji 0 n-1 1 n-2 2 n n-2 1 T(n) = [t 1 + (n-1) t 2 ] + [t 1 + (n-2) t 2 ] + [t 1 + (n-3) t 2 ] [t 1 + (1) t 2 ] odnosno, grupirajući članove u oblik t 1 ( ) + (...) t 2 dobije se T(n) = (n-1) t 1 + [ (n-1) + (n-2) + (n-3) ] t 2 Izraz u uglatim zagradama predstavlja sumu aritmetičkog niza (n-1) = (n-1)n/2 = (n 2 -n)/2, pa je ukupno vrijeme jednako: T(n) = (n-1) t 1 + [(n 2 -n)/2] t 2 = - t 1 + t 1 n - t 2 n/2 + t 2 n 2 /2 Očito je da dominira član sa n 2, pa je složenost selekcijskog sortiranja jednaka O(n 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: > ako je A[1]< A[0], umetni A[1] u A[0..0] > ako je A[2]< A[1], umetni A[2] u A[0..1] > ako je A[3]< A[2], umetni A[3] u A[0..2] > ako je A[4]< A[3], umetni A[4] u A[0..3] > ako je A[5]< A[4], umetni A[5] u A[0..4] > niz je sortiran Algoritam se može zapisati pseudokôdom: for (i = 1; i < n; i++) 241

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(n 2 ). 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(n 2 ) 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); 242

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 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 */ 243

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 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++]; 244

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 log 2 n), ukoliko se uzme da je veličina niza potencija broja 2, tj. da je n = 2 m. 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 log 2 n. Na k-toj razini niz je podijeljen na 2 k podnizova duljine n/2 k. To znači da spajanje sortiranih nizova na k-toj razini ima složenost 2 k xo(n/2 k )= O(n), a pošto ima log 2 n razina, proizlazi da je ukupna složenost jednaka O(n log 2 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 Quicksort Quicksort je najčešće korištena metoda sortiranja. Njome se u prosječnom slučaju postiže složenost O(n log 2 n), a u najgorem slučaju O(n 2 ). 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. 245

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 Slika 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. */ 246

Programiranje III razred

Programiranje III razred Tehnička škola 9. maj Bačka Palanka Programiranje III razred Naredbe ciklusa for petlja Naredbe ciklusa Veoma često se ukazuje potreba za ponavljanjem nekih naredbi više puta tj. za ponavljanjem nekog

More information

Naredbe za kontrolu toka

Naredbe za kontrolu toka Naredbe za kontrolu toka Naredbe za kontrolu toka Nakon odslušanog bit ćete u stanju: objasniti semantiku naredbi za kontrolu postupaka navesti sintaksu naredbi if, if-else i case u programskom jeziku

More information

Programiranje Programski jezik C. Sadržaj. Datoteke. prof.dr.sc. Ivo Ipšić 2009/2010

Programiranje Programski jezik C. Sadržaj. Datoteke. prof.dr.sc. Ivo Ipšić 2009/2010 Programiranje Programski jezik C prof.dr.sc. Ivo Ipšić 2009/2010 Sadržaj Ulazno-izlazne funkcije Datoteke Formatirane datoteke Funkcije za rad s datotekama Primjeri Datoteke komunikacija između programa

More information

VRIJEDNOSTI ATRIBUTA

VRIJEDNOSTI ATRIBUTA VRIJEDNOSTI ATRIBUTA Svaki atribut (bilo da je primarni ključ, vanjski ključ ili običan atribut) može i ne mora imati ograničenja na svojim vrijednostima. Neka od ograničenja nad atributima: Null / Not

More information

PREDMET. Osnove Java Programiranja. Čas JAVADOC

PREDMET. Osnove Java Programiranja. Čas JAVADOC PREDMET Osnove Java Programiranja JAVADOC Copyright 2010 UNIVERZITET METROPOLITAN, Beograd. Sva prava zadržana. Bez prethodne pismene dozvole od strane Univerziteta METROPOLITAN zabranjena je reprodukcija,

More information

Osnove programskog jezika C# Čas 5. Delegati, događaji i interfejsi

Osnove programskog jezika C# Čas 5. Delegati, događaji i interfejsi Osnove programskog jezika C# Čas 5. Delegati, događaji i interfejsi DELEGATI Bezbedni pokazivači na funkcije Jer garantuju vrednost deklarisanog tipa. Prevodilac prijavljuje grešku ako pokušate da povežete

More information

Uputa: Zabranjeno je koristiti bilo kakva pomagala. Rje²enja pi²ete desno od zadatka. Predajete samo ovaj list.

Uputa: Zabranjeno je koristiti bilo kakva pomagala. Rje²enja pi²ete desno od zadatka. Predajete samo ovaj list. Ime i prezime: Asistent: Predava : Programiranje (C) 1. kolokvij 14. 4. 2003. 1. 2. 3. 4. 5. 6. 7. Uputa: Zabranjeno je koristiti bilo kakva pomagala. Rje²enja pi²ete desno od zadatka. Predajete samo ovaj

More information

Učitati cio broj n i štampati njegovu recipročnu vrijednost. Ako je učitan broj 0, štampati 1/0.

Učitati cio broj n i štampati njegovu recipročnu vrijednost. Ako je učitan broj 0, štampati 1/0. Kontrolne naredbe Primjeri: Opšti oblik razgranate strukture (if sa ) if (uslov) Naredba 1 ili blok naredbi1 Naredba 2 ili blok naredbi2 Učitati broj x i štampati vrijednost double x, z; Scanner in=new

More information

SVEUČILIŠTE U MOSTARU FAKULTET PRIRODOSLOVNO-MATEMATIČKIH I ODGOJNIH ZNANOSTI BAZE PODATAKA 2. Doc.dr.sc. GORAN KRALJEVIĆ BAZE PODATAKA 2 1

SVEUČILIŠTE U MOSTARU FAKULTET PRIRODOSLOVNO-MATEMATIČKIH I ODGOJNIH ZNANOSTI BAZE PODATAKA 2. Doc.dr.sc. GORAN KRALJEVIĆ BAZE PODATAKA 2 1 SVEUČILIŠTE U MOSTARU FAKULTET PRIRODOSLOVNO-MATEMATIČKIH I ODGOJNIH ZNANOSTI BAZE PODATAKA 2 Doc.dr.sc. GORAN KRALJEVIĆ BAZE PODATAKA 2 1 Baze podataka 2 Web: http://www.fpmoz.ba/gkraljevic Pitanja, primjedbe,

More information

Binarne hrpe. Strukture podataka i algoritmi VJEŽBE 26. siječnja / 133

Binarne hrpe. Strukture podataka i algoritmi VJEŽBE 26. siječnja / 133 Binarne hrpe Potpuno binarno stablo binarno stablo u kojem svaki čvor koji nije list ima točno 2 nasljednika. Binarna hrpa potpuno binarno stablo u kojem svaki čvor koji nije list ima veću ključnu vrijednost

More information

b) program deljiv3; uses wincrt; var i:integer; begin i:=3; while i<100 do begin write(i:5); i:=i+3; end; end.

b) program deljiv3; uses wincrt; var i:integer; begin i:=3; while i<100 do begin write(i:5); i:=i+3; end; end. NAREDBA CIKLUSA SA PREDUSLOVOM WHILE 1.Odrediti vrednosti s i p nakon izvrsenja sledecih naredbi za dato a=43, a=34, a=105 program p1; var a,s,p:integer; write('unesite a:');readln(a); p:=a; s:=0; while

More information

Uvod u programiranje - vežbe. Kontrola toka izvršavanja programa

Uvod u programiranje - vežbe. Kontrola toka izvršavanja programa Uvod u programiranje - vežbe Kontrola toka izvršavanja programa Naredbe za kontrolu toka if, if-else, switch uslovni operator (?:) for, while, do-while break, continue, return if if (uslov) naredba; if

More information

VB komande. Programiranje 1

VB komande. Programiranje 1 VB komande Programiranje 1 Zadatak 1: Sastaviti program koji se sastoji iz jedne ListBox kontrole, jedne Textbox kontrole i dva komandna dugmeta. Klikom na prvo komandno dugme umeće se u ListBox sadržaj

More information

CSS CSS. selector { property: value; } 3/20/2018. CSS: Cascading Style Sheets

CSS CSS. selector { property: value; } 3/20/2018. CSS: Cascading Style Sheets CSS CSS CSS: Cascading Style Sheets - Opisuje izgled (appearance) i raspored (layout) stranice - Sastoji se od CSS pravila, koji defini[u skup stilova selector { property: value; 1 Font face: font-family

More information

PARALELNO PROGRAMIRANJE

PARALELNO PROGRAMIRANJE Predavanje 09 Odjel za matematiku 1 PARALELNO PROGRAMIRANJE POSIX threadovi za C++ Predavanje 09 Odjel za matematiku 2 Programske niti (thread) unutar procesa Danas ćemo se upoznati s POSIX thread bibliotekom

More information

PROGRAMIRANJE. Amir Hajdar

PROGRAMIRANJE. Amir Hajdar PROGRAMIRANJE Amir Hajdar Teme 2 Klase i objekti u Javi Primjer kroz klasu Krug Atributi i metode Inicijalizacija objekata (konstruktori) Polymorphism Statičke varijable i metode This Klase i objekti u

More information

Scheme je funkcionalni jezik moderna varijanta jezika LISP-a, s dodacima iz Algola (lokalni doseg identifikatora).

Scheme je funkcionalni jezik moderna varijanta jezika LISP-a, s dodacima iz Algola (lokalni doseg identifikatora). SCHEME Scheme je funkcionalni jezik moderna varijanta jezika LISP-a, s dodacima iz Algola (lokalni doseg identifikatora). Sheme se uglavnom koristi kao intepreter. Koristit ćemo Petit Chez Scheme. Petite

More information

Svi Java tipovi imaju ekvivalentan tip u jeziku Scala Većina Scala koda se direktno preslikava u odgovarajući Java konstrukt

Svi Java tipovi imaju ekvivalentan tip u jeziku Scala Većina Scala koda se direktno preslikava u odgovarajući Java konstrukt Funkcionalno programiranje Interoperabilnost jezika Scala i Java Prevođenje u Java bajt kod Svi Java tipovi imaju ekvivalentan tip u jeziku Scala Većina Scala koda se direktno preslikava u odgovarajući

More information

Uputstvo za korišćenje logrotate funkcije

Uputstvo za korišćenje logrotate funkcije Copyright AMRES Sadržaj Uvod 3 Podešavanja logrotate konfiguracionog fajla 4 Strana 2 od 5 Uvod Ukoliko je aktivirano logovanje za RADIUS proces, može se desiti da posle određenog vremena server bude preopterećen

More information

RAČUNALSTVO Algoritmi, programi, programski jezici 1

RAČUNALSTVO Algoritmi, programi, programski jezici 1 SVEUČILIŠTE U ZAGREBU FAKULTET PROMETNIH ZNANOSTI RAČUNALSTVO Prof. dr. sc. Hrvoje Gold 2009/2010. RAČUNALSTVO 06. ALGORITMI, PROGRAMI, PROGRAMSKI JEZICI 2 Podaci i informacije Podaci, informacije, komunikacija

More information

Prva recenica. Druga recenica.

Prva recenica. Druga recenica. Algoritmi i programiranje Predavanje 4 METODE LOKALNE, GLOBALNE VARIJABLE I KONSTANTE METODA je imenovani izdvojeni slijed naredbi koji rješava određeni zadatak i po potrebi se poziva jednom ili više puta

More information

8. NIZOVI. // deklaracija niza od 10 elemenata: data[0], data[1],..data[9] int data[10] ; S elementima niza se operira kao s prostim varijablama

8. NIZOVI. // deklaracija niza od 10 elemenata: data[0], data[1],..data[9] int data[10] ; S elementima niza se operira kao s prostim varijablama 8. NIZOVI Niz je indeksirani skup podataka - elemenata niza. Niz se deklarira imenom iza kojeg se u uglatim zagradama zapisuje broj elemenata niza, a ispred imena se zapisuje tip elemenata. // deklaracija

More information

Push(3,&S) 3 1 S Uvijek trebamo paziti da ne zovemo Pop nad praznim stogom.

Push(3,&S) 3 1 S Uvijek trebamo paziti da ne zovemo Pop nad praznim stogom. tog (tack) tog je posebna vrsta liste: od svih operacija dozvoljeno je ubacivanje, brisanje i gledanje sadržaja elementa samo na jednom kraju liste koji zovemo vrh stoga. tog zovemo i lifo last in first

More information

VHDLPrimeri Poglavlje5.doc

VHDLPrimeri Poglavlje5.doc 5. VHDL opis kola koja obavljaju osnovne aritmetičke funkcije Sabirači Jednobitni potpuni sabirač definisan je tablicom istinitosti iz Tabele 5.1. Tabela 5.1. cin a b sum cout 0 0 0 0 0 0 0 1 1 0 0 1 0

More information

Osnove programskog jezika C# Čas 4. Nasledjivanje 2. deo

Osnove programskog jezika C# Čas 4. Nasledjivanje 2. deo Osnove programskog jezika C# Čas 4. Nasledjivanje 2. deo Nasledjivanje klasa Modifikator new class A { public virtual void F() { Console.WriteLine("I am A"); } } class B : A { public override void F()

More information

Programske paradigme Funkcionalna paradigma

Programske paradigme Funkcionalna paradigma Programske paradigme Funkcionalna paradigma 1. čas: Uvod u funkcionalno programiranje. Programski jezik Haskel. Upoznavanje sa razvojnim okruženjem. Tipovi podataka. Funkcionalno programiranje Stil u programiranju

More information

Sberbank Business Online na Mozilla FireFox

Sberbank Business Online na Mozilla FireFox Sberbank Business Online na Mozilla FireFox Verzija 1.6 Srpanj 2016. Sberbank d.d. Stranica 1 SADRŽAJ 1 INSTALACIJA... 2 2 POKRETANJE MOZILLE FIREFOX... 3 2.1 IMPORT SECURITY MODULA... 4 2.2 AUTOMATSKI

More information

Uputa za instaliranje programske potpore za operativni sustav WINDOWS

Uputa za instaliranje programske potpore za operativni sustav WINDOWS ZABA SignErgy Desktop aplikacija Uputa za instaliranje programske potpore za operativni sustav WINDOWS SADRŽAJ 1. UVOD 3 2. PODRŽANI OPERATIVNI SUSTAVI 3 3. PROGRAMSKI PREDUVJETI ZA INSTALACIJU PROGRAMSKE

More information

JavaScript i HTML DOM

JavaScript i HTML DOM 4. vježbe iz WEB programiranja četvrtak, 22. ožujka 2012. JavaScript 1. dio JavaScript i Što je DOM? Kako JS koristi DOM? Pristup elementima dokumenta Promjena i učitavanje vrijednosti tagova Primjer 1.

More information

Upute za postavljanje Outlook Expressa

Upute za postavljanje Outlook Expressa Upute za postavljanje Outlook Expressa Prije postavljanja klijenata morate obavezno obaviti prvu prijavu na web mail kako bi aktivirali vaš račun na novom sustavu. Ukoliko niste obavili prvu prijavu, nećete

More information

Nizovi. Programiranje 1

Nizovi. Programiranje 1 Nizovi Programiranje 1 VB Nizovi Zamislite da imate 10,000 šešira i da morate svakome od njih dati jedinstvenu oznaku. Kako biste to napravili? Bilo bi razumno svakom šeširu dati njegov broj. Sada možete

More information

VDSL modem Zyxel VMG1312-B10A/B30A

VDSL modem Zyxel VMG1312-B10A/B30A VDSL modem Zyxel VMG1312-B10A/B30A Default Login Details LAN IP Address http://192.168.2.1 User Name user Password 1234 Funkcionalnost lampica Power lampica treperi kratko vrijeme nakon uključivanja modema,

More information

pojedinačnom elementu niza se pristupa imeniza[indeks] indeks od 0 do n-1

pojedinačnom elementu niza se pristupa imeniza[indeks] indeks od 0 do n-1 NIZOVI Niz deklarišemo navođenjemtipa elemenata za kojim sledi par srednjih zagrada[] i naziv niza. Ako je niz višedimenzionalni između zagrada[] se navode zarezi, čiji je broj za jedan manji od dimenzija

More information

Vežbe - XII nedelja PHP Doc

Vežbe - XII nedelja PHP Doc Vežbe - XII nedelja PHP Doc Dražen Drašković, asistent Elektrotehnički fakultet Univerziteta u Beogradu Verzija alata JavaDoc za programski jezik PHP Standard za komentarisanje PHP koda Omogućava generisanje

More information

16. Sigurnije programiranje

16. Sigurnije programiranje 16. Sigurnije programiranje 16.1 Pretvorba tipova Pretvorba realnog broja u cijeli broj se može izvršiti naredbama: int i; double d; ili: i = (int) d; i = int(d); U cilju bolje kontrole pretvorbe tipova

More information

Sveučilište J. J. Strossmayera u Osijeku Odjel za matematiku Sveučilišni preddiplomski studij matematike. Arhitektura računala. Osijek, 2016.

Sveučilište J. J. Strossmayera u Osijeku Odjel za matematiku Sveučilišni preddiplomski studij matematike. Arhitektura računala. Osijek, 2016. Sveučilište J. J. Strossmayera u Osijeku Odjel za matematiku Sveučilišni preddiplomski studij matematike Ivan Miličić Arhitektura računala Završni rad Osijek, 2016. Sveučilište J. J. Strossmayera u Osijeku

More information

2. Linijska algoritamska struktura

2. Linijska algoritamska struktura Univerzitet u Nišu Građevinsko-arhitektonski fakultet Informatika 2 2. Linijska algoritamska struktura Milica Ćirić Blokovi za prikaz algoritma Algoritam se vizuelno može prikazati pomoću blok dijagrama,

More information

Informatika Uvod u C#,.NET Framework i Visual Studio... nastavak...

Informatika Uvod u C#,.NET Framework i Visual Studio... nastavak... Informatika Uvod u C#,.NET Framework i Visual Studio... nastavak... Prof. dr. sc. Tomislav Pribanić Izv. prof. dr. sc. Vedran Podobnik Doc. dr. sc. Marija Seder Sveučilište u Zagrebu Fakultet elektrotehnike

More information

Prirodno-matematički fakultet u Nišu Departman za fiziku. dr Dejan S. Aleksić Programiranje u fizici

Prirodno-matematički fakultet u Nišu Departman za fiziku. dr Dejan S. Aleksić Programiranje u fizici Programiranje u fizici Prirodno-matematički fakultet u Nišu Departman za fiziku dr Dejan S. Aleksić Programiranje u fizici 7-8 Definicija, inicijalizacija promenljivih 2/21 u C-u Program napisan u programskog

More information

Uvod, varijable, naredbe, petlje

Uvod, varijable, naredbe, petlje 8. JavaScript Uvod, varijable, naredbe, petlje M. Zekić-Sušac 1 Što je JavaScript? JavaScript je najpopularniji skriptni jezik na Internetu kojeg podržavaju svi poznatiji preglednici (Internet Explorer,

More information

Blokovska struktura C++ programa ima četiri razine:

Blokovska struktura C++ programa ima četiri razine: Blokovska struktura C++ programa ima četiri razine: 1. razina datoteke (temeljna kompilacijska jedinica) 2. razina definicije (tijela) funkcije 3. razina bloka kontrolnih struktura (sekvenca, iteracija,

More information

Sadržaj predavanja 02. Cjelobrojni tip podataka(1/3) Cjelobrojni tip podataka(2/3) Cjelobrojni tip podataka(3/3) prec(short) prec(int) prec(long) int

Sadržaj predavanja 02. Cjelobrojni tip podataka(1/3) Cjelobrojni tip podataka(2/3) Cjelobrojni tip podataka(3/3) prec(short) prec(int) prec(long) int Sadržaj predavanja 02 Cjelobrojni tip podataka(1/3) Cjelobrojni tip podataka Realni tip podataka Aritmetički operatori Izrazi Operatori inkrement i dekrement Kontrola toka programa Naredba za jednostruki

More information

Uvod u računarstvo. Preddiplomski studij elektrotehnike 2009/2010. prof.dr.sc. Ivo Ipšić UUR 2009/2010

Uvod u računarstvo. Preddiplomski studij elektrotehnike 2009/2010. prof.dr.sc. Ivo Ipšić UUR 2009/2010 Uvod u računarstvo Preddiplomski studij elektrotehnike 2009/2010 prof.dr.sc. Ivo Ipšić 1 Saržaj kolegija Uvod u Uvod i razvoj računala računarstvo Zapis podataka i kodiranje informacija u računalu Građa

More information

FAKULTET ELEKTROTEHNIKE I RAČUNARSTVA

FAKULTET ELEKTROTEHNIKE I RAČUNARSTVA FAKULTET ELEKTROTEHNIKE I RAČUNARSTVA Sustavi za praćenje i vođenje procesa Seminarski rad LOGIČKI ANALIZATOR (PC kao instrument) 26. svibnja 2007. Ivan Grubišić 0036404380 1. Logički analizator Logički

More information

Windows Server 2012, VDI Licenciranje najprodavanijeg servera, što je novo, VDI licenciranje. Office 2013 / Office 365

Windows Server 2012, VDI Licenciranje najprodavanijeg servera, što je novo, VDI licenciranje. Office 2013 / Office 365 Windows 8 Licenciranje, razlike u verzijama Windows Server 2012, VDI Licenciranje najprodavanijeg servera, što je novo, VDI licenciranje Serverski proizvodi Server 2012, System centar 2012, SQL 2012, Sharepoint

More information

SVEUČILIŠTE U Z GRE U Fakultet prometnih znanosti Zavod za inteligentne transportne sustave Vukelićeva 4, Zagreb, HRV TSK.

SVEUČILIŠTE U Z GRE U Fakultet prometnih znanosti Zavod za inteligentne transportne sustave Vukelićeva 4, Zagreb, HRV TSK. SVEUČILIŠTE U Z GRE U Fakultet prometnih znanosti Zavod za inteligentne transportne sustave Vukelićeva 4, Zagreb, HRV TSK Računalstvo Unos i ispis podataka Doc. dr. sc. Edouard Ivanjko, dipl.ing. Sadržaj

More information

dr. sc.. Josip Musić Originalne slideove izradio:

dr. sc.. Josip Musić Originalne slideove izradio: Uvod u programiranje Programiranje 1 (550) Poglavlje 3 Strukture odluka i ponavljanja dr. sc.. Josip Musić jmusic@fesb.hr 1 Originalne slideove izradio: Teo Žuljević, dipl.. ing. teo.zuljevic@fesb.hr Pregled

More information

Numeričke metode i praktikum

Numeričke metode i praktikum Numeričke metode i praktikum Aleksandar Maksimović IRB / 23/03/2006 / Str. 1 vektori Vektor u 3D prostoru. C: int v1[3]; v1[0]=a;v1[1]=b;v1[2]=c; Fortran: INTEGER V1(3) V1(1)=a V1(2)=b V1(3)=c Skalarni

More information

UNIVERZITET U BEOGRADU ELEKTROTEHNIČKI FAKULTET

UNIVERZITET U BEOGRADU ELEKTROTEHNIČKI FAKULTET UNIVERZITET U BEOGRADU ELEKTROTEHNIČKI FAKULTET Katedra za elektroniku Računarska elektronika Grupa br. 11 Projekat br. 8 Studenti: Stefan Vukašinović 466/2013 Jelena Urošević 99/2013 Tekst projekta :

More information

PKI Applet Desktop Application Uputa za instalaciju programske potpore

PKI Applet Desktop Application Uputa za instalaciju programske potpore 1 SADRŽAJ 1. UVOD 3 2. PODRŽANI OPERATIVNI SUSTAVI 3 3. PROGRAMSKI PREDUVJETI ZA INSTALACIJU PROGRAMSKE POTPORE 3 4. INSTALACIJA PROGRAMSKE POTPORE 3 5. DEINSTALACIJA PROGRAMSKE POTPORE 6 2 1. Uvod PKI

More information

9. RAD S DATOTEKAMA PODATAKA

9. RAD S DATOTEKAMA PODATAKA Rad s datotekama 7-1 9. RAD S DATOTEKAMA PODATAKA U programiranju se često radi sa skupovima podataka koji se čuvaju na jedinicama perfernih memorija, a koji se organizuju u posebne cjeline koje nazivamo

More information

NIZOVI.

NIZOVI. NIZOVI LINKOVI ZA KONZOLNI C# OSNOVNO http://www.mycity.rs/net/programiranje-u-c-za-osnovce-i-srednjoskolce.html http://milan.milanovic.org/skola/csharp-00.htm Niz deklarišemo navođenjem tipa elemenata

More information

Događaj koji se javlja u toku izvršenja programa i kvari normalno izvršenje. Kada se desi izuzetak, sistem pokušava da pronađe način da ga obradi.

Događaj koji se javlja u toku izvršenja programa i kvari normalno izvršenje. Kada se desi izuzetak, sistem pokušava da pronađe način da ga obradi. Obrada izuzetaka Šta je izuzetak? Događaj koji se javlja u toku izvršenja programa i kvari normalno izvršenje. Kada se desi izuzetak, sistem pokušava da pronađe način da ga obradi. Prosleđuje izuzetak,

More information

Sveučilište u Zagrebu PMF Matematički odsjek. Mreže računala. Vježbe 08. Zvonimir Bujanović Slaven Kožić Vinko Petričević

Sveučilište u Zagrebu PMF Matematički odsjek. Mreže računala. Vježbe 08. Zvonimir Bujanović Slaven Kožić Vinko Petričević Sveučilište u Zagrebu PMF Matematički odsjek Mreže računala Vježbe 08 Zvonimir Bujanović Slaven Kožić Vinko Petričević Uvod: (X)HTML i CSS Na ovim i idućim vježbama naučit ćemo osnove jezika za opisivanje

More information

Vidljivost TipPovratneVrednosti ImeFunkcije (NizParametara) { TeloFunkcije }

Vidljivost TipPovratneVrednosti ImeFunkcije (NizParametara) { TeloFunkcije } 1. FUNKCIJE I STRUKTRUE PROGRAMA Složeni problemi lakše se rašavaju ako se podele na manje celine koje mogu nezavisno da se rešavaju. Rešenje celokupnog složenog problema dobija se kombinovanjem rešenja

More information

GUI - događaji (Events) i izuzeci. Bojan Tomić

GUI - događaji (Events) i izuzeci. Bojan Tomić GUI - događaji (Events) i izuzeci Bojan Tomić Događaji GUI reaguje na događaje (events) Događaj je neka akcija koju korisnik programa ili neko drugi izvrši korišćenjem perifernih uređaja (uglavnom miša

More information

Algoritmi i strukture podataka 2. Čas, Uvod u C++

Algoritmi i strukture podataka 2. Čas, Uvod u C++ Algoritmi i strukture podataka 2. Čas, Uvod u C++ Aleksandar Veljković 2017/2018 1 Uvod Jezik C++ je jezik koji pripada objektno orijentisanoj paradigmi, ipak, u okviru ovog kursa naglasak neće biti na

More information

VEŽBA 5 do while petlja, switch case

VEŽBA 5 do while petlja, switch case VEŽBA do while petlja, switch case Petlja sa ulaznim uslovom do while U slučaju do while petlje obavezno izvršavanje bar jedne iteracije se postiže tako što je upravljački izraz petlje na samom dnu petlje.

More information

OSNOVE PROGRAMIRANJA RAČUNALO PROGRAM OSNOVNE GRUPE SOFTVERA PROGRAMSKI JEZIK

OSNOVE PROGRAMIRANJA RAČUNALO PROGRAM OSNOVNE GRUPE SOFTVERA PROGRAMSKI JEZIK RAČUNALO OSNOVE PROGRAMIRANJA Hardver električni, elektronički i mehanički dijelovi od kojih je građeno računalo te njegovi pojedini priključci Softver programi koje računalo koristi da bi izvršilo određeni

More information

Programiranje III razred

Programiranje III razred Tehnička škola 9. maj Bačka Palanka Programiranje III razred Konverzija tipova Konverzija tipova Prilikom komunikacije aplikacije sa korisnikom, korisnik najčešće unosi ulazne podatke koristeći tastaturu.

More information

CAD u građevinarstvu. v.prof.dr. Samir Lemeš. Predavanja za predmet CAD u građevinarstvu. Politehnički fakultet Univerziteta u Zenici, 2018.

CAD u građevinarstvu. v.prof.dr. Samir Lemeš. Predavanja za predmet CAD u građevinarstvu. Politehnički fakultet Univerziteta u Zenici, 2018. CAD u građevinarstvu v.prof.dr. Samir Lemeš Predavanja za predmet CAD u građevinarstvu Politehnički fakultet Univerziteta u Zenici, 2018. Korisnički interfejsi AutoCAD interfejsi Komandna linija Visual

More information

Uvod u Javu. Programski jezik Java Izvršavanje Java programa BlueJ razvojno okruženje Elementi Jave Tipovi podataka Prvi programi

Uvod u Javu. Programski jezik Java Izvršavanje Java programa BlueJ razvojno okruženje Elementi Jave Tipovi podataka Prvi programi 2 Uvod u Javu Programski jezik Java Izvršavanje Java programa BlueJ razvojno okruženje Elementi Jave Tipovi podataka Prvi programi 12 Java i objektno orijentirano programiranje Programski jezik Java Krajem

More information

namespace spojneice { public partial class Form1 : Form { public Form1() { InitializeComponent(); }

namespace spojneice { public partial class Form1 : Form { public Form1() { InitializeComponent(); } Spojnice using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.IO;

More information

Primjer ispisuje rečenicu "Dobro dosli na brzi tecaj C-a" na ekranu

Primjer ispisuje rečenicu Dobro dosli na brzi tecaj C-a na ekranu Osnove programiranja p. 1/27 Brzi tečaj C-a Primjer ispisuje rečenicu "Dobro dosli na brzi tecaj C-a" na ekranu Programiranje se sastoji od nekoliko koraka - pisanje programa u tekstualnom editoru (joe,

More information

Rekurzivne metode. Posmatrajmo rekurzivan metod kojim u objektu listbox1 klase ListBox upisujemo sve prirodne brojeve od 1 do datog n.

Rekurzivne metode. Posmatrajmo rekurzivan metod kojim u objektu listbox1 klase ListBox upisujemo sve prirodne brojeve od 1 do datog n. Rekurzivne metode Rekurzivan metod je onaj metod koji u nekoj svojoj instrukciji sadrži poziv samog sebe. Svakako prilikom kreiranja rekurzivnog metoda moramo voditi računa da ne dodje do beskonačne rekurzije

More information

Uvod u računarstvo. Preddiplomski studij elektrotehnike 2008/2009. prof.dr.sc. Ivo Ipšić UUR 2008/2009

Uvod u računarstvo. Preddiplomski studij elektrotehnike 2008/2009. prof.dr.sc. Ivo Ipšić UUR 2008/2009 Uvod u računarstvo Preddiplomski studij elektrotehnike 2008/2009 prof.dr.sc. Ivo Ipšić 1 Saržaj kolegija Uvod u Uvod i razvoj računala računarstvo Zapis podataka i kodiranje informacija u računalu Građa

More information

Izrada VI laboratorijske vježbe

Izrada VI laboratorijske vježbe Izrada VI laboratorijske vježbe 1. Programirati proceduru koja se aktivira sa Standard palete alatki klikom na button Fajlovi. Prilikom startovanja procedure prikazuje se forma koja sadrži jedan list box

More information

24/03/2018. Deklaracija promenljivih. Inicijalizacija promenljivih. Deklaracija i inicijalizacija promenljivih

24/03/2018. Deklaracija promenljivih. Inicijalizacija promenljivih. Deklaracija i inicijalizacija promenljivih Deklaracija promenljivih Inicijalizacija promenljivih Deklaracija promenljive obuhvata: dodelu simboličkog imena promenljivoj i određivanje tipa promenljive (tip određuje koja će vrsta memorijskog registra

More information

RAČUNARSKI PRAKTIKUM II

RAČUNARSKI PRAKTIKUM II Prirodoslovno-matematički fakultet Matematički odsjek Sveučilište u Zagrebu RAČUNARSKI PRAKTIKUM II Predavanje 07 - Uvod u PHP 4. svibnja 2015. Sastavio: Zvonimir Bujanović PHP PHP Interpretirani skriptni

More information

Uvod u relacione baze podataka

Uvod u relacione baze podataka Uvod u relacione baze podataka Ana Spasić 5. čas 1 Podupiti, operatori exists i in 1. Izdvojiti imena i prezimena studenata koji su položili predmet čiji je identifikator 2001. Rešenje korišćenjem spajanja

More information

PRIJEMNI ISPIT IZ INFORMATIKE

PRIJEMNI ISPIT IZ INFORMATIKE PRIRODNO-MATEMATIČKI FAKULTET U NIŠU DEPARTMAN ZA RAČUNARSKE NAUKE Petak,04.09.2015 PRIJEMNI ISPIT IZ INFORMATIKE PITANJA I ZADACI IZ INFORMATIKE 1. Kombinacija tastera Ctrl+C koristi se u Windows aplikacijama

More information

dr. sc.. Josip Musić Originalne slideove izradio:

dr. sc.. Josip Musić Originalne slideove izradio: Uvod u programiranje Programiranje 1 (450) Poglavlje 1 Uvod u računala, Microsoft.NET i VB.NET (dio 2 od 2): Pregled.NET platforme dr. sc.. Josip Musić jmusic@fesb.hr 1 Originalne slideove izradio: Teo

More information

Fortran 90. Numeričke Metode DECEMBAR ĐURĐEVAC NATAŠA

Fortran 90. Numeričke Metode DECEMBAR ĐURĐEVAC NATAŠA Fortran 90 Numeričke Metode DECEMBAR 2007. ĐURĐEVAC NATAŠA Zašto Fortran? jer je konstruisan da bi se koristio za rešavanje matematičkih problema. jer je jednostavan jezik sa dobrim performansama (odlična

More information

UPUTSTVO ZA KORIŠĆENJE NOVOG SPINTER WEBMAIL-a

UPUTSTVO ZA KORIŠĆENJE NOVOG SPINTER WEBMAIL-a UPUTSTVO ZA KORIŠĆENJE NOVOG SPINTER WEBMAIL-a Webmail sistem ima podršku za SSL (HTTPS). Korištenjem ovog protokola sva komunikacija između Webmail sistema i vašeg Web čitača je kriptovana. Prilikom pristupa

More information

4.1 Učitavanje podatka tipa string Učitavanje brojčanih vrijednosti Rad sa dinamičkim objektima... 7

4.1 Učitavanje podatka tipa string Učitavanje brojčanih vrijednosti Rad sa dinamičkim objektima... 7 Java zadaci Zadaci 1. Priprema novog projekta... 1 2. Organizacija klasa u fajlove... 2 3. Ispis vrijednosti u konzolni prozor... 3 4. Učitavanje vrijednosti sa konzolnog prozora... 4 4.1 Učitavanje podatka

More information

SELECT CASE i FOR NEXT. Programiranje 1

SELECT CASE i FOR NEXT. Programiranje 1 SELECT CASE i FOR NEXT Programiranje 1 VISUAL BASIC SELECT SELECT komanda služi umjesto višestrukih IF THEN naredbi u slučaju grananja programa, zavisno o vrijednosti ispitivanog izraza početak v1 var

More information

Microsoft Hyper-V Server 2016 radionica EDU IT Pro, Zagreb,

Microsoft Hyper-V Server 2016 radionica EDU IT Pro, Zagreb, Microsoft Hyper-V Server 2016 radionica EDU IT Pro, Zagreb, 13.04.2017. Podešavanje Hyper-V Servera 2016 za RSAT upravljanje Dario Štefek Lokacije za preuzimanje: Microsoft Hyper-V Server 2016 https://www.microsoft.com/en-us/evalcenter/evaluate-hyper-v-server-2016

More information

Jezik Baze Podataka SQL. Jennifer Widom

Jezik Baze Podataka SQL. Jennifer Widom Jezik Baze Podataka SQL SQL o Jezik koji se koristi u radu sa relacionim bazama podataka o Nije programski jezik i manje je kompleksan. o Koristi se isključivo u radu za bazama podataka. o SQL nije case

More information

Univerzitet u Nišu Građevinsko-arhitektonski fakultet. 4. Ciklična algoritamska struktura 5. Jednodimenzionalno polje.

Univerzitet u Nišu Građevinsko-arhitektonski fakultet. 4. Ciklična algoritamska struktura 5. Jednodimenzionalno polje. Univerzitet u Nišu Građevinsko-arhitektonski fakultet Informatika 2 4. Ciklična algoritamska struktura 5. Jednodimenzionalno polje Milica Ćirić Ciklična algoritamska struktura Ciklična struktura (petlja)

More information

Veliki računski zadaci mogu se razbiti u manje delove i time se omogućava ljudima da iskoriste ono što su neki drugi već uradili, umesto da počinju

Veliki računski zadaci mogu se razbiti u manje delove i time se omogućava ljudima da iskoriste ono što su neki drugi već uradili, umesto da počinju Staša Vujičić Čas 9 Veliki računski zadaci mogu se razbiti u manje delove i time se omogućava ljudima da iskoriste ono što su neki drugi već uradili, umesto da počinju sve od početka. Odgovarajuće funkcije

More information

Programiranje 1 Programski jezik C 2. čas. Mirko Spasić

Programiranje 1 Programski jezik C 2. čas. Mirko Spasić Programiranje 1 Programski jezik C 2. čas Mirko Spasić Operatori U C-u postoji veliki broj operatora. Mogu biti unarni (imaju jedan argument) i binarni (dva argumenta). Unarni operatori mogu biti prefiksni

More information

IV SQL. Slika 1. SQL*Plus ikona. Slika 2. Dijalog provere identifikacije korisnika. Slika 3. Prozor SQL*Plus programa

IV SQL. Slika 1. SQL*Plus ikona. Slika 2. Dijalog provere identifikacije korisnika. Slika 3. Prozor SQL*Plus programa IV SQL SQL (Structured Query Language) je jezik koji je Američki Institut za Nacionalne Standarde (ANSI - American National Standards Institute) prihvatio kao standardni jezik za relacione baze podataka.

More information

KLASIFIKACIJA JELENA JOVANOVIĆ. Web:

KLASIFIKACIJA JELENA JOVANOVIĆ.   Web: KLASIFIKACIJA JELENA JOVANOVIĆ Email: jeljov@gmail.com Web: http://jelenajovanovic.net PREGLED PREDAVANJA Šta je klasifikacija? Binarna i više-klasna klasifikacija Algoritmi klasifikacije Mere uspešnosti

More information

KURSORI BAZE PODATAKA U ORACLE 11g

KURSORI BAZE PODATAKA U ORACLE 11g KURSORI BAZE PODATAKA U ORACLE 11g SAŽETAK Kursor baze podataka je privatno SQL područje u kojem se čuvaju informacije za procesiranje određene SQL naredbe. Oracle PLSQL jezik koristi implicitne i eksplicitne

More information

String. String. Kreiranje string objekta pomoću string literala (konstanti) Kreiranje string objekta

String. String. Kreiranje string objekta pomoću string literala (konstanti) Kreiranje string objekta String string ili znakovni niz (string) nije niz znakova u Javi su stringovi klase String paketa java.lang!!! Usporedba: char gf = G ; Niz znakova String char [] gf = { G, e, o, d, e, t, s, k, i ; String

More information

Zadaci za Tutorijal 2.

Zadaci za Tutorijal 2. Dr. Željko Jurić: Tehnike programiranja /kroz programski jezik C++/ Tutorijal 2 Zadaci predviđeni za rad na laboratorijskim vježbama uz pomoć tutora Akademska godina 2013/14 Zadaci za Tutorijal 2. NAPOMENA:

More information

Gramatika mc programskog jezika

Gramatika mc programskog jezika Gramatika mc programskog jezika Gramatika za programski jezik m C podskup programskog jezika C izražena u BNF notaciji analiza svih simbola mc gramatike 1 Skener za mc Za rezervisane reči (if, return,...)

More information

Uputstvo za podešavanje mail klijenta

Uputstvo za podešavanje mail klijenta Uputstvo za podešavanje mail klijenta 1. Podešavanje Thunderbird mail klijenta 1.1 Dodavanje mail naloga Da biste podesili Vaš mail klijent (u ovom slučaju Thunderbird) da prima i šalje mail-ove potrebno

More information

Računarske osnove Interneta (SI3ROI, IR4ROI)

Računarske osnove Interneta (SI3ROI, IR4ROI) Računarske osnove terneta (SI3ROI, IR4ROI) Vežbe MPLS Predavač: 08.11.2011. Dražen Drašković, drazen.draskovic@etf.rs Autori: Dražen Drašković Naučili ste na predavanjima MPLS (Multi-Protocol Label Switching)

More information

KINEMATIČKA ANALIZA MEHANIZAMA INDUSTRIJSKIH ROBOTA KORIŠTENJEM PROGRAMSKOG JEZIKA MATLAB

KINEMATIČKA ANALIZA MEHANIZAMA INDUSTRIJSKIH ROBOTA KORIŠTENJEM PROGRAMSKOG JEZIKA MATLAB 10 th International Scientific Conference on Production Engineering DEVELOPMENT AND MODERNIZATION OF PRODUCTION KINEMATIČKA ANALIZA MEHANIZAMA INDUSTRIJSKIH ROBOTA KORIŠTENJEM PROGRAMSKOG JEZIKA MATLAB

More information

ARDUINO KROZ JEDNOSTAVNE PRIMJERE - pripreme za natjecanja -

ARDUINO KROZ JEDNOSTAVNE PRIMJERE - pripreme za natjecanja - ARDUINO KROZ JEDNOSTAVNE PRIMJERE - pripreme za natjecanja - PRIPREMA 5-2015 DVOSMJERNA SERIJSKA KOMUNIKACIJA Paolo Zenzerović, mag. ing. el. Zagreb, 2015. 2 ARDUINO KROZ JEDNOSTAVNE PRIMJERE DVOSMJERNA

More information

<A rel="stylesheet" B="mystylesheet.css" C="text/css" />

<A rel=stylesheet B=mystylesheet.css C=text/css /> 1 od 9 9.4.2013 7:18 EFOS_kol1_2011 - RJEŠENJA 16.4.2011. Uključivanje vanjske mystylesheet.css datoteke sa određenim stilovima, postiže se zadavanjem naredbe unutar HTML koda, koja izgleda ovako:

More information

VHDLPrimeri Poglavlje3.doc. end process seq; Slika 3.1: Anatomija osnovne definicije test bench-a

VHDLPrimeri Poglavlje3.doc. end process seq; Slika 3.1: Anatomija osnovne definicije test bench-a 3. Verifikacija projekta - Test bench entity TestBench is end entity TestBench; architecture TB_Arhitektura of TestBench is component UUT (Arhitektura_UUT) port( end component UUT; prazan entitet -- deklarisanje

More information

Uputstva za instaliranje čitača Datalogic Skorpio u operativnom sistemu Windows 7 i višim POM-NA-XX-46, V3.0

Uputstva za instaliranje čitača Datalogic Skorpio u operativnom sistemu Windows 7 i višim POM-NA-XX-46, V3.0 POM - Pomoć korisnicima Uputstva za instaliranje čitača Datalogic Skorpio u operativnom sistemu Windows 7 i višim POM-NA-XX-46, V3.0 IZUM, 2016 COBISS, COMARC, COBIB, COLIB, IZUM su zaštićeni znaci u posedu

More information

Iskočni okviri (eng. popup boxes)

Iskočni okviri (eng. popup boxes) 9. JavaScript 2.dio Iskočni okviri, funkcije, petlje, događaji M. Zekić-Sušac 1 Iskočni okviri (eng. popup boxes) U JavaScriptu mogu se koristiti 3 vrste iskočnih okvira: Upozoravajući okviri (eng. alert

More information

ILM implementacija DWH baza u T-mobile

ILM implementacija DWH baza u T-mobile ILM implementacija DWH baza u T-mobile Bojan Šumljak, PS Consultant Hrvoje Dubravica, PS Head Consultant www.snt-world.com 1 Što je ILM? - information Lifecycle Management praksa primjenjivanja pravila

More information

12. Uskladištene procedure (Stored Procedures)

12. Uskladištene procedure (Stored Procedures) 12. Uskladištene procedure (Stored Procedures) Uskladištena procedura je skup SQL iskaza koji su kompajlirani i sačuvani u trenutku njenog kreiranja. Veoma su moćne i preko njih mogu da se izvršavaju sve

More information

SVEUČILIŠTE U ZAGREBU Fakultet prometnih znanosti Zavod za inteligentne transportne sustave Vukelićeva 4, Zagreb, HRVATSKA.

SVEUČILIŠTE U ZAGREBU Fakultet prometnih znanosti Zavod za inteligentne transportne sustave Vukelićeva 4, Zagreb, HRVATSKA. SVEUČILIŠTE U ZAGREBU Fakultet prometnih znanosti Zavod za inteligentne transportne sustave Vukelićeva 4, Zagreb, HRVATSKA Računalstvo Građa i način rada računala Doc. dr. sc. Edouard Ivanjko, dipl.ing.

More information

Osnovne strukture podataka

Osnovne strukture podataka Osnovne strukture podataka Osnovni pojmovi Promenljive i konstante su osnovni oblici podataka sa kojima se operiše u programu Deklaracije listaju spisak promenljivih koje ce se koristiti, određuju kog

More information

modifier returnvaluetype methodname(list of parameters) { // Method body; }

modifier returnvaluetype methodname(list of parameters) { // Method body; } Početna grupa, 28.11.2015. Metodi 1. Metodi opšti oblik metoda: modifier returnvaluetype methodname(list of parameters) // Method body; 2. Ime metoda: početno slovo je malo, a zatim slijede slova, cifre

More information