Your SlideShare is downloading. ×

Programiranje c jezikom

2,384

Published on

PROGRAMSKI JEZIK C

PROGRAMSKI JEZIK C

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
2,384
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
87
Comments
0
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. PROGRAMIRANJEC JEZIKOMNastavni materijal za studente FESB-a.Split, 2005/2006Autor: Ivo Mateljan 1
  • 2. Sadržaj1 Uvod........................................................................................................................................... 52 Matematički i elektronički temelji računarstva ........................................................................ 13 2.1 Izjavna i digitalna logika ................................................................................................... 13 2.2 Brojevni sustavi i računska sposobnost računala .............................................................. 163 Izrada prvog C programa.......................................................................................................... 20 3.1 Strojni, asemblerski i viši programski jezici ..................................................................... 20 3.2 Prvi program u C jeziku .................................................................................................... 21 3.3 Struktura i kompiliranje C programa ................................................................................ 25 3.4 Integrirana razvojna okolina (IDE) ................................................................................... 27 3.5 Usmjeravanje procesa kompiliranja programom nmake................................................... 324 Kodiranje i tipovi podataka ...................................................................................................... 33 4.1 Kodiranje i zapis podataka ................................................................................................ 33 4.2 Memorija ........................................................................................................................... 40 4.3 Prosti tipovi podataka........................................................................................................ 42 4.4 Direktiva #define............................................................................................................... 46 4.5 Specifikatori printf funkcije .............................................................................................. 47 4.6 Pristup podacima pomoću pokazivača .............................................................................. 49 4.7 Unos podataka u memoriju računala ................................................................................. 52 4.8 Inicijalizacija varijabli....................................................................................................... 545 Uvod u programiranje C jezikom............................................................................................. 55 5.1 Postupak izrade programa ................................................................................................. 55 5.2 Algoritamska struktura C programa? ............................................................................... 57 5.3 Funkcije C jezika............................................................................................................... 63 5.4 Zaključak........................................................................................................................... 706 Izrazi i sintaksa C jezika........................................................................................................... 71 6.1 Izrazi.................................................................................................................................. 71 6.2 Automatska i explicitna pretvorba tipova ......................................................................... 78 6.3 Definiranje sinonima tipa pomoću typedef ....................................................................... 81 6.4 Formalni zapis sintakse C-jezika....................................................................................... 817 Proste i strukturalne naredbe C jezika...................................................................................... 87 7.1 Proste naredbe ................................................................................................................... 87 7.2 Strukturalne naredbe ......................................................................................................... 898 Nizovi..................................................................................................................................... 102 8.1 Jednodimenzionalni nizovi.............................................................................................. 102 8.2 Prijenos nizova u funkciju............................................................................................... 107 8.3 Višedimenzionalni nizovi................................................................................................ 1109 Blokovi, moduli i dekompozicija programa........................................................................... 112 9.1 Blokovska struktura programa ........................................................................................ 112 9.2 Funkcionalna dekompozicija programa "od vrha prema dolje" ...................................... 120 9.3 Zaključak......................................................................................................................... 12810 Rad s pokazivačima.............................................................................................................. 129 2
  • 3. 10.1 Tip pokazivača .............................................................................................................. 129 10.2 Operacije s pokazivačima.............................................................................................. 130 10.3 Pokazivači kao argumenti funkcije ............................................................................... 131 10.4 Pokazivači i nizovi ........................................................................................................ 132 10.5 Pokazivači i argumenti funkcije tipa niza ..................................................................... 134 10.6 Patrametri funkcije tipa void pokazivača ...................................................................... 136 10.7 Pokazivači na funkcije .................................................................................................. 137 10.8 Kompleksnost deklaracija ............................................................................................. 139 10.9 Polimorfne funkcije....................................................................................................... 141 10.10 Zaključak..................................................................................................................... 14411 Nizovi znakova - string ........................................................................................................ 146 11.1 Definicija stringa ........................................................................................................... 146 11.2 Standardne funkcije za rad sa stringovima.................................................................... 148 11.3 Ulazno-izlazne operacije sa stringovima....................................................................... 151 11.4 Korisnički definirane ulazne operacije sa stringovima ................................................. 152 11.5 Pretvorba stringa u numeričku vrijednost ..................................................................... 153 11.6 Nizovi stringova ............................................................................................................ 155 11.7 Generator slučajnih brojeva .......................................................................................... 157 11.8 Argumenti komandne linije operativnog sustava.......................................................... 15812 Dinamičko alociranje memorije ........................................................................................... 160 12.1 Funkcije za dinamičko alociranje memorije ................................................................. 160 12.2 Kako se vrši alociranje memorije.................................................................................. 163 12.3 Alociranje višedimenzionalnih nizova .......................................................................... 165 12.4 Standardne funkcije za brzi pristup memoriji ............................................................... 17113 Korisnički definirane strukture podataka ............................................................................. 172 13.1 Struktura (struct)...................................................................................................... 172 13.2 Union – zajednički memorijski objekt za različite tipova podataka.............................. 180 13.3 Bit-polja......................................................................................................................... 181 13.4 Pobrojanji tip (enum).................................................................................................... 182 13.5 Strukture i funkcije za očitanje vremena....................................................................... 18314 Leksički pretprocesor ........................................................................................................... 188 14.1 Direktiva #include ......................................................................................................... 188 14.2 Direktiva #define za makro-supstitucije........................................................................ 188 14.3 String operatori # i ##.................................................................................................. 190 14.4 Direktiva #undef............................................................................................................ 191 14.5 Direktive za uvjetno kompiliranje................................................................................. 19215 Rad s datotekama i tokovima ............................................................................................... 194 15.1 Ulazno-izlazni tokovi .................................................................................................... 194 15.2 Binarne i tekstualne datoteke ........................................................................................ 195 15.3 Pristup datotekama ........................................................................................................ 195 15.4 Formatirano pisanje podataka u datoteku...................................................................... 197 15.5 Formatirano čitanje podataka iz datoteke...................................................................... 199 15.6 Znakovni ulaz/izlaz ....................................................................................................... 200 15.7 Direktni ulaz/izlaz za memorijske objekte .................................................................... 203 15.8 Sekvencijani i proizvoljni pristup datotekama .............................................................. 206 15.9 Funkcije za održavanje datoteka ................................................................................... 20816 Apstraktni tipovi podataka - ADT........................................................................................ 210 3
  • 4. 16.1 Koncept apstraktnog dinamičkog tipa podataka ........................................................... 210 16.2 Stog i STACK ADT ...................................................................................................... 215 16.3 Primjena stoga za proračun izraza postfiksne notacije.................................................. 218 16.4 Red i QUEUE ADT....................................................................................................... 221 16.5 Zaključak....................................................................................................................... 22417 Rekurzija i složenost algoritama .......................................................................................... 225 17.1 Rekurzivne funkcije ...................................................................................................... 225 17.2 Matematička indukcija .................................................................................................. 227 17.3 Kule Hanoja .................................................................................................................. 227 17.4 Metoda - podijeli pa vladaj (Divide and Conquer)........................................................ 230 17.5 Pretvorba rekurzije u iteraciju ....................................................................................... 232 17.6 Standardna bsearch() funkcija ....................................................................................... 234 17.7 Složenost algoritama - "Veliki - O" notacija................................................................. 236 17.8 Sortiranje ....................................................................................................................... 239 17.9 Zaključak....................................................................................................................... 24818 Samoreferentne strukture i liste............................................................................................ 249 18.1 Samoreferentne strukture i lista..................................................................................... 249 18.2 Operacije s vezanom listom .......................................................................................... 250 18.3 Što može biti element liste ............................................................................................ 259 18.4 Lista sa sortiranim redoslijedom elemenata .................................................................. 260 18.5 Implementacija ADT STACK pomoću linearne liste ................................................... 265 18.6 Implementacija ADT QUEUE pomoću vezane liste..................................................... 267 18.7 Dvostruko vezana lista .................................................................................................. 269 18.8 Generički dvostrani red - ADT DEQUEUE.................................................................. 271 18.9 Zaključak....................................................................................................................... 28019 Razgranate strukture - stabla ................................................................................................ 281 19.1 Definicija stabla ............................................................................................................ 281 19.2 Binarno stablo ............................................................................................................... 282 19.3 Interpreter prefiksnih izraza .......................................................................................... 291 19.4 Stabla s proizvoljnim brojem grana .............................................................................. 305 19.5 Prioritetni redovi i hrpe ................................................................................................. 309 19.6 Zaključak....................................................................................................................... 31620 Strukture za brzo traženje podataka .................................................................................... 317 20.1 Tablice simbola i rječnici .............................................................................................. 317 20.2 Hash tablica ................................................................................................................... 318 20.3 BST - binarno stablo traženja........................................................................................ 333 20.4 Crveno-crna stabla......................................................................................................... 344Literatura ................................................................................................................................... 354Dodatak ..................................................................................................................................... 355 Dodatak A - Elementi dijagrama toka................................................................................... 355 Dodatak B - Gramatika C jezika ........................................................................................... 356 Dodatak C - Standardna biblioteka C jezika ......................................................................... 361Index.......................................................................................................................................... 392 4
  • 5. 1 UvodNaglasci: • Š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 premaprogramima koje izrađuje čovjek. Sastavni dijelovi računala nazivaju se hardver, a programi injihova dokumentacija nazivaju se softver. Prvotno su računala služila za obavljanje numeričkihprorač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šavajurazličiti problemi. Bit će opisana i unutarnja građa računala. Za pisanje programa koristit će seprogramski jeziku C i asemblerski jezik.Što je program? Program je zapis operacija koje računalo treba obaviti. Taj zapis može biti u oblikuizvršnog programa ili u obliku izvornog programa. Izvršni program sadrži kôd operacija kojeizvršava stroj računala, pa se naziva i strojni program. Izvorni program se zapisuje simboličkimjezikom koji se naziva programski jezik. Prevođenje izvornog programa u strojni program vršise 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 seobrađuju kontinuirani elektronički signali. Digitalnim računalom se obrađuju, prenose i pamtediskretni elektronički signali koji u jednom trenutku mogu imati samo jedno od dva mogućastanja. Ta stanja se označavaju znamenkama 0 i 1, odatle i naziv digitalna računala (eng. digitznač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 imatisamo 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 zanizove bitova koristiti nazive iz Tablice 1.1. U binarnom nizu često se označava redoslijed bitova. Kratica LSB označava bit najmanjegznačaja (eng. least significant bit), a MSB označava bit najvećeg značaja (eng. most significantbit). Primjer je dan na slici 1.1. Bit je naziv za binarnu znamenku Nibl je naziv za skupinu od četiri bita (eng. nibble) s kojom se operira kao s cjelinom. Bajt ili oktet je naziv za skupinu od osam bita (eng. byte) s kojom se operira kao s cjelinom. Riječ je naziv za skupinu od više bajta (eng. word) s kojom se operira kao s cjelinom. Kod mikro računala za riječ se uzima skupina od 2 bajta. Kod većih računala za riječ se uzima skupina od 4 ili 8 bajta. Tablica 1.1 Nazivi temeljnih binarnih nizova MSB LSB značaj bitova 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 položaj bita 1 0 0 0 1 1 1 1 1 0 1 0 1 1 0 1 binarni niz nibl 3 nibl 2 nibl 1 nibl 0 niz nibla bajt 1 bajt 0 niz bajta Riječ riječ Slika 1.1 Označavanje binarnog nizaZa označavanje većih nizova koriste se prefiksi: k (kilo) ⇔ × 1024 M (mega) ⇔ k × 1024 G (giga) ⇔ M × 1024 T (tera) ⇔ G × 1024Primjerice, 2 kB (kilobajta) = 2048 bajta, 3 Mb (megabita) = 3145728 bita. Digitalno računalo može pamtiti i izvršavati programe, te dobavljati, pamtiti i prikazivatirazličite informacije. Te informacije, koje su na prikladan način pohranjene u računalu, suprogramski podaci. broj bita – n broj kombinacija – 2n 2 4 3 8 4 16 8 256 16 65536 32 4294967296 Tablica 1.2 Broj kombinacija s n bita Često se računala klasificiraju kao 8-bitna, 16-bitna, 32-bitna ili 64-bitna. Pod time sepodrazumijeva da n-bitno računalo može operirati s nizom od n bita kao s cjelinom. Broj bitakoji 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 8elemenata 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 odmaksimalno 2n elemenata. Tablica 1.2 pokazuje da se udvostručenjem broja bitova značajnopoveć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 istovremenoprenosi i obrađuje više bita. Kod svih računala usvojeno je da najmanja jedinica digitalneinformacije, 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 procesorskajedinica (CPU – central processing unit) - kontrolira izvršenje programa i aritmetičko-logičkihoperacija. CPU je kod mikro i mini računala izveden kao jedinstveni integrirani elektroničkisklop (čip) i naziva se mikroprocesor. Uobičajeno je koristiti naziv procesor, bilo da se radi omikroprocesoru ili o skupini čipova koji obavljaju funkcije CPU, a programe koji se izvršavajuu 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 operativnomstanju. U memoriji se nalazi programski kôd i podaci s kojima operira procesor na temeljunaredbi sadržanih u programskom kôdu. Memorija je napravljena od poluvodičkih elemenata ukoje procesor može upisati i iz kojih može čitati digitalne informacije. Ta memorija se nazivaRAM (eng. random access memory). Sa programerskog stajališta RAM predstavlja linearnouređen prostor u kojem se istovremeno može pristupiti grupi od 8 bita digitalne informacije (1bajt). 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 pamtitidigitalnu informaciju, a naziva se ROM (eng. read-only memory). U ROM-u je upisan programkoji služi pokretanju osnovnih funkcija računala. U samom procesoru ugrađeno je nekolikomanjih memorijskih jedinica koje se nazivaju registri. Registri služe za privremeni smještajprogramskog kôda i podataka iz radne memorije, te rezultata aritmetičko-logičkih operacije kojese izvršavaju u samom procesoru. Broj bita koji može biti pohranjen u jednom registru naziva seriječ procesora. Kod većine današnjih PC računala riječ procesora sadrži 32 bita (4 bajta), pa sekaže da su to 32-bitna računala. Vanjska memorija - služi za trajnu pohranu podataka. U tu svrhu koriste se magnetski ioptički mediji (tvrdi disk, savitljive diskete, magnetske trake, optički diskovi,..). Podaci se nanjima 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 priuključenju električnog napajanja, kontrolu i redoslijed izvođenja programa, kontrolu korištenjamemorije, 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. Onse, jednim dijelom, trajno nalazi u ROM memoriji računala. Programi s novakvim svojstvomnazivaju se rezidentni programi. Svi ostali programi moraju se prije izvršenja upisati umemoriju računala. Može se izvršiti funkcionalna podjela softvera na sistemski i aplikativni softver. Usistemski softver spadaju programi operativnog sustava, razni jezični procesori (interpreteri,kompilatori, emulatori itd.), programi za testiranje programa (debugger), servisni i uslužniprogrami, 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đedo 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šenjeproblema 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čnombroju koraka i u konačnom vremenskom intervalu. Pravila kako se piše algoritam nisu strogoodređena. Algoritam se može definirati običnim govornim jezikom, tablicama i matematičkimformulama 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 jeizvršitelj kojem upute, iskazane nizom naredbi, nisu dovoljno jasne, jer ono ne zna (1) gdje senalazi 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 preciziranjempojedinog 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 suoznačene s 1.1, 1.2,..1.5. Naredbe iskazane u koracima 1.1 do 1.4 su same po sebi jasne. Korak1.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 "izmjerirazinu 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 tipnaredbe naziva iteracija ili petlja. Uobičajeno se kaže da petlja ima zaglavlje, u kojem seispituje uvjet ponavljanja petlje (dok je razina tlaka manja od 1,6 ponavljaj), i tijelo petlje, kojeobuhvaća jednu ili više naredbi koje treba ponavljati. Naziv petlja podsjeća na činjenicu da seuvijek 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 nisuposebno numerirane jer su one povezane uz zaglavlje petlje, a izvršavaju se u kao jedinstvenasložena naredba. Uobičajeno se niz naredbi koji predstavljaju jedinstvenu složenu naredbunaziva i blok naredbi ili samo blok. Uvjet ponavljanja petlje je izjava: "razina tlaka manja od 1,6". Odgovor na ovu izjavu možebiti "Da" ili "Ne", ovisno o trenutno izmjerenoj razini tlaka. Ako je odgovor "Da", kažemo da jeispunjen uvjet ponavljanja petlje. Računarska se znanost koristi znanjima matematičke logike. Utom kontekstu ova izjava predstavlja tzv. predikatni izraz koji može imati samo dvije logičkevrijednosti: "istina" ili "laž", pa se kaže da je uvjet održanja petlje ispunjen ako je predikatniizraz 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 problemispravno riješen, moguće je da se opet potkrade neka greška. To se obično događa kada se nepredvide sve moguće situacije, odnosno stanja u koja se može doći. Primjerice, gornji algoritamnije predvidio slučaj da je guma probušena. Kakav bi razvoj događaja tada bio, ako bi sedosljedno poštovao postupak iz koraka 1.5? Pošto je kod probušene gume razina tlaka uvijekmanja od 1,6, ispada da bi tada izvršitelj naredbi ponavljao postupak pumpanja gumebeskonač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 tlakaU 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 naredbiOvaj se tip naredbe zove uvjetna selekcija ili grananje, jer se nakon ispitivanja logičkog uvjetavrš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 naredbuNjome se određuje izvršenje neke naredbe samo ako je ispunjen neki uvjet.Koristeći naredbe selekcije, algoritam se može zapisati u obliku: 1. ispitaj ispravnost rezervnog točka, 1.1 otvori prtljažnik 1.1.1. uzmi najmanji od tri ključa 1.1.2. gurni ključ u bravu i lagano ga okreni na desno 1.1.3. podigni vrata prtljažnika 9
  • 10. 1.2. izvadi rezervni točak 1.2.1. podigni tapetu 1.2.2.ako je točak pričvršćen vijkom onda odvij vijak 1.2 .2. izvadi točak 1.3. uzmi kutiju s alatom 1.4. ispitaj razinu tlaka 1.4.1. izvadi mjerač tlaka iz kutije alata 1.4.2. postavi ga na zračnicu točka 1.4.3. očitaj razinu tlaka 1.5. ako je tlak manji od 0,1 onda 1.5.1. provjeri da li je guma probušena 1.5.2. ako je guma probušena onda odnesi točak na popravak inače, ako je tlak manji od 1,6 onda 1.5.3. otvori prednji poklopac motora 1.5.4. uzmi zračnu pumpu 1.5.5. dok je tlak < 1,6 ponavljaj postavi crijevo pumpe na zračnicu dvadeset puta pritisni pumpu na zračnicu postavi mjerač tlaka ispitaj razinu tlakaOč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 izvornogprograma. Kako se to radi bit će objašnjeno u sljedećim poglavljima. Važno je uočiti da su uzapisu 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 objektiSvaki proces rezultira promjenom stanja ili atributa objekata na koje procesi djeluju. Uobičajenose stanje nekog promjenljivog objekta označava kao varijabla koja ima neko ime. U računalu sestanje objekta pamti u memoriji računala pa se algoritamske varijable mora tretirati kaomemorijske 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 paknapiše iskaz: x = 2*x +5;on predstavlja proces u kojem se najprije iz memorije očitava vrijednost memorijskog objekta xzapisanog na desnoj strani znaka =. Zatim se ta vrijednost množi s 2 i pribraja joj se numeričkavrijednost konstante 5. Time je dobivena numerička vrijednost izraza s desne strane znaka =. Tase vrijednost zatim pridjeljuje memorijskom objektu s lijeve strane znaka =. Konačni je rezultatovog procesa da je varijabli x pridijeljena vrijednost 15. Ako bi prethodni iskaz tretirali kaomatematički iskaz, on bi predstavljao jednadžbu s jednom varijablom, koja uvjetuje da jevrijednost varijable x jednaka 5. 10
  • 11. Znak = u C jeziku ne predstavlja znak jednakosti, kao u matematici već operator pridjelevrijednosti. Njegova upotreba označava naredbu da se vrijednost memorijskog objekta s lijevestrane znaka = postavi na vrijednost izraza koji je zapisan s desne strane znaka =. Takovenaredbe se zovu naredbe pridjele vrijednosti. Zbog ove nekonzistentnosti upotrebe znaka = umatematici u odnosu na upotrebu u nekim programskim jezicima (C, Basic, Fortan, Java) čestose 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 uračunalu. Naime, procesor može vršiti operacije samo nad podacima koji se nalaze u registrimaprocesora, pa je prije svake operacije s memorijskim objektima prethodno potrebno njihovsadržaj (vrijednost) prenijeti u registre procesora, a nakon obavljene operacije se sadržaj izregistra, koji sadrži rezultat operacije, prebacuje u memorijski objekt označen s lijeve straneoperatora 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 vrloneefikasna i skupa upotreba računala. Međutim, malo iskusniji programer bi prema gornjemalgoritmu mogao lako napraviti program kojim se animirano simulira proces zamjene točka. Toje moguće jer, iako je prethodni algoritam apstraktan, on specificira procese u obliku koji semože ostvariti računarskim programom. Apstrakcija je temeljna mentalna aktivnost programiranja. U računarskoj se terminologijipod pojmom apstrakcije podrazumijeva prikladan način zapisa o objektima i procesima koje seobrađuje pomoću računala, a da se pri tome ne vodi računa o tome kako je izvršena stvarnaračunarska implementacija, niti objekta niti procesa. Važna je samo ona pojavnost koja jeodređena apstrakcijom. Algoritam zapisan programskim jezikom predstavlja apstrakcijustrojnog koda, a algoritam zapisan prirodnim jezikom predstavlja apstrakciju programskogjezika. Programski jezik služi da se formalnim jezikom zapiše procese i stanje memorijskihobjekata 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 utoku izvršenja programa moguće je daljnje usložnjavanje apstrakcije. Primjerice, korisnik CADprograma 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šitisljedeć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 sunaredbe 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 predstavljajuatribute nekog objekta, a funkcije predstavljaju metode pomoću kojih objekt komunicira sdrugim objektima. Specifikacije atributa i metoda određuju klase objekata. Kod funkcionalnihse jezika ne koristi temeljna imperativna naredba pridjele vrijednosti, već se svameđ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 predikatnimlogičkim izrazima i funkcijama. Naglasak je na zapisu onoga “što program treba izvršiti”, zarazliku 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 sedobro poznaje programski jezik i programske algoritme za efikasno korištenje računarskihresursa.O tome će biti riječi u sljedećim poglavljima. 12
  • 13. 2 Matematički i elektronički temeljiračunarstvaNaglasci: • Izjavna logika • Logičke funkcije i predikati • Booleova logika • Temeljni digitalni sklopovi • Brojevni sustavi2.1 Izjavna i digitalna logika Bit će navedeni osnovni pojmovi potrebni za razumijevanje izjavne logike (ili propozicijskelogike), koji se intenzivno koristi u programiranju, i digitalne logike koja je temelj izgradnjedigitalnog računala. Osnovni objekt kojeg proučava izjavna logika je elementarna izjava. Ona može imati samojedno svojstvo - njome se izriče "laž" ili "istina". Primjerice, izjava "osam je veće od sedam" je istinita, a izjava "broj sto je djeljiv sasedam" je laž. Pri označavanju izjava koristit će se slovo T (true) za istinitu izjavu i F (false) zalažnu izjavu. Rečenica "broj x je veći od broja y" ne predstavlja izjavu jer njena istinitost ovisi o veličinibrojeva x i y. Ako se umjesto x i y uvrste brojevi dobije se izjava. Ovakve rečenice se nazivajuizjavne funkcije, a za x i y se kaže da su (predmetne) varijable. Odnos među varijablama, kojegizjavna funkcija izriče, naziva se predikat. Označi li se u prethodnom primjeru predikat " ... jeveć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 seuz izjavne funkcije primijene neodređene zamjenice svaki (oznaka ∀ koja se naziva univerzalnikvantifikator) ili neki (oznaka ∃ koja se naziva egzistencijalni kvantifikator). ∃x se čita i "postojix". 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čkuvrijednost nazivaju se logičke varijable. U programiranju se često koriste izjavne funkcije iskazane tzv. relacijskim izrazimaprimjerice 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 bje 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 raznilogič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 moguiskazati Booleovom algebrom jer se djelovanje operatora implikacije i ekvivalencije možeizraziti 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 & x2. Zakon asocijacije 14
  • 15. x ∨ (y ∨ z) ≡ (x ∨ y) ∨ z x & (y & z) ≡ (x & y) & z3. Zakon idempotentnosti x ∨ x ≡ x x & x ≡ x4. 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 ∨ ¬z6. Zakon dvostruke negacije ¬¬x ≡ x Booleova logika ima veliku primjenu u programiranju i posebno pri projektiranju sklopovadigitalnog računala, jer se gotovo svi potrebni sklopovi digitalnog računala mogu realiziratipomoć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 naponomi 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 kojimaizlaz 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žeopisati strujnim krugom u kojem su serijski spojene žarulja, sklopka A i sklopka B. Žarulja ćezasvijetliti kada proteče struja, a to je moguće samo ako ako su obje sklopke uključene, odnosnoizlaz je 1 samo ako su varijable A i B jednake 1. Kod sklopa ILI dovoljno je uključiti jednusklopku da bi zasvijetlila žarulja. Očito sklop I obavlja logičku funkciju konjunkcije, a sklop ILIobavlja logičku funkciju disjunkcije. U digitalnom se računalu pomoću navedenih sklopovaobrađ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 dase 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 ilimatematička logika često koristi pojam digitalna logika. U digitalnoj je tehnici uobičajena primjena logičkih operatora na nizove bitova. Tada sepodrazumijeva da se logičke operacije provode nad bitovima jednake značajnosti. Takvelogičke operacije se nazivaju bit-značajne operacije.Primjer: bit značajnom konjunkcijom dva binarna niza A i B dobije se niz C: 7 6 5 4 3 2 1 0 bit ----------------------- 1 1 0 0 1 1 1 1 = A 0 0 0 0 0 1 0 0 = B ---------------------- A & B = 0 0 0 0 0 1 0 0 = CU nizu C jedino bit 2 može biti jednak 1 i to samo ako je i u nizu A taj bit jednak 1. Ovo ječesto korišten postupak da se ispita da li je neki bit u nizu jednak 1 ili 0. Obično se niz B naziva"maska" za ispitivanje bitova u nizu A. Pored prije navedenih Booleovih logičkih operacija u digitalnoj se tehnici često koristi bit-značajna operacija koja se naziva ekskluzivna disjunkcija ili ekskluzivno ILI. Označava seznakom ⊕ ili XOR. Ima značaj zbrajanja po modulu 2, a njeno korištenje u programiranju bit ćepojašnjeno kasnije. A XOR B = A ⊕ B = (¬A & B) ∨ (A & ¬B) A B A ⊕ B 0 0 0 0 1 1 A ⊕ B = (¬A & B) ∨ (A & ¬B) 1 0 1 1 1 0 Slika 2.2 Definicijska tablica ekskluzivne disjunkcije i simbol digitalnog XOR-sklopa2.2 Brojevni sustavi i računska sposobnost računala U programskim jezicima operacije s brojevima se najčešće zapisuju u decimalnombrojevnom sustavu, jer je čovjek naviknut na rad s decimalnim brojevima. U računalu se pakračunske operacije vrše u binarnom brojevnom sustavu.2.2.1 Binarni brojevni sustavSasvim općenito, numerička vrijednost broja Z, koji je u pozicionoj notaciji zapisanznamenkama: zn-1....z1z0, u brojevnom sustavu baze x, računa se prema izrazu: n −1 Z = ( zn −1 .... z1z0 ) x = ∑ zi ⋅ x i i =0Decimalni brojevni sustav je definiran bazom x=10 i znamenkama zi ε{0,1,2,3,4,5,6,7,8,9},primjerice iznos broja 765 je jednak 7⋅102 + 6⋅101 + 5⋅100 . 16
  • 17. Binarni brojevni sustav je definiran bazom x=2 i binarnim znamenkama zi ∈{0,1}.Primjerice, iznos binarnog broja 1011 odgovara iznosu broja 11 u decimalnom sustavu,jer je (1011)2 = 1⋅23 + 0⋅22 + 1⋅21 +1⋅20 = 8 + 0 + 2 + 1 = (11)10. Općenito vrijedi da se s binarnim nizom od n bita može kodirati pozitivni cijeli brojmaksimalnog iznosa 2n -1, što odgovara broju različitih kombinacija binarnog niza duljine numanjenom za jedan (i nula je broj!). Za pozitivne cijele brojeve koristi se i nazivi kardinalnibrojevi i nepredznačeni cijeli brojevi. U binarnom brojevnom sustavu se mogu izvoditi osnovne računske operacije kao i udecimalnom brojevnom sustavu. Binarno zbrajanje se obavlja kao i decimalno zbrajanje, osimšto se prijenos na slijedeće značajnije mjesto ne obavlja nakon zbroja 10, već nakon 2 (1+1).Primjer: 1 1 ← prijenos 1 0 1 = 510 1 1 1 = 710 + 0 1 0 = 210 + 1 0 1 = 510 ----------------- ---------------------- 1 1 1 = 710 1 1 0 0 = 1210 Ukoliko se zbrajanje izvodi bez prijenosa ta operacija se naziva zbrajanje po modulu 2. Ulogičkom smislu ta operacija je ekvivalentna ekskluzivnoj disjunkciji (XOR). Operacijuzbrajanja LSB bitova može se prikazati tablicom istinitosti 2.2: A B zbroj = A ⊕ B prijenos = A & B 0 0 0 0 0 1 1 0 1 0 1 0 1 1 0 1 Tablica 2.2. Tablica istinitosti za polu-zbrajalo A B Donos Zbroj prijenos 0 0 0 0 0 0 0 1 1 0 0 1 0 1 0 0 1 1 0 1 1 0 0 1 0 1 0 1 0 1 1 1 0 0 1 1 1 1 1 1 Tablica 2.3. Tablica istinitosti za potpuno zbrajalo Digitalni sklop koji realizira ovu funciju naziva se poluzbrajalo (half-adder) i prikazan je naslici 2.3(a). Pri zbrajanju ostalih bitove treba pribrojiti i bit donosa kao u tablici 2.3. Digitalnisklop koji realizira ovu funkciju naziva se potpuno zbrajalo (full-adder). Prikazan je na slici2.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 zbrajalobitova 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 oveoperacije je da ako se na neki binarni niz A dva puta uzastopno primjeni bit-značajnaekskluzivna 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 = ADakle, prvo djelovanje je šifriranje, a drugo djelovanje je dešifriranje originalnog niza. Oduzimanje broja se može izvesti kao zbrajanje negativne vrijednosti broja. Kako sekodiraju negativni brojevi bit će pokazano kasnije. Binarno množenja se vrši tako da se djelomičan umnožak pomiče za jedno mjesto ulijevopri svakom uzimanju idućeg množitelja. Ako je množitelj 0, djelomični umnožak je 0, a ako jemnožitelj 1, djelomični umnožak jednak je množeniku. Primjer: 5 x 5 = 25 5 x 10 = 50 101 (5) 101 (5) 101 (5) 1010 (10) ------------ --------------- 101 000 000 101 101 000 ------------ 101 11001 (25) ---------------- 110010 (50) Binarno dijeljenje se u računalu izvodi primjenom binarnog množenja i oduzimanja, na istinačin kao i kod decimalnih brojeva. Navedene operacije su ugrađene u skup naredbi većinedanašnjih procesora. 18
  • 19. Još dvije operacije su specifične za rad s nizovima bitova. To su operacije logičkogposmaka bitova u lijevo ili u desno (podrazumijeva se LSB na desnoj strani niza), a označavajuse 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 posmakbitova jedno mjesto udesno odgovara dijeljenju kardinalnih brojeva s 2. Na prazna mjesta sepostavljaju nule.Primjer: 0011 SHL 1 ≡ 0110 odgovara 3 * 2 = 6 0011 SHL 2 ≡ 1100 odgovara 3 * 4 = 12 1110 SHR 1 ≡ 0111 odgovara 14 / 2 = 72.2.2 Oktalni i heksadecimalni brojevni sustavi U višim programskim se jezicima rijetko koristi zapis broja u binarnom obliku jer čovjekteško pamti veće nizove "nula i jedinica". Radije se koristi oktalni ili heksadecimalni brojevnisustav. U oktalnom brojevnom sustavu koristi se 8 znamenki: 01234567, a baza brojevnog sustavaje x=23=8. Oktalnim brojem jednostavno se označava niz od 3 bita, jer je s binarnim nizom od 3bita moguće kodirati 8 znamenki oktalnog brojevnog sustava: bit 0 0 1 0 1 0 1 0 1 bit 1 0 0 1 1 0 0 1 1 bit 2 0 0 0 0 1 1 1 1 ----------------------- 0 1 2 3 4 5 6 7 znamenke oktalnog brojevnog sustavaTo omogućuje pregledniji zapis većih binarnih nizova, primjerice 1001000101112 = 44278,a koristi se pravilo grupiranja po 3 bita: 100=4, 100=4, 010=2, 111=7. U heksadecimalnom brojevnom sustavu koristi se 16 znamenki: 0123456789ABCDEF, abaza brojevnog sustava iznosi x=16. Za kombinacije od 10 do 15 upotrebljena su prva slovaabecede, 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 od4 bita može kodirati 16 znamenki heksadecimalnog brojevnog sustava: bit 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 bit 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 bit 2 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 bit 3 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 --------------------------------------- 0 1 2 3 4 5 6 7 8 9 A B C D E F heksadecimalne znamenkeTo omogućava pregledniji zapis većih binarnih nizova, primjerice: 10010001011111102 = 917E16,a koristi se pravilo grupiranja po 4 bita: 1001=9, 0001=1, 0111=7, 1110=E. U programskim jezicima se uvode posebna leksička pravila za zapis konstanti u pojedinombrojevnom sustavu. Ta pravila će biti opisana u četvrtom poglavlju. 19
  • 20. 3 Izrada prvog C programaNaglasci: • 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 • MakefileU ovom poglavlju opisani su programi koji se koriste u razvoju programa. Njihova upotreba sedemonstrira 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, kojedobavlja iz memorije računala. Skup svih strojnih naredbi procesora naziva se strojni jezik, askup naredbi kojima se neposredno izvršava neka zadana operacija u računalu naziva se strojniprogram. Strojne naredbe je veoma teško pamtiti. Zbog toga se koristi simboličko zapisivanje naredbiasemblerskim jezikom, u kojem se naredbe zapisuju s kraticama riječi iz engleskog jezika.Primjerice, naredba da se broj, koji se nalazi u registru ax procesora Intel 8086, uveća za jedan,glasi: strojni jezik 01000000 asemblerski jezik inc ax (inc je kratica od increment)Dakle, binarni strojni kôd je zamijenjen simboličkim zapisom koje procesor ne razumije. Zaprevođenje asemblerskog zapisa u strojni program koristi se program koji se naziva asembler. Viši programski jezici omogućuju jednostavnije programiranje uvođenjem simboličkihnaredbi 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 funkcijesin(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 tekstakoji se nazivaju editori. Izvorni program u C jeziku se obično pohranjuje kao datoteka simenom koje završava znakovima “.c” Programi za prevođenje izvornog programa u izvršni program mogu biti realizirani kaointerpreteri ili kao kompilatori (eng. compiler), a razlika među njima je u načinu kako se izvorniprogram prevodi u izvršni program. Kompilator analizira izvorni program i stvara strojni kôd, koji pohranjuje u tzv. objektnudatoteku (eng. object file). Kod MS-DOS računala ime objektne datoteke završava s “.obj”, akod Unix računala s “.o”. Iako objektna datoteka sadrži strojni kôd, on još nije pogodan zaizvođ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 zovepovezivač 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, MicrosoftVisual 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 programatako 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 operativnisustav. Programi se mogu izvršavati i pomoću specijalnih programa koji služe za testiranjeizvrš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 ponaredbu, i nakon svakog koraka moguće je pratiti stanje varijabli (memorije) i registaraprocesora. Kod naprednijih dibagera moguće je pratiti izvršenje programa na razini izvornogkoda, dok se kod jednostavnijih dibagera i monitora izvršenje programa može pratiti na razinistrojnog koda. Interpreter prevodi izvorni kod u niz izvršnih naredbi koje se obično izvršavaju unutarsamog programa interpretera. Skup izvršnih naredbi interpretera obično se naziva "virtuelnistroj" (primjerice Java VM ili Microsoft CLR). Program za "interpretiranje", pomoću virtuelnogstroja, može biti i odvojeni program. U tom slučaju interpreter ima funkciju kompilatora kojigenerira datoteke s nizom naredbi virtuelnog stroja. U razvoju programa koristi se veliki broj programa, tzv. softverskih alata, koji olakšavajurazvoj programa i pisanja dokumentacije. Primjerice, na Unix-u se koriste: make - program zakontrolu procesa kompiliranja, grep - program za pretraživanje datoteka, profiler - program zaanalizu izvršenja programa, diff – program za utvrđivanje razlika među izvornim datotekama,patch – program za automatsko unošenje izmjena u izvorne datoteke. Kod Windowsoperativnog sustava više su u upotrebi programi koji se nazivaju integrirana razvojna okolina(eng. IDE – integrated developement environment) kod kojih se pod jedinstvenim grafičkimsučeljem koordinira rad svih faza razvoja programa – editiranje izvornog koda, kompiliranje,dibagiranje i profiliranje koda. U okviru IDE-a integriran je i pristup kompletnoj dokumentacijikompilatora i programskih biblioteka. Bez sumnje, najpopularniji program ovog tipa jeMicrosoft Visual Studio.3.2 Prvi program u C jezikuGotovo 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 ilijoš jednostavnije komandna konzola. Karakteristika ispisa u konzoli je da se može jedino vršitiispis teksta, i to u po principu da se ispisuje redak po redak, od vrha konzole na dolje. U konzolise mogu zadavati komande operativnom sustavu u retku koji uobičajeno započinje s oznakomtekuć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 trikoraka: 1. Pomoću editora stvara se tekstualna datoteka, imena "hello1.c", koja sadrži prikazani izvorni kôd programa. 2. Pomoću kompilatora se izvorni kôd programa prevodi u objektni, a zatim i u izvršni kôd. Ako kompilator dojavi da u izvornom kôdu postoje leksičke ili sintaktičke pogreške, ponavlja se korak 1 i ispravljaju pogreške. 3. Izvršenje programa se zadaje u komandnoj liniji.Primjer za OS Windows:c:> edit hello.c ↵ - poziv editora edit (↵ je tipka enter) i unos izvornog programa u datoteku hello.cc:> cl hello.c ↵ - poziv kompilatora (Microsoft-program cl.exe) koji stvara izvršnu datoteku hello.exec:> hello ↵ - komanda za izvršenje programa hello.exeHello world! - rezultat izvršenja programa Slika 3.1 Izgled komandnog prozora u Windows operativnom sustavuPrimjer 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 helloHello 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 usvakom C programu, jer predstavlja mjesto početka izvršenja programa. Programer možedefinirati nove funkcije, svaku s jedinstvenim imenom, a mogu se koristiti i prethodnodefinirane funkcije iz standardne biblioteke funkcija C jezika. Radnje koje obavlja neka funkcija zapisuju se unutar tijela funkcije. Tijelo funkcije jeomeđeno vitičastim zagradama. U ovom je slučaju u tijelu funkcije je iskaz koji predstavljanaredbu da se pomoću standardne C funkcije printf(), na standardnoj izlaznoj jedinici,ispiše poruka "Hello World!". Pojedini dijelovi programa "hello1.c" imaju sljedeći značaj: /* Prvi C program. */ Tekst omeđen znakovima /* i */ predstavlja komentar. Kompilator ne analizira komentare, već ih tretira kao umetnuto "prazno" mjesto. #include <stdio.h> #include predstavlja pretprocesorsku direktivu. Ona označava da u proces kompiliranja treba uključiti sadržaj datoteke imena "stdio.h". Ta datoteka sadrži deklaracije funkcija iz standardne biblioteke C-jezika. int main() Ovo je zaglavlje funkcije imena main. int označava tip vrijednosti (cijeli broj) koji vraća funkcija na kraju svog izvršenja (u ovom programu to nema nikakvi značaj). { { označava početak tijela funkcije main. printf("Hello world!n"); Ovo je naredba za poziv standardne funkcije printf(), kojom se ispisuje niz znakova (string) koji je argument ove funkcije. n predstavlja oznaku za prijelaz u novi red ispisa. Znak točka-zarez označava kraj naredbe. Return 0; main() "vraća" vrijednost 0, što se uobičajeno koristi kao oznaka uspješnog završetka programa. } } označava kraja tijela funkcije main. U objašnjenju programskih iskaza korišteni su neki novi pojmovi (deklaracija, standardnabiblioteka, 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 programsintaktički pogrešan. Primjerice, ukoliko u prethodnom programu nije otkucana točka-zarez izanaredbe 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škau sljedećem obliku: C:>cl hello.cMicrosoft (R) 32-bit C/C Optimizing Compiler Ver.12.00.8168 for 80x86Copyright (C) Microsoft Corp 1984-1998. All rights reserved.hello.chello.c(5) : error C2143: syntax error : missing ; before returnPoruka 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 napisananaredba u četvrtom retku. Razlog tome je pravilo C jezika po kojem se naredba može pisati uviše redaka, a stvarni kraj naredbe predstavlja znak točka-zarez. Pošto kompilator nije pronašaotočku-zarez u četvrtom retku, kompiliranje je nastavljeno s petim retkom i tek tada je utvrđenoda 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 kaoposebna funkcija s prikladnim imenom. Primjerice, prethodni program se može napisati pomoćudvije 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 worldn"); } int main() { Hello(); return 0; } Za razumijevanje ovog programa potrebno je upoznati pravila za definiranje i pozivanjefunkcija. Funkcija se definira zaglavljem i tijelom funkcije. Zaglavlje funkcije se zapisuje na sljedeći način: ime funkcije se zapisuje nizom znakovakoji sadrži slova, znamenke i znak _, ali uz uvjet da je prvi znak niza slovo ili _. Ispred imenafunkcije se navodi vrijednost koju funkcija vraća. Ako funkcija ne vraća nikakovu vrijednosttada se ispred imena piše riječ void, koja znači “ništa ili nevažno je”. Ovakve funkcije senazivaju procedure. U njima se ne mora koristiti naredba return, već one završavaju kada seizvrši posljednje definirana naredba. Iza imena funkcije se, u zagradama, navode formalniargumenti funkcije, ako ih ima. Kasnije će biti objašnjeno kako se definiraju i koriste argumentifunkcije. 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 definiraneunutar funkcije). Zapisuje se na način da se prvo napiše ime funkcije, a zatim obvezno zagrade iargumenti funkcije, ako su prethodno definirani. Primjerice, u funkciji main() iskazHello(); predstavlja poziv funkcije Hello(). Poziv funkcije pokreće izvršenje naredbi kojesu definirane u tijelu funkcije Hello(), tj. poziva se funkcija printf() s argumentom"Hello Worldn". Nakon izvršenja te naredbe program se vraća, u funkciju main() naizvršenje prve naredbe koja je napisana iza poziva funkcije Hello(). Funkcija iz koje sepokreće izvršenje pozvane funkcije naziva se pozivna funkcija. U prethodnom primjeru funkcija Hello() je definirana prije funkcije main(). Tajredoslijed 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 deklaracijefunkcija). 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 uprogramu iza mjesta njenog poziva ili u drugoj datoteci. U skladu s ovim pravilom dozvoljenoje 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 worldn"); }U C jeziku se programi mogu zapisati u više odvojenih datoteka. Primjerice, prethodni programse 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 */ /* Datoteka: hellosub.c */ void Hello(); #include <stdio.h> int main() void Hello() { { Hello(); printf("Hello worldn"); return 0; } }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 komandnompreklopkom /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 pretprocesorkompilatora unosi sve deklaracije iz datoteke "stdio.h" u datoteku "hellosub.c". Zatimkompilator 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 strojnijezik 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 programlink.exe, kojeg skriveno poziva program cl.exe. Dobra strana odvajanja programa u više datoteka je da se ne mora uvijek kompilirati svedatoteke, 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šiprijevod u objektne datoteke, tj. c:> cl /c hellomain.c c:> cl /c hellosub.cTime se dobiju dvije objektne datoteke: "hellomain.obj" i "hellosub.obj". Povezivanje ovihdatoteka 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žeubrzati 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 formiranjaizvrš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" dokumentacijeo programskom jeziku, standardnim bibliotekama i programskom sučelju prema operativnomsustavu (Win32 API). Pomoću njega se mogu izrađivati programi s grafičkim korisničkimsučeljem i programi za konzolni rad.Nakon poziva programa dobije se IDE Visual Studio prikazan na slici 3.3. 27
  • 28. Slika 3.3 Izgled Visual Studio pri pokretanju programa Najprije će biti opisano kako se formira projekt za konzolni tip programa. Prije pokretanjaprograma, neka se u direktoriju c:My DocumentsC2002pog2 nalaze izvorne datotekehellomain.c i hellosub.c. Pokretanjem komande menija: File-New-Project, dobije dijalog za postavljanje novogprojekta. 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 ihello.sln (datoteka ekstenzije .sln sadrži opis radne okoline, a datoteka ekstenzije .vcproj sadržiopis 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 zarad 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.9odabiremo datoteke "hellomain.c" i "hellosub.c", iz direktorija c:My Documentscpp-2001pog3. Dobije se izgled radne okoline kao na slici 3.10. Pokretanjem komande: Build – Build hello.exe vrši se proces kompiliranja i linkanja. Akose izvrši bez greške, nastaje izvršni program imena hello.exe. Program hello.exe se možepokrenuti pozivom komande Build – Execute hello.exe (ili pritiskom tipki Ctrl+F5). 28
  • 29. Slika 3.4 Dijalog za postavljanje novog projektaSlika 3.5 Dijalog za postavljanje tipa Win32-Console projekta 29
  • 30. Slika 3.6 Dijalog koji izvještava o postavkama novog projektaSlika 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žeupravljati pomoću program imena nmake (make na UNIX-u) i specifikacije koja se zapisuje udatoteci koji se obično naziva makefile. Za prethodni primjer datoteka makefile može bitinapisana 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.cAko se ovu datoteku spremi pod imenom makefile, dovoljno je u komandnoj liniji otkucati: c:> nmakei biti će izvršen cijeli postupak kompiliranja i linkanja izvršnog programa. Ako se pak ovadatoteka zapiše pod nekim drugim imenom, primjerice "hello.mak", u komandnoj liniji, izapreklopke –f treba zadati i ime datoteke, tj. c:>nmake –fhello.makKako 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 promijenisadržaj "hellosub.c", dolazi do razlike u vremenu nastanka međuovisnih datoteka, pa programnmake pokreće program za kompiliranje koji je specificiran u drugom retku. Korištenje makefile je vrlo popularno, posebno na Unix sustavima i kod profesionalnihprogramera, jer se pomoću simboličkih definicija lako može definirati proces kompiliranja narazličitim operativnim sustavima. 32
  • 33. 4 Kodiranje i tipovi podatakaNaglasci: • 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 podatakaU ovom se poglavlju se opisuje kako se u računalu kodiraju brojevi i znakovi, objašnjava sekoncept 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 tipovimapodataka pridjeljuje dogovorom utvrđena kombinacija binarnih znamenki. Ovdje će biti opisanokodiranje koje se koristi u C jeziku. S programerskog stajališta, važnije od samog načina kodiranja je veličina zauzećamemorije i interval vrijednosti koji se postiže kodiranjem. Također, važno je upoznati leksičkapravila po kojima se zapisuju znakovne i numeričke konstante u "literalnom" obliku.4.1.1 Kodiranje pozitivnih cijelih brojeva (eng. unsigned integers) Pozitivni cijeli brojevi (eng. unsigned integers), ili kardinalni brojevi, su brojevi iz skupakojeg čine prirodni brojevi i nula. Način njihovog kodiranja je opisan u poglavlju 2. U C jezikuse literalne konstante, koje predstavljaju pozitivne cijele brojeve, mogu zapisati u decimalnom,heksadecimalnom i oktalnom brojevnom sustavu, prema slijedećem leksičkom pravilu: • niz decimalnih znamenki označava decimalnu konstantu ukoliko prva znamenka nije nula. • niz oktalnih znamenki označava oktalnu konstantu ako je prva znamenka jednaka nuli. • niz heksadecimalnih znamenki, kojem prethodi prefix 0x ili 0X, označava heksadecimalnu konstantu.Primjer: tri ekvivalentna literalna zapisa vrijednosti binarnog niza 011011 u C jeziku su: decimalna konstanta 27 oktalna konstanta 033 heksadecimalna konstanta 0x1B4.1.2 Kodiranje cijelih brojeva (eng. integers) Cijeli brojevi (eng. integers) su brojevi iz skupa kojeg čine prirodni brojevi, negativniprirodni 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 Nx, u brojevnom sustavu baze xmatematički se definira izrazom: N x = xn − N x ′primjerice, u decimalnom sustavu komplement troznamenkastog broja 733 je 103-733= 277.Ako se n-znamenkasti broj i njegov komplement zbroje vrijedi da će n znamenki biti jednakonuli. U prijašnjem primjeru 733+277=1000, dakle tri znamenke su jednake nuli. U binarnom se sustavu puni komplement naziva komplement dvojke i vrijedi: N 2 = 2n − N 2 ′Komplement dvojke se koristi za označavanje negativnih brojeva. Primjerice, komplementdvojke broja +1 za n=4 iznosi: ′ N 2 = 2 4 − 1 = 10000 − 0001 = 1111 . Ako se broj i njegov komplement zbroje, rezultat treba biti nula. To vrijedi, u prethodnomprimjeru, 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 brojeviimaju MSB=1. Što se događa ako se zbroje dva pozitivna broja koji imaju bitove ispod MSB jednakejedinici. Primjerice, ako zbrojimo 4+5 ( u 4-bitnom sustavu) 0100 +0101 ----- 1001dobije se rezultat koji predstavlja negativan broj u sustavu komplementa dvojke. Do prijelaza upodručje komplementa ne bi došlo da je rezultat zbrajanja bio manji od 7, odnosno 24-1-1. Poopći li se prethodno zapažanje na brojeve od n-bita, može se zaključiti da operacijazbrajanja ima smisla samo ako je zbroj operanada manji od 2n-1-1. Zbog toga, najvećipozitivni broj koji se može predstaviti u sustavu komplementa dvojke iznosi: max_int = (0111...111) = 2n-1-1,a najveći iznos negativnog broja iznosi: min_int = (1000...000) = -2n-1.Uočite da postoji za jedan više negativnih brojeva od pozitivnih brojeva. Obični komplement binarnog broja (naziva se i komplement jedinice) dobije se zamjenomsvih jedinica s nulom i obratno. Iznos broja. koji se dobije na ovaj način, računa se premaizrazu: N 2 = 2n − N 2 − 1 Obični komplement nije pogodan za izražavanje prirodnih brojeva jer nije jednoznačnoodređena vrijednost nule, naime obični komplement od 0000 iznosi 1111. On služi zajednostavno izračunavanje punog komplementa, jer vrijedi: N2 = N2 + 1 ′ 34
  • 35. Puni komplement se izračunava tako da se običnom komplementu pribroji jedinica, primjerice,komplement dvojke broja 6 u 8-bitnoj notaciji iznosi: 00000110 (+6) -------- 11111001 (obični komplement od +6) 1 (dodaj 1) -------- 11111010 (-6 u komplementu dvojke)Izračunajmo puni komplement od 0: 00000000 (0) -------- 11111111 (komplement od 0) 1 (dodaj 1) -------- 100000000 (-0 u komplementu dvojke)Jedinica predstavlja deveti bit. Ona se u 8-bitnom sustavu odbacuje pa rezultat opet predstavljanulu. Komplement dvojke omogućuje jednoznačno određivanje nule, pa je podesan za kodiranjecijelih brojeva.4.1.3. Kodiranje realnih brojeva Realni brojevi se u matematici zapisuju na način da se cijeli i decimalni dio odvojedecimalnim zarezom (pr. 67,098), a koristi se i ekponentni format (eng. scientific format).Primjerice, prethodni se broj može zapisati u obliku 0,67089⋅102. U programskom jeziku Ckoristi se sličan zapis kao u matematici, s razlikom da se umjesto decimalnog zareza koristi"decimalna točka", a potencija broja 10 se označava velikim ili malim slovom E. matematički zapis ekvivalentni zapis u C-jeziku 1,789 1.789 0,789 0.789 ili .789 -178,9⋅10-2 -178.9e-2 ili -178.9E-2 -0,01789⋅102 -0.01789e2 ili -0.01789E2 ili -0.01789e+2 Tablica 4.1. Matematički i programski zapis realnih brojevaEksponentni format se sastoji od dva dijela: mantise i eksponenta eksponentni decimalni format = mantisa 10eksponent Mantisa se zapisuje kao obični decimalni broj s predznakom, a eksponent se zapisuje kaocijeli broj. Prednost korištenja eksponentnog formata je u lakšem zapisu vrlo velikih i vrlo malihbrojeva. 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 zapisivanjarealnog 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 bazi2: 1/2, 1/4, 1/8 itd.) glasi: bN −1bN − 2 ...b0 • b−1b− 2 ...b− n ,a iznos mu se računa prema izrazu: N .n2 = bN −1 2 N −1 + ... + b0 2 0 + b−1 2 −1 + b− 2 2 −2 + ... + b− n 2 − n , bi ∈ (0,1) Ako se ovaj broj pomnoži s 2n može ga se u operacijama smatrati cijelim brojem, a nakonizvršenih aritmetičkih operacija rezultat se skalira za iznos 2-n. Ovaj oblik kodiranja ima brojnenedostatke, 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 binarnombrojevnom sustavu. Kodiranje se vrši prema izrazu F = ( − 1) m 2 e sgdje 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 cijelibroj, 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 fiksnimpoložajem binarne točke. Primjerice, neka je broj kodiran u obliku: 0.00001xxxxxxxxx 2egdje x može biti 0 ili 1. Ovaj oblik zapisa realnog broja naziva se nenormalizirani zapis.Pomakne li se položaj binarne točke za 5 mjesta ulijevo dobije se ekvivalentni zapis 1.xxxxxxxxx00000 2e-5, Posmak bitova ulijevo ekvivalentan je dijeljenju s dva, stoga se vrijednost eksponentasmanjuje za 5. Ovakav kodni zapis, u kojem je uvijek jedinica na prvom mjestu, naziva senormalizirani zapis. Značaj normaliziranog zapisa je u činjenici što se njime iskorištavaju svibitovi mantise za kodiranje vrijednosti, dakle osigurava se veća točnost zapisa. Normaliziranimse oblikom ipak ne može kodirati veoma male vrijednosti, pa je tada pogodniji nenormaliziranizapis broja. Treba pojasniti i kako je kodirana vrijednost nula. Pogodno bi bilo da sva bitnapolja pri kodiranju vrijednosti nula budu jednaka nuli (zbog logičkih operacija), ali pošto se zakodiranje eksponenta također koristi binarni zapis, vrijednost eksponenta nula se matematičkikoristi za označavanje brojeva većih od jedinice. Da bi se zadovoljilo zahtjevu kodiranja nule snultim zapisom eksponenta uobičajeno je da se umjesto stvarne vrijednosti eksponenta kodiravrijednost: E = e + pomak,gdje je pomak neka konstantna vrijednost, a odabire se na način da je jednak najnižoj vrijednostieksponenta e, koji je negativna vrijednost Ovako zapisani eksponent naziva se pomaknutieksponent. Značaj pomaka pokazuje sljedeći primjer. Neka je eksponent opisan s 8 bita. Tada seE kreće u rasponu od 0 do 255. Ako se uzme da je pomak=127, i da je E=0 rezervirano zakodiranje nule, onda se vrijednost binarnog eksponenta kreće u rasponu od -126 do +127. 36
  • 37. Postoji više različitih formata zapisa mantise i eksponenta u binarnom kodu. Danas segotovo isključivo koristi format koji je određen ANSI/IEEE standardom br.745 iz 1985. godine.Prema tom standardu koriste se dva tipa kodiranja: jednostruki format (32 bita) i dvostrukiformat (64 bita). Slika 4.1 Format kodiranja realnih brojeva prema IEEE/ANSI standardu STANDARDNI IEEE/ANSI FORMAT REALNIH BROJEVA parametar Jednostruki (SINGLE) dvostruki (DOUBLE) ukupan broj bita 32 (+1) 64 (+1) broj bita eksponenta 8 11 broj bita za predznak 1 1 broj bita mantise 23 (+1) 52 (+1) pomak +127 +1023 Emax 255 2047 Emin 0 0 minreal (za nenorm.) (-1)s⋅1.4⋅10-45 (-1)s⋅2.225⋅10-324 minreal (za norm.) (-1)s⋅1.175⋅10-38 (-1)s⋅2.225⋅10-308 maxreal (-1)s⋅3.4028⋅10+38 (-1)s⋅1.797⋅10+308 Tablica 4.2. Standardni IEEE/ANSI format realnih brojeva Bitna karakteristika ovog standarda je da je u format za kodiranje realnog broja mogućeupisati i šifru o ispravno obavljenoj matematičkoj operaciji (pr. dijeljenje s nulom dalo bibeskonač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 nijebroj, u standardu se opisuje pod nazivom NaN (Not a Number). Kodiranje s normaliziranimzapisom mantise je izvršeno na način da se ne upisuje prva jedinica, čime se podrazumjeva da jemantisa kodirana s jednim bitom više nego je to predviđeno u binarnom zapisu. Vrijednostpomaka i raspona eksponenta dana je tablicom 3.2. Vrijednost eksponenta Emin je iskorištena zakodiranje nule, a vrijednost Emax za kodiranje NaN-a i beskonačnosti.Zapis formata se interpretira na sljedeći način: 1. Ako je E=Emax i m≠0 kodna riječ predstavlja NaN, bez obzira na vrijedost predznaka s. 2. Ako je E=Emax i m=0 kodna riječ predstavlja (-1)s (∝). 3. Ako je Emin<E<Emax kodna riječ predstavlja broj (-1)s2e-127(1.m), tj. predstavlja normalizirani realni broj. 4. Ako je E=0 i m=0 kodna riječ predstavlja broj (-1)s(0). 5. Ako je E=0 i m≠0 kodna riječ predstavlja broj (-1)s2ee-127(0.m) tj. predstavlja nenormalizirani realni broj (vodeća nula se ne zapisuje). Opis nenormaliziranim brojevima ne osigurava točnost za sve brojeve pa se ovo kodiranje u nekim implementacija ne koristi. Vrijednosti za minimalnu i maksimalnu vrijednost realnog broja u tablici 3.2. dani su za normalizirani i nenormalizirani format realnog broja. 37
  • 38. Programer, koji programira u višem programskom jeziku, ne mora znati kako se neki brojkodira 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 15decimala za prošireni format.4.1.4. Kodiranje znakova Znak (eng. character) je element dogovorno usvojenog skupa različitih simbola koji sunamijenjeni 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 stringliteralno zapisuje kao niz znakova omeđen znakom navodnika ("ovo je literalni zapis Cstringa"). Za kodiranje slova, znamenki i ostalih tzv. kontrolnih znakova koristi se američki standardza izmjenu informacija ASCII (American Standard Code for Information Interchange). Njemuje ekvivalentan međunarodni standard ISO 7. ASCII kôd se najviše koristi za komuniciranjeizmeđ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 koristekao kontrolni znakovi za različite namjene, a ostali znakovi predstavljaju slova abecede,pravopisne i matematičke simbole.000: (nul) 016: (dle) 032: (sp) 048: 0 064: @ 080: P 096: ž 112: p001: (soh) 017: (dc1) 033: ! 049: 1 065: A 081: Q 097: a 113: q002: (stx) 018: (dc2) 034: " 050: 2 066: B 082: R 098: b 114: r003: (etx) 019: (dc3) 035: # 051: 3 067: C 083: S 099: c 115: s004: (eot) 020: (dc4) 036: $ 052: 4 068: D 084: T 100: d 116: t005: (enq) 021: (nak) 037: % 053: 5 069: E 085: U 101: e 117: u006: (ack) 022: (syn) 038: & 054: 6 070: F 086: V 102: f 118: v007: (bel) 023: (etb) 039: 055: 7 071: G 087: W 103: g 119: w008: (bs) 024: (can) 040: ( 056: 8 072: H 088: X 104: h 120: x009: (tab) 025: (em) 041: ) 057: 9 073: I 089: Y 105: i 121: y010: (lf) 026: (eof) 042: * 058: : 074; J 090: Z 106: j 122: z011: (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 znakovaU Hrvatskoj se za latinična slova Č,Š,Ž,Ć i Đ koristi modifikacija ASCII standarda. (tablica4.4). HR-ASCII IBM EBCDIC-852 standard dec hex ASCII HR-ASCII Dec hex EBCDIC-852 64 40 @ Ž 166 A6 Ž 91 5B [ Š 230 E6 Š 92 5C Đ 209 D1 Đ 93 5D ] Ć 172 AC Ć 94 5E ^ Č 143 8F Č 96 60 ` ž 167 A7 ž 123 7B { š 231 E7 š 124 7C | đ 208 D0 đ 125 7D } ć 134 86 ć 126 7E ~ č 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 kodiranje256 znakova. Ovaj kod se naziva EBCDIC (Extended Binary Coded Decimal InterchangeCode). Prvih 128 znakova ovog koda jednaki su ASCII standardu, a ostali znakovi predstavljajurazličite grafičke i matematičke simbole te slova koja se koriste u alfabetu većine zapadno-evropskih zemalja, a nisu obuhvaćena ASCII standardom. U sklopu tog standarda predviđeno jeda se znakovi Č,Š,Ž,Ć,Đ,č,ć,ž,š,đ kodiraju vrijednostima većim od 127. Taj standard imaoznaku EBCDIC-852 i prikazan je u tablici 4.4. U Windows operativnom sustavu, koji koristigrafič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 Unicodestandardom. Njime su obuhvaćeni znakovi iz svih svjetskih jezika. U C jeziku se imena varijabli i funkcija označavaju nizom znakova, stoga se zaoznač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 desnestrane je napisana znakovna konstanta a. Ovom se naredbom se varijabli a pridjeljuje ASCIIvrijednost 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 imanumerič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 znakovnekonstante, 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 postojioznaka na standardnoj tipkovnici. Za slučaj da se pak želi raditi i s takvim znakovima , zanjihov unos u C jeziku se koriste specijalne kontrolne sekvence (tzv. escape sequence), koje sezapisuju 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), onese zapisuju unutar dvostrukih navodnika, primjerice "Hello World!n".Unutar stringa se kontrolni znakovi zapisuju bez navodnika. U ovom primjeru, na kraju stringaje zapisan znak za prijelaz u novu liniju.4.1.5. Kodiranje logičkih vrijednosti Moguće su samo su dvije logičke vrijednosti: istina i laž. U većini programskih jezika(C++, Pascal) ove vrijednosti se zapisuju s “true” i “false”. U C jeziku se ne koristi posebnooznačavanje logičkih vrijednosti, već vrijedi pravilo da se svaka numerička vrijednost možetretirati 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 != (nijejednako). U C jeziku, ako je vrijednost relacijskog izraza logička vrijednost “istina” ona sekodira kao numerička vrijednost 1, a logička vrijednost “laž” se kodira kao numeričkavrijednost 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šavanjuprograma zapravo određuje pomoću operacije oduzimanja, a definiran je u tablici 4.5. Ako je x == y x != y x > y x < y x <= y x >= y x-y > 0 0 1 1 0 0 1 x-y = 0 1 0 0 0 1 1 x-y < 0 0 1 0 1 1 0 Tablica 4.5 Rezultat izvršenja relacijskih operacija u C jeziku4.2 Memorija Prije nego se pokaže kako se na apstraktnoj razini manipulira s podacima, koji se nalaze uradnoj memoriji računala, potrebno je upoznati neke hardverske karakteristike radne memorijeračunala. Elektronička memorijska jedinica može pamtiti 1 bit informacije. Više memorijskihjedinica se pakira u integrirani elektronički sklop koji se popularno naziva čip. U računalu sememorijski čipovi spajaju na način da se istovremeno može pristupiti grupi od 8 ili višetemeljnih 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šiistovremeni prijenos više digitalnih znamenki (8, 16, 32 ili 64 bita) pomoću višestrukihelektrič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 prijenospodatka koji će biti spremljen (zapisan) ili dobavljen (pročitan) iz te memorijske ćelije ikontrolna 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. Stanjeupisivanja 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činda se vrijeme upisivanja ili čitanja iz memorije kontrolira posebnim signalima generatora takta.Aktiviranje jedne memorijske grupe određeno je binarnom kombinacijom naponskih stanjeadresne 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 isadržaj memorije na toj adresi. Veličina se adresne sabirnice i sabirnice podataka iskazujebrojem bita koje oni u jednom trenutku prenose i određena je mogućnostima procesora ihardverskom strukturom računala. Maksimalni memorijski kapacitet je određen izrazom: Maksimalni memorijski kapacitet = 2veličina adresne sabirnice (bajta) Tzv. 8-bitni mikroprocesori (Z-80, Motorola 6800, Intel 8080) imaju 16-bitnu adresnusabirnicu i 8-bitnu sabirnicu podataka, pa im maksimalni memorijski kapacitet iznosi 64kilobajta. Prva personalna računala klase IBM PC XT imala su adresnu sabirnica od 20 linija i8-bitnu sabirnica podataka, pa je maksimalni kapacitet memorije tih računala iznosio 1M. Koddanaš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 uračunala ugrađuje znatno manje memorije (64MB-1GB). Na temelju onoga što je do sada kazano o kodiranju sadržaja i hardverskom ustrojstvumemorije, sa programerskog stajališta, važne su sljedeće činjenice i definicije: 1. Podaci, koji se nalaze na nekoj adresi u memoriji, nazivaju se memorijski objekti. 2. Adresa se opisuje kardinalnim brojem koji opisuje položaj temeljne memorijske ćelije, veličine 8 bita, u linearno uređenom prostoru memorije. 3. Podaci su najčešće kodirani tako da pripadaju skupu numeričkih vrijednosti ili skupu znakova koji su kodirani prema ASCII standardu, ili im se pridodaje logička vrijednost {true, false}. 4. Memorijskim objektima procesor može mijenjati sadržaj i s njima obavljati aritmetičko- logičke operacije. 5. Memorijski objekti, s kojima procesor operira kao s cjelinom, nazivaju se varijable. 41
  • 42. 4.3 Prosti tipovi podataka U uvodu je naglašeno da ime varijable simbolički označava položaj u memoriji (adresu) nakojem je upisana vrijednost varijable. Sada ćemo uz pojam varijable uvesti i pojam tipavarijable. U matematici je uobičajeno uz matematičke izraze navesti i skup vrijednosti varijabli zakoje ti izrazi vrijede. Primjerice, neka varijabla x pripada skupu realnih brojeva (x ∈ R), avarijabla z skupu kompleksnih brojeva (z ∈ Z). Tada vrijedi: z Re( z) Im( z) = + j x x xVrijednost ovog izraza također pripada skupu kompleksnih brojeva. Bitno je uočiti da oznakapripadnosti skupu vrijednosti određuje način kako se računa matematički izraz i kojem skupuvrijednosti pripada rezultat izraza. U programskim se jezicima pripadnost skupu koji imazajedničke karakteristike naziva tipom. Tip varijable određuje način kodiranja, veličinuzauzimanja memorije i operacije koje su sa tom varijablom dozvoljene. Tipovi varijabli (ilikonstanti) 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 izravanprikaz u memoriji računala. Tip Oznaka u Interval zauzeće C jeziku vrijednosti Memorije znakovni [signed] char -127 .. 128 1 bajt tip Unsigned char 0 .. 255 1 bajt cjelobrojni [signed] int -2147483648.. 2147483647 4 bajta tip [signed] short -32768 .. 32767 2 bajta [signed] long -2147483648.. 2147483647 4 bajta kardinalni unsigned [int] 0 .. 4294967295 4 bajta tip unsigned short 0 .. 65535 2 bajta unsigned long 0 .. 4294967295 4 bajta realni tip min ± 1.175494351e-38 (jednostruk float maks ± 3.402823466e+38 4 bajta i format) realni tip double min ± 2.2250738585072014e-308 8 bajta (dvostruki maks ± 1.7976931348623158e+308 format) logički tip - 0 .. različito od nule -Tablica 4.6. Označavanje standardnih tipova podataka u C jeziku (uglate zagrade označavaju opcioni element) Tablica 4.6 prikazuje karakteristike standardnih tipove podataka koji se koriste u C jeziku;ime kojim se označavaju, interval vrijednosti i veličina zauzeća memorije u bajtima. Uglatezagrade 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 (cijelibroj), char je kratica leksema character (znak), signed je pridjev koji označava skup cijelihbrojeva u kojem se koristi predznak (+ ili -), a unsigned je pridjev koji označava da se izskupa cijelih brojeva koriste samo brojevi bez predznaka (kardinalni brojevi), float je kraticaod floating point (realni broj kodiran prema IEEE standardu jednostrukog formata), a doubleznač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 koristese dvije varijable tipa double: r označava radijus, a P površinu kruga. Pretpostavlja se da jeradijus poznat i neka iznosi 2.1 m. Rezultat proračuna će biti ispisan pomoću printf()funkcije. Evo kako je napisan taj program. /* Datoteka: povrsina.c */ /* Program koji računa površinu kruga radijusa 2.1m */ #include <stdio.h> int main() { double r, P; /* deklaracija varijabli r i P */ r = 2.1; /* zadana vrijednost radijusa 2.1m */ P = r*r*3.14; /* proračun površine kruga */ printf( "n Povrsina kruga = %f m", P); return 0; }Kada se program kompilira i izvrši dobije se ispis: Povrsina kruga = 13.847400 m Komentarima je opisan značaj pojedinog programskog iskaza. Najprije je izvršenadeklaracija varijabli r i P, prema pravilu da se oznaka tipa napiše ispred imena varijabli (akose istovremeno deklarira više varijabli istoga tipa, njihova se imena odvajaju zarezom). Zapisdeklaracije završava znakom točka-zarez. Nadalje, naredbom pridjele vrijednosti postavljena jevrijednost varijable r na zadanu vrijednost 2.1. Proračun površine je izvršen prema izrazu3.14*r*r, gdje zvjezdica označava operator množenja. U matematici formula za površinuglasi r2π. Ovu formulu se ne može izravno primijeniti jer u C jeziku ne postoji operatorpotenciranja. 43
  • 44. Rezultat proračuna je pridijeljen varijabli P. Za ispis vrijednosti te varijable koristi sestandardna funkcija printf(). Potrebno je uočiti razliku od zapisa printf() funkcije koji jekorišten u programu Hello.c. U ovom slučaju funkcija printf() koristi dva argumenta. Prviargument je literalni string, a drugi argument je varijabla P čiju vrijednost treba ispisati. Položajna kojem će se ispisati ta vrijednost označen je unutar literalnog stringa oznakom %d. Ovaoznaka 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žajgdje će biti izvršen ispis vrijednosti tih argumenta označava se u stringu format_ispisaspecifikatorom ispisa. Broj specifikatora ispisa mora biti jednak broju argumenata funkcije kojisu navedeni u lista_argumenata. Specifikatori ispisa moraju odgovarati tipu argumenta,primjerice za cjelobrojne argumente ( tip: int, short, long) oznaka je %d, za znakovneargumente (tip char) oznaka je %c, za realne argumente (tip: float, double) oznaka je %fili %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. Prviprogram 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) = %dn", 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 tablici4.7. Uočite da sufiks U ili u označava konstante kardinalnog tipa, sufiks f ili F označava realnebrojeve jednostrukog formata, sufiks L ili l označava brojeve dvostrukog formata. char A znakovna konstanata A a znakovna konstanata a ili 035 znakovna konstanata 35 u oktalnoj notaciji x29 znakovna konstanata 29 u heksadecimalnoj notaciji int n znak za novu liniju Int 156 decimalna notacija 0234 oktalna notacija cjelobrojne konstante 0x9c heksadecimalna notacija unsigned 156U Decimalno 0234U oktalno (prefiks U određuje kardinalni broj) 0x9cU heksadecimalno float 15.6F realni broj – jednostruki format 1.56e1F određen primjenom sufiksa F ili f double 15.6 konstante su tipa "double" ukoliko se ne koristi 1.56E1L 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 scjelobrojnim tipom. To demonstrira program charsize.c. /* Datoteka: charsize.c */ /* Program kojim se ispituje tip znakovne konstante */ #include <stdio.h> int main() { char c; /* deklaracija varijable c tipa char*/ int x; /* deklaracija varijable x tipa int*/ c = A; /* objema varijablama može se pridijeliti */ x = A; /* vrijednost znakovne konstante A */ printf( "n c = %c", c); printf( "n Sizeof c = %d", sizeof (c) ); printf( "n x = %d", x); printf( "n Sizeof x = %d", sizeof(x)); printf( "n Sizeof A = %d", sizeof(A)); return 0; }koji daje ispis: c = A Sizeof c = 1 x = 65 Sizeof x = 4 Sizeof A = 4 45
  • 46. U programu su prvo deklarirane dvije varijable; c je tipa char, a x je tipa int. Objemavarijablama je zatim pridijeljena vrijednost znakovne konstante A. Prilikom ispisa varijable ckoristi se specifikator ispisa za znakove - %c, pa se ispisuje slovo A. Ispis vrijednosti varijable xdaje cjelobrojnu vrijednost, koja je jednaka vrijednosti ASCII koda znaka A. Očito je dakompilator tretira znakovnu konstantu ovisno o kontekstu u kojem se koristi, a za nju rezervirau memoriji mjesto od 4 bajta, što pokazuje posljednji ispis. Ova dvostrukost primjene znakovnihkonstanti zapravo je dobra strana C jezika. Time je omogućeno da se znakovne konstante mogukoristiti i kao simboli i kao numeričke vrijednosti. Simbolički značaj konstante važan je priunosu i ispisu znakova, numerički značaj omogućuje da se znakovne konstante mogu koristiti uaritmetič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 samogprogramskog teksta, može pridijeliti nekom simboličkom imenu pomoću pretprocesorskedirektive #define. Primjerice, ako se zapiše: #define PI 3.141592653589793to ima efekt da kada se u tekstu programa, koji slijedi iza ove direktive, zapiše PI, vrijedi kao daje zapisano 3.141592653589793. Do sada smo upoznali direktivu #include. Sve pretprocesorske direktive počinju znakom#. Njih se ne smatra programskim naredbama pa se iza njih ne zapisuje znak točka-zarez.Pomoću njih se vrši leksička supstitucija teksta iz drugih datoteka ( pomoću #include) iliprema supstitucijskom pravilu koji je opisan direktivu #define .Općenito direktivu #define ima oblik: #define IME supstitucijski_tekstgdje IME predstavlja proizvoljan naziv zapisan neprekinutim nizom znakova (obično se zapisujevelikim slovima i podvlakom), a supstitucijski_tekst može sadržavati i isprekidani nizznakova. Primjerice, #include <stdio. h> #define MESSAGE "Vrijednost broja pi = " #define PI 3.141592653589793 #define PR_NL printf("n") int main( void) { printf(MESSAGE); printf("%f" ,PI); PR_NL; return 0; }Nakon izvršenja dobije se ispis Vrijednost broja pi = 3.141592653589793Kasnije će biti detaljnije pojašnjen rad s pretprocesorskim direktivama. 46
  • 47. Na kraju ovog poglavlja pogledajmo program Limits.c. Njime se ispisuje minimalna imaksimalna vrijednost za sve proste tipove podataka C jezika. U programu se koristesimboličke konstante koje su pomoću direktive #define zapisane u standardnim datotekamalimits.h i float.h. /* Datoteka: Limits.c */ /* Ispisuje interval vrijednost numeričkih tipova */ #include <stdio.h> #include <limits.h> #include <float.h> int main( void ) { printf("%12s%12s%15s%15sn", "Tip", "Sizeof", "Minimum", "Maksimum"); printf("%12s%15d%15dn","char", CHAR_MIN, CHAR_MAX) ; printf("%12s%15d%15dn","short int", SHRT_MIN, SHRT_MAX) ; printf("%12s%15d%15dn","int", INT_MIN, INT_MAX) ; printf("%12s%15ld%15ldn","long int", LONG_MIN, LONG_MAX) ; printf("%12s%15g%15gn", "float", FLT_MIN, FLT_MAX) ; printf("%12s%15g%15gn", "double", DBL_MIN, DBL_MAX) ; printf("%12s%15Lg%15Lgn","long double",LDBL_MIN, LDBL_MAX) ; return 0 ; }Nakon izvršenja dobije se ispis: Tip Sizeof Minimum Maksimum char 1 -128 127 short int 2 -32768 32767 int 4 -2147483648 2147483647 long int 4 -2147483648 2147483647 float 4 1.17549e-038 3.40282e+038 double 8 2.22507e-308 1.79769e+308 long double 8 2.22507e-308 1.79769e+308Ovaj ispis izgleda uredno. To je postignuto korištenjem specifikatora ispisa printf() funkcijeu proširenom obliku, tako da je između znaka % i oznake tipa ispisa upisan broj kojim seodređ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 formatiraniispis. Sada će biti pokazano kako se zadaje format ispisa printf() funkcije. Općenito formatse zadaje pomoću šest polja: %[prefiks][širina_ispisa][. preciznost][veličina_tipa]tip_argumentaFormat mora započeti znakom % i završiti s oznakom tipa argumenta. Sva ostala polja suopciona (zbog toga su napisana unutar uglatih zagrada). U polje širina_ispisa zadaje se minimalni broj kolona predviđenih za ispis vrijednosti. Akoispis 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 realnogbroja ili minimalni broj znamenki ispisa cijelog broja ili maksimalni broj znakova koji seispisuje 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 nekoristi, tada se podrazumijeva da će realni brojevi biti ispisano s maksimalno šest decimalnihznamenki 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 ispredoznake tipa.h Pripadni argument tipa int tretira se kao short int ili unsigned short int.l Pripadni argument je long int ili unsigned long int.L Pripadni argument realnog tipa je long double.Primjeri korištenja printf() funkcije dani su u programu printf.c. /* Datoteka: printf.c */ /* Primjer korištenja printf funkcije */ #include<stdio.h> int main() { 48
  • 49. printf("%d, %5d, %-5d, %05d, %5.5dn", 1, 2, 3, 4, 5); printf("%o %x %X %#o %#xn", 171, 171, 171, 171, 171); printf("%f %e %gn", 3.14, 3.14, 3.14); printf("%s, %.5s!n", "Hello", "worldly"); printf("%0*d, %.*f, %*.*sn", 2, 3, 4, 5.6, 7, 3, "abcdef"); return 0; }Ovaj pogram daje ispis: 1, 2, 3 , 00004, 00005 253 ab AB 0253 0xab 3.140000 3.140000e+000 3.14 Hello, world! 03, 5.6000, abc U prethodnim tablicama za oznake tipa argumenata p i s naglašeno je da su argumentifunkcije 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ćujekorištenje numeričke vrijednosti adresa varijabli i funkcija za indirektno manipuliranje spodacima i izvršenjem programa. U tu svrhu koriste se posebni operatori - adresni operator & ioperator indirekcije *, te specijalni tip varijabli koje se nazivaju pokazivačke varijable ilipokazivač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 imenavarijable. 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 uheksadecimalnim obliku s osam znamenki, jer je korišteno 32-bitno računalo na kojem je adresaodređena 32-bitnim kardinalnim brojem.Napomena: pri ponovljenom izvršenju programa ispis adrese ne mora biti isti jer operativnisustav ne učitava program uvijek na isto mjesto u memoriji (time se mijenja i adresa na kojoj senalazi varijabla x).Operator indirekcije * Komplementarno adresnom operatoru koristi se unarni operator indirekcije koji seoznačava znakom *. Zapisuje se ispred izraza čija vrijednost predstavlja neku adresu. Značajoperatora indirekcije je u tome da se pomoću njega dobije vrijednost koja je upisana na tojadresi. To znači da ako je y jednako 777, onda je *(&y) također jednako 777jer operator indirekcije daje vrijednost koja se nalazi na adresi varijable y. Ovo pravilo se možeprovjeriti 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 777Napomena: izraz *(&y) se može pisati i bez zagrada *&y, međutim , obično se pišu zagradezbog lakšeg uočavanja redoslijeda djelovanja operatora.Pokazivači Varijable kojima je vrijednost adresa neke druge varijable ili funkcije nazivaju sepokazivači ili pointeri. Pokazivači moraju biti deklarirani, jer C kompilator mora znati kakav ćetip podatka biti na adresi koju oni sadrže, ili kako se češće kaže, mora biti poznat tip objekta nakojeg oni pokazuju. Deklaracija pokazivača vrši se slično deklaraciji varijable, s razlikom što se između oznaketipa 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 jeneodređena, jer im nije pridijeljena adrese nekog realnog memorijskog objekta. Važno je znatida pokazivače prije upotrebe treba inicijalizirati, odnosno mora im se pridijeliti vrijednostadrese postojećeg memorijskog objekta. Pri tome, tip pokazivača mora biti jednak tipumemorijskog 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 sumapridijeljena vrijednost 777, a pokazivač p je inicijaliziran da pokazuje na tu varijablu. Ako bidalje 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 jevrijednost varijable suma (*p ⇔*&suma). memorijska sadržaj ime adresa memorije varijable 0x09A8 ...... ...... 0x09AC 777 suma 0x09B0 ...... ...... ...... ...... ...... ...... ...... ...... ...... ...... ...... 0x1000 0x09AC p 0x1004 ...... ...... Slika 4.3. Prikaz memorijskog sadržaja i adresa varijable suma i pokazivača pStanje u memoriji prikazano je na slici 4.3. Strelicom je označeno da je vrijednost pokazivača pjednaka adresi varijable suma. Vrijednost adresa je napisana proizvoljno, jer se ne možeunaprijed znati na kojoj će se adresi nalaziti podaci. Zbog toga se češće za opis operacija spokazivačima koristi sljedeća simbolika. Varijabla se označava kao pravokutni objekt. Unutarpravokutnika upisuje se vrijednost varijable (ako je vrijednost neodređena upisuje se znak ?), aime varijable se upisuje uz pravokutnik. Sadržaj pokazivačke varijable se označava strelicomkoja pokazuje na neki drugi objekt. U skladu s ovakvom simbolikom prije opisane operacije semogu predstaviti slikom 4.4. Slika 4.4. Prikaz operacija s pokazivačem na varijabluVažna osobina indirekcije pokazivača je da se može koristiti u svim izrazima u kojima sekoriste 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 jekorištena naredba: suma = 678; Ovime je demonstriran glavni razlog korištenja pokazivača, a to je da se pomoćupokazivač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 sekoristi 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, jersama 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čjememorije 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 objektekojima se može mijenjati sadržaj. Memorijskom se objektu može pristupiti pomoću imena iliposredno 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 sekoriste, i zašto se koriste, biti će objašnjeni tek kada se upoznaju temeljni elementiprogramiranja 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 vrijednostmože predočiti korisniku programa pomoću printf() funkcije. Sada će biti pokazano kako sepodaci mogu unijeti u memoriju računala, u slučaju kada se unos podataka vrši pomoćutipkovnice. 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 12345.Važno je zapamtiti da argumenti funkcije scanf() moraju biti izrazi čija je vrijednost adresa.U prethodnom primjeru to je adresa varijable unos. Adresa se dobije primjenom adresnogoperatora & na varijablu unos. Obično se u format_unosa ne upisuje nikakvi dodatni tekst, kao što je bio slučaj kodprintf() funkcije, iako je to dozvoljeno. Razlog tome je činjenica da se tada od korisnikaoč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 biotkucao samo broj 25, funkcija scanf() ne bi dala ispravan rezultat. Pomoću scanf() funkcije može se odjednom unijeti više vrijednosti, primjerice unosjednog cijelog broja, jednog realnog broja i jednog znaka, može se ostvariti samo s jednimpozivom 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 brojazavrš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 prethodnogunosa. Razmotrimo prijašnji primjer i pretpostavimo da korisnik želi unijet cijeli broj 67, realnibroj 3.14 i znak Z. Prema zadanom formatu on bi morao otkucati: 86 3.14Zdakle, znak bi trebalo otipkati bez razmaka od prethodnog broja. Problem se može riješiti takoda 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 samojedan razmak. Primjerice ako bi korisnik otipkao: 86 3.14 Zne 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 zakomuniciranje s korisnikom. Kasnije će biti pokazano da je za unos znakova pogodnije koristitineke druge funkcije. Također, bit će pokazano kako dijagnosticirati da li je izvršen unos kojiodgovara zadanom tipu varijable.4.8 Inicijalizacija varijabli Temeljni uvjet korištenja neke varijable je da ona prethodno mora biti deklarirana. Samomdeklaracijom nije određeno i početna (inicijalna) vrijednost varijable, pa prije korištenjavarijable 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 jeodređena početna vrijednost varijable y. Kada ne bi bila određena vrijednost od x, program bi sekompilirao bez dojave pogreške, ali tada bi pri izvršenju programa bila neodređena vrijednostvarijable y. Određivanje početnih vrijednosti varijabli važan je element programiranja. Prilikom izradevećih programa, ukoliko se koriste neinicijalizirane varijable, mogu nastati greške koje je teškootkriti. 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 jezikomNaglasci: • 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 bioupoznati standardne tipove podataka i jednostavne postupke komuniciranja s korisnikom udobavi i ispisu podataka. Sada će biti opisana algoritamska i funkcionalna struktura C programate postupci izrade jednostavnih programa.5.1 Postupak izrade programaIzrada se programa može opisati kao aktivnost koja se odvija u četiri temeljna koraka: 1. Definiranje zadatka i analiza problema. 2. Izrada detaljne specifikacije i uputa za rješenje problema. 3. Pisanje programa, dokumentacije i formiranje izvršnog programa. 4. Testiranje programa.Programer treba znati: • mogućnosti programskog jezika, • kako obraditi problem: o definiranje objekata obrade (podaci), o definiranje postupaka obrade (apstraktni i programski algoritmi), o definiranje korisničkog sučelja za unos podataka i prezentiranje rezultata obrade. • kako metodološki pristupiti razradi programa (strukturalno programiranje, modularno programiranje, objektno orijentirano programiranje), • kako optimalno iskoristiti računarske resurse i mogućnosti operativnog sustava računala, • koje softverske alate koristiti za razvoj programa.Većina od ovih pitanja bit će obrađena u narednim poglavljima. Postupak izrade manjih programa se može prikazati i dijagramom toka na slici 5.1.(korišteni su standardnim elementi za opis dijagrama toka, a opisani su u Dodatku 1). 55
  • 56. Slika 5.1. Postupak izrade manjih programaBitno 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 sesastoje od naredbi koje se izvršavaju jedna za drugom (sekvence), od naredbi selekcije iiterativnih naredbi (petlji). Kroz niz primjera bit će pokazano kako se ove naredbe zapisuju u Cjeziku. Posebnu pažnju posvetit će se problemu koji se obrađuje. Prvo će se definirati zadatak, azatim ć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 Cjeziku. Na kraju će se analizirati napisani program i rezultati koje on iskazuje tijekom svogizvrš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 za n = 0 ⎪ n n! = ⎨ ⎪∏ k za n > 0 ⎩ k =1Ovu 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*..*nRješenje: Trivijalno rješenje problema dano je u programu fact0.c. Najprije je deklariranacjelobrojna varijabla nfac. Zatim je toj varijabli pridijeljena vrijednost umnoška konstanti1*2*3*4*5, što odgovara vrijednosti 5!. Za ispis te vrijednosti korištena je standardna funkcijaprintf(). /* Datoteka fact0.c - Proračun 5! */ #include <stdio.h> int main() { int nfact; nfact = 1 * 2 * 3 * 4 * 5; printf("Vrijednost 5! iznosi: %dn", nfact); return 0; }Nakon izvršenja programa dobije se ispis: Vrijednost 5! iznosi: 120Poš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: %dn", 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.5.2.1 Naredba iteracije – while petlja Cilj pisanja programa je poopćenje procesa obrade nekog problema na način da se dobijerezultat 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 seunaprijed 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 > 0Lako je uočiti da vrijedi i sljedeće pravilo: n! = 1 za n=0 n! = n * (n-1)! za n>0koje 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šitiuvođ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 ukazujeda 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šenjeu 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 seniz_naredbi tijela petlje koje su napisane unutar vitičastih zagrada. Ako je izraz jednak nuliizvršenje programa se nastavlja operacijom koja je definirana naredbom koja slijedi iza tijelapetlje. U slučaju kada se u tijelu petlje navodi samo jedna naredba, tada nije nužno pisativitičaste zagrade. Izraz može biti bilo koji numerički ili relacijski izraz, a tretira se kao logički uvjet zaizvršenje naredbi koje su obuhvaćene tijelom petlje. U prethodnom primjeru izraz ima oblikrelacijskog izraza k < 5. Taj izraz u C jeziku može imati samo dvije vrijednosti: 1 ili 0, što jeekvivalentno logičkim vrijednostima istina ili laž. Naredbe tijela petlje će se ponavljati zavrijednosti k=1,2,3,4, jer je za te vrijednosti relacijski izraz k<5 istinit, odnosno njegovanumerička vrijednost je različita od nule. Pravu korist korištenja while-petlje spoznaje se tek kada ona primijeni za računanjevrijednosti n!, gdje je n vrijednost koju zadaje korisnik programa. Rješenje je jednostavno: uzaglavlju while-petlje, umjeto izraza k<5, dovoljno je uvrstiti izraz k<n, pa za realizacijukoraka 2 vrijedi algoritam: 2. Izračunati vrijednost n!. 2.1. Postavi nfact = 1; 2.2. Postavi k=0; 2.3. Dok je k < n ponavljaj Uvećaj vrijednost varijable k za jedan nfact = k * nfact;Dorada koraka 2:Analiziram prethodnog algoritama može se uočiti da je vrijednost nfact jednaka jedinici nesamo kada je n=0, već i u slučaju kada je n=1. Zbog toga se kao početna vrijednost varijable kmož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: %dn", 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: 120Ako korisnik unese vrijednost 13 dobije se rezultat: Vrijednost 13! iznosi: 1932053504Ako korisnik unese vrijednost 18 dobije se rezultat: Vrijednost 18! iznosi: -898433024Ovaj posljednji rezultat je pogrešan, jer vrijednost od 18! nadmašuje maksimalnu vrijednostkoja se može kodirati kao cijeli broj u memoriji veličine 4 bajta (tj. 2147483647). Može sezaključ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: 1Ovaj rezultat nema nikakvog smisla jer funkcija n-faktorijela nije definirana za negativnebrojeve. 60
  • 61. 5.2.2 Uvjetna naredba – if naredba Nakon provedenog testiranja, pokazala se potreba za doradom prvog koraka algoritmasljedećim operacijama:Dorada koraka 1: 1. Dobaviti vrijednost od n. 1.1. Upozoriti korisnika da se očekuje unos broja unutar intervala [0,13] 1.2. Dobaviti otipkanu vrijednost u varijablu n 1.3 Ako je n < 0 ili n > 13 tada izvršiti sljedeće: izvijestiti korisnika da je otkucao nedozvoljeni broj prekinuti izvršenje programaKako 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 uvjetnanaredba 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 jeomeđ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 Cjeziku ima oblik n < 0 || n > 13(Napomena: logički operator “i” se zapisuje s &&, a logička negacija znakom ! ispred logičkogizraza).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: %dn", n, nfact); return 0; } Konačno je ostvaren kvalitetan i robustan program. On za bilo koju ulaznu vrijednost dajerezultat nakon konačnog broja operacija. Ovo svojstvo se smatra temeljnim uvjetom koji morazadovoljiti svaki programski algoritam.5.2.3 Naredba selekcije: if-else naredba Radi vježbe i upoznavanja još jednog programskog iskaza – if-else naredbe, prethodnialgoritam 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 naredbiU 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 nemoraju 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 dvauvjeta: n>=0 i n<=13. Ovaj se uvjet u C jeziku zapisuje s dva relacijska izraza povezanalogič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: %dn", 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 seoni koriste u implementaciji programskih algoritama. Čitav se program izvršavao unutar jednefunkcije – 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 poznatapravila njihove upotrebe i efekti koje one uzrokuju.5.3.1 Korištenje funkcija iz standardne biblioteke C jezika Funkcije se u programiranju koriste slično načinu kako se koriste funkcije u matematici.Kada se u matematici napiše y=sin(x), x predstavlja argument funkcije, a ime funkcije sinoznačava pravilo po kojem se skup vrijednosti, kojem pripada argument x, pretvara u skupvrijednosti koje može poprimiti y. Funkcija sin() se može koristiti i u izrazima C-jezika jer je implementirana u standardnojbiblioteci funkcija. Primjerice, dio programa, u kojem se ona koristi, može biti sljedećegoblika: #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" ukojoj je specificiran prototip (ili deklaracija) funkcije sin(). Pregledom te datoteke može sepronaći specifikacija prototipa funkcije sin() oblika: double sin(double);Prototip iskazuje da argument funkcije mora biti vrijednost tipa double i da funkcija u izrazevraća vrijednost tipa double. Općenito, funkcija može imati više argumenata. Oni se navode u zagradama iza imenafunkcije i odvajaju zarezom. Tip vrijednosti kojim rezultira izvršenje funkcije uvijek se navodiispred imena funkcije. Deklaracija prototipa završava znakom točka-zarez. Ime argumenta funkcije nije navedeno već samo tip argumenta. Ime argumenta može biti inapisano (primjerice, double sin(double x) ), međutim, u prototipu ono nema nikakviznačaj jer deklaracija prototipa služi kompilatoru jedino kao pokazatelj s kojim tipovimavrijednosti će se koristiti funkcija. Važno je zapamtiti da C funkcije “uzimaju” vrijednost svojih argumenata za proračunnovih vrijednosti ili za ostvarenje nekog drugog procesa. Argument funkcije može biti bilo kojiizraz 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 naredbiy=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 naredbekoje realiziraju tu funkciju. Funkcija iz koje se poziva funkcija, naziva se pozivna funkcija, (uovom 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ćavrijednost 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 redoslijedomse izvršavaju operacije u navedenoj naredbi pridjele vrijednosti. U C jeziku vrijedi pravilo da sepri 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 upotrebljenakao argument za ponovni poziv funkcije sin(). Konačno, dobivenoj će vrijednosti bitipribrojena 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ćojsekciji.5.3.2 Korisnički definirane funkcije Sada će biti pokazano kako korisnik može definiranja neku funkciju i kako se ona uključujeu 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 returnoznač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 seprethodno izračuna vrijednost izraza koji je napisan iza riječi return. Vrijednost tog izraza jevrijednost 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štajurezultata izraza y*y. Funkcija vraća vrijednost te varijable. Iako ova verzija funkcijekvadrat() izgleda bitno drugačije od prve verzije, ne mora biti nikakve razlike u načinu kakose 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 registreprocesora, jer se njima najbrže pristupa. Iz ovog razloga u C jeziku je omogućeno da se pomoćuključne riječi register, napisane ispred deklaracije cjelobrojne varijable, sugerirakompilatoru da za smještaj varijable koristi registre procesora. Primjerice, sljedeći oblikfunkcije 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, jermoderni optimizirajući kompilatori mnogo efikasnije koriste procesorske registre, nego što tomože učiniti programer tijekom procesa programiranja.5.3.2 “void” funkcije U programskim se jezicima često koristi dva tipa potprograma: funkcije i procedure.Procedura je potprogram koji vrši neki proces, ali ne vraća nikakvu vrijednost. Pošto se u C-jeziku svi potprogrami nazivaju funkcije, onda se kaže da je procedura funkcija koja vraća ništa(eng. void). Primjerice, u trećem poglavlju korištena je funkcija void hello() za ispisporuke "Hello World!". Pomoću ključne riječi void označava se da je tip vrijednosti koji funkcija vraća "ništa", odnosno da je nevažan. Poziv procedure se vrši njezinim imenom. Pošto procedure ne vraćaju nikakvu vrijednost, ne mogu se koristiti u izrazima. U proceduri se ne navodi ključna riječ return, iako se može koristiti (bez argumenta) ako se želi prekinuti izvršenje procedure prije izvršenja svih naredbi koje se pozivaju u proceduri.5.3.3 Primjer: funkcija za proračun n! Za proračuna n-faktorijela zgodno je definirati funkciju koja obavlja taj proračun. Prototipte funkcije može biti oblika: int factorial(int n);Funkcija factorial() će kao argument koristiti vrijednost tipa int. Primjena ove funkcije uizrazima rezultirat će vrijednošću tipa int koji predstavlja vrijednost n-faktorijela. Definicija iprimjena 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: %dn", n, factorial(n)); return 0; } Bitno je uočiti da se u glavnom programu više ne koriste varijable k i nfact. Te varijablesu deklarirane unutar funkcije factorial(), jer su one potrebne samo za vrijeme dok seizvršava ta funkcija. U C jeziku vrijedi opće pravilo da sve varijable, koje se definiraju unutar bloka ili tijelafunkcije, zauzimaju memoriju samo dok se izvršava taj blok ili funkcija. Kada započneizvršenje funkcije, skriveno od korisnika rezervira se dio memorije za te varijable, i to u dijelumemorije koja se uobičajeno naziva stog (eng. stack). Nakon izvršenja funkcije, a prije nego senastavi izvršenje programa iz pozivne funkcije, ta se memorija ponovo smatra slobodnom zakorištenje. Ovo ujedno znači da se varijable, koje se deklariraju u nekoj funkciji, mogu koristitisamo u toj funkciji. One se stoga po dosegu imena ili vidljivosti (eng. scope) nazivaju lokalnevarijable, a pošto im je vrijeme postojanja ograničeno na vrijeme u kojem se izvršavaju naredbefunkcije, nazivaju se i automatske varijable.5.3.4 Funkcija za proračun exZadatak je napisati funkciju kojom se približno određuje vrijednost funkcije ex (e = 2. 718282) irezultat usporediti s vrijednošću koja se dobije pomoću standardne funkcije exp(), kojoj jeprototip - double exp(double x) - deklariran u datoteci "math.h".Metod: Koristeći razvoj u red: ex = 1 + x/1! + x2 /2! + x3 /3! + .. zbrajati članovi reda, za dati x, sve dok razlika od prethodnog rezultata ne bude manja od zadane preciznosti eps. Primjerice, za x = 1.0, i eps = 0.0001 trebat će zbrojiti 10 članova reda.Specifikacija funkcije: double my_exp(double x, double eps); Parametri: x - vrijednost za koju se računa ex , tipa double eps - zadana preciznost proračuna, tipa double Rezultat: vrijednost tipa double, jednaka vrijednosti exAlgoritam: Razvoj u red funkcije ex ima karakteristiku da se i-ti pribrojnik reda dobije tako dase prethodni pribrojnik pomnoži s x/i. Koristeći tu činjenicu, može se primijeniti sljedećiiterativni 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 funkcijedouble fabs(double x) koja je deklarirana u <math.h>.Realizacija programa: /*Datoteka: ex.c*/ #include <stdio.h> #include <math.h> double my_exp(double x, double epsilon) { int i = 1; double pribroj = 1.0; double ex = 1.0, preth_ex = 0.0; while (fabs( ex - preth_ex) > epsilon) { preth_ex = ex; pribroj = pribroj * x / i; ex = ex + pribroj; i = i + 1; } return ex; } int main( void) { double eps, x, ex; printf(" Unesi x i preciznost eps:n"); scanf("%lf%lf", &x, &eps); ex = my_exp(x, eps); printf(" e^%f = %f; (tocno: %f)n", x, ex, exp(x)); return 0; }Izvršenjem programa dobiju su rezultati: c:>ex Unesi x i preciznost eps: 1 .00001 e^1.000000 = 2.718282; (tocno: 2.718282) c:>ex.exe Enter x and the eps: 2 .0001 e^2.000000 = 7.389047; (tocno: 7.389056) U prethodnom programu istim imenom (ex) su deklarirane varijable u funkciji main() i ufunkciji my_exp(). Postavlja se pitanje: da li je to ista varijabla ili se radi o dvije različitevarijable? Na to pitanje daju odgovor pravila dosega ili postojanosti identifikatora. Pravilo je dase u različitim blokovima mogu deklarirati varijable s istim imenom. To su onda različitevarijable koje postoje samo u bloku u kojem su definirane. O tome će biti više govora upoglavlju 8. Na sličan način su definirane mnoge matematičke funkcije iz standardne biblioteke (vidiDodatak C). 69
  • 70. 5.4 ZaključakDo sada su korišteni sljedeći elementi C jezika:1. Varijable i funkcije su zapisivane simboličkim imenima. U iskazima deklaracije svim varijablama je uvijek označen tip. To omogućuje kompilatoru da rezervira memoriju potrebnu za smještaj vrijednosti varijable.2. Numeričke konstante i stringovi su zapisivani u literalnom obliku – upravo onako kako se zapisuju i u govornom jeziku..3. Korišteni su različiti operatori pomoću kojih se formiraju aritmetički, relacijski i logički izrazi.4. Korištene su naredbe kojima se određuje izvršenje procesa u računalu. Najprije su korištene tzv. proste naredbe: pridjela vrijednosti i poziv izvršenja standardnih funkcija printf() i scanf(). Zatim su korištene tzv. strukturalne naredbe: sekvenca naredbi koja se omeđuje vitičastim zagradama, while-petlja kojom se kontrolira tijek iterativnih procesa, if- naredba, pomoću koje se uvjetno određuje izvršenje neke naredbe, te if-else naredba, pomoću koje se vrši selekcija naredbi. Kasnije će biti opisane još neke naredbe za kontrolu toka programa.5. Opisan je jednostavni način interakcije s korisnikom programa.6. Pokazano je kako se koriste funkcije iz standardne biblioteke i kako korisnik može definirati nove funkcije.7. Pokazano je da se funkcija može pozivati višestruko.8. Pokazano je da se proračuni u računalu mogu izvršiti s ograničenom točnošću. Na primjeru eksponencijalne funkcije pokazano je kako je implementirana većina trigonometrijskih funkcija.9. Razvijen je algoritam za proračun n-faktorijela i izvršena implementaciju tog algoritma u C jeziku. Sam tijek razvoja algoritma može programerima - početnicima biti zbunjujući, jer su stalno vršene dodatne analize i dorada algoritma. Iskusniji programeri znaju da je to jedini ispravni način razvoja programa, jer se samo postupnom analizom i doradom programa može napraviti kvalitetan program. Razvoj programa postupnom analizom i doradom (ili razvoj u koracima preciziranja) jemetoda koju su popularizirali E. Dijkstra, u knjizi "Structured Programming", Academic Press,1972, i N. Wirth u članku "Program Development by Stepwise Refinement",CACM, April1971.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 idalje vrijedi izneseni metodološki pristup razvoju programskih algoritama. 70
  • 71. 6 Izrazi i sintaksa C jezikaNaglasci: • 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 sintakse6.1 Izrazi Izrazi su zapisi koji sadrže operande i operatore. Svaki izraz daje neku vrijednost. Operandimogu biti varijable, funkcije i konstante. U izrazima može biti više operatora i više različitihtipova operanada.S obzirom na složenost izraza razlikuju se: • Unarni izrazi – imaju samo jedan operator i jedan operand, • Binarni izrazi – imaju dva operanda i jedan operator, • Ternarni izrazi – imaju tri operanda i dva operatora, • Složeni izrazi – sastoje se od više operanada, operatora i zagrada koje služe za grupiranje izraza. Pravilo je da se najprije računa vrijednost izraza koji je napisan u zagradama, a zatim se ta vrijednost tretira kao prosti operand. Ukoliko nema zagrada, tada za redoslijed izvršenja složenog izraza vrijede posebna pravila prioriteta i asocijativnosti djelovanja operatora.S obzirom na upotrebu različitih operatora, izrazi mogu biti aritmetički, relacijski i logički.Bit će pokazno: • Kako se izvršavaju izrazi? • Koji su pravila prioriteta i asocijativnosti djelovanja operatora? • Kako se vrši pretvorba tipova ako u nekom izrazu postoji više različitih tipova?6.1.1 Aritmetički izrazi Binarni aritmetički izrazi koriste dva operanda i jedan operator: + za zbrajanje, - zaoduzimanje, * za mnnoženje, / za djeljenje i % za ostatak dijeljenja cjelobrojnih tipova (modulooperacija). Operandi mogu biti varijable, konstante i funkcije koja vraćaju numeričkuvrijednost.Operator % se može primijeniti samo na cjelobrojne operande jer se njime dobija ostatakcjelobrojnog dijeljenja, primjerice izraz 71
  • 72. x % 2daje vrijednost ostatka dijeljenja s 2. Taj ostatak može biti 0 ili 1 (ako je 0, broj x je paran, a akoje 1, broj x je neparan). Unarni aritmetički izrazi imaju jedan operand i jedan operator: - za negaciju (dajenegativnu vrijednost) i + za afirmaciju (ne mijenja vrijednost operanda). Operatori se zapisujuispred 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čkihvarijabli 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 jepravilima prioriteta i asocijativnosti. Prioritet djelovanja operatora određuje koji se podizrazprvi 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 istogprioriteta. 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šavaizraz u zagradama.Kako se vrši potenciranje? U C jeziku ne postoji operator potenciranja. Kada je potrebno potencirati neki broj ilinumeričku varijablu, može se koristiti dva postupka: 1. ako se potencira s cijelim brojem tada se potenciranje može realizirati pomoću višestrukog množenja, primjerice a3 se realizira izrazom a*a*a a-3 se realizira izrazom 1/(a*a*a) 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 xy. Ova funkcija je deklarirana u <math.h>.6.1.2 Relacijski i logički izrazi Relacijski ili uvjetni izrazi se sastoje se od dva operanda numeričkog tipa i sljedećihoperatora: < 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, anenulta vrijednost predstavlja logičku vrijednost true.Logički operatori su: && logička konjunkcija (i) || logička disjunkcija (ili) ! negacijaDjelovanje 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 odaritmetičkih operatora viši Aritmetički prioritet operatori a + b < max || max == 0 && a == b <, <=, >, >= se izvršava kao: ==, != niži && (( a + b) < max) || (max == 0 && (a == b)) prioritet ||Primjer: Godina je prestupna ako je djeljiva sa 4, a ne i s 100, ali godine koje su djeljive s 400su 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 godinan", godina); else printf("%d nije prestupna godina n", godina);Primjer: Definirana je funkcija isupper() kojom se određuje da li neka cjelobrojnavrijednost 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 maloslovo. 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 brojeviZadatak: Napišite funcije Funkcija vraća vrijednost različitu od nule (true), ako je znak c int isupper(int c); veliko slovo int islower(int c); malo slovo int isalpha(int c); veliko ili malo slovo int iscntrl(int c); kontrolni znak int isalnum(int c); slovo ili znamenka int isdigit(int c); decimalna znamenka int isxdigit(int c); heksadecimalna znamanka int isgraph(int c); tiskani znak osim razmaka int isprint(int c); tiskani znak uključujući razmak int ispunct(int c); tiskani znak osim razmaka, slova ili znamanke int isspace(int c); razmak, tab, vert. tab, nova linija, povrat, nova stranicaOve funkcije su implementirane u standardnoj biblioteci, a njihova deklaracija je dana udatoteci "ctype.h".6.1.3 Bitznačajni operatori U C jeziku se koristi 6 bitznačajnih operatora, koji se mogu primijeniti na integralne tipove(char, short, int i long) . 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 navrijednost od n; 1010111000011011 n & 0000000000001111 0x000F ---------------- 0000000000001011 rezultatBitznač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 ostalibitovi su nepromijenjeni; 1010111000011011 n | 0000000000001111 0x000F ---------------- 1010111000011111 rezultat Bitznačajni "ekskluzivno ili" operator ^ postavlja bitove na vrijednost 1 na mjestima gdjesu 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 seupisuje 0).Dokažite: Kada posmačni operatori djeluju na varijable unsigned tipa onda pomak bitova zajedno mjesto u lijevo je ekvivalentno množenju s 2, a pomak bitova za jedno mjesto u desno jeekvivalentno dijeljenju s cijelim brojem 2.Primjer: Definirana je funkcija getbit(x,n) kojom se ispituje da li u cijelom broju x n-ti bitima 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 binarnioblik. /* datoteka: binary.c */ /* program ispisuje binarni kod cijelog broja*/ #include <stdio.h> int main() { int x, i, n; printf("Otkucaj cijeli broj:n"); scanf("%d", &x); n = 8*sizeof(x); printf("Binarni kod je: "); i =n-1; while(i >=0) printf("%d",getbit(x,i--)); printf("n"); return 0; }Ispis je sljedeći: Otkucaj cijeli broj: -2 Binarni kod je: 11111111111111111111111111111110 ili Otkucaj cijeli broj: 67 Binarni kod je: 000000000000000000000000010000116.1.4 Složeni operatori pridjele vrijednostiIzraz oblika i = i + 2u kojem se ista varijabla pojavljuje s obje strane znaka pridjele vrijednosti, može se zapisati uobliku: i += 2Operator += se naziva složeni operator pridjele vrijednosti.Ovaj oblik se može primijeniti na većinu binarnih operatora: +=, -=, *=, /=, %=, <<=,>>=, &=, ^= i |=, koristeći opće pravilo:Ako su izraz1 i izraz2 neki izrazi, tada izraz1 op= izraz2 76
  • 77. je ekvivalentno izraz1 = (izraz1) op (izraz2) pri tome izraz1 mora biti izraz koji označava položaj u memoriji (ime varijable ili dereferencirani pokazivač). Ovi operatori, kao i operator pridjele vrijednosti, imaju niži prioritet od aritmetičkih,relacijskih i logičkih operatora, stoga iskaz x *= y + 1;znači x = x * (y + 1);a ne x = x * y + 1;Primjer: Definirana je funkcija brojbita(x) koja vraća broj bita koji u argumentu x imajuvrijednost 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 kojisu u tom broju različiti od nule.6.1.5 Ternarni uvjetni izrazTernarni izraz se sastoji od tri izraza međusobno odvojena upitnikom i dvotočkom: izraz1 ? izraz2 : izraz3a značenje mu je slijedeće: ako je izraz1 različit od nule, vrijednost ternarnog izraza je jednakaizrazu2, a ako je izraz1 jednak nuli vrijednost ternarnog izraza je jednaka izrazu3. Primjerice unaredbi: max = (x>y) ? x : y;vrijednost varijable max će biti jednaka x ako je x>y, u suprotnom vrijednost od max će bitijednaka 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 uizrazima.6.2 Automatska i explicitna pretvorba tipovaAutomatska pretvorba tipova Svaki izraz daje neku vrijednost čiji tip ovisi o tipu članova izraza. Kada su u nekom izrazusvi č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 usvim izrazima. Kada u nekom izrazu ima više različitih tipova tada kompilator u izvršnom koduvrši automatsku pretvorbu tipova. Princip je da se uvijek izvršava jedna operacija s maksimalnodva operanda. Ako su ta dva operanda različitog tipa onda se prije označene operacije vršipretvorba tipa niže opsežnosti u tip više opsežnosti. Opsežnost tipa, u redoslijedu od manje prema većoj opsežnosti je: char → int → unsigned → long → float → double.Primjerice, ako se koriste varijable int j=5, k=7; float x=2.1;u izrazu: j+7.1*(x+k)on se izvršava sljedećim redoslijedom: 1. najprije se izvršava proračun izraza u zagradama. U tom izrazu se najprije vrijednost varijable k pretvara (kodira) u tip float, jer je drugi operand tipa float. Zatim se toj vrijednosti dodaje vrijednost varijable x. 2. Vrijednost dobivenog izraza se zatim množi s realnom konstantom 7.1, jer množenje ima viši prioritet od zbrajanja. 3. Konačno preostaje da se zbroji vrijednost varijable j s vrijednošću prethodno izračunatog izraza (7.1*(x +k)), koji je realnog tipa. Pošto je to izraz s dva različita tipa, najprije se vrši pretvorba vrijednosti varijable i u tip float, i tek tada se izvršava operacija zbrajanja. 78
  • 79. Pretvorba tipova u naredbi pridjele vrijednosti Pretvorba tipova u naredbi pridjele vrijednosti se uvijek vrši tako da se vrijednost koja sedobije iz izraza koji je na desnoj strani pretvara u tip koji ima varijabla na lijevoj strani. Uslučaju da je s lijeve strane tip veće opsežnosti pretvorba se uglavnom može izvršiti bez gubitkatočnosti. Primjerice, nakon izvršenja naredbi float x; int i = 3; x = i; printf("x=%f", x);bit će ispisano: x=3.00000. U slijedećem slučaju pretvorba tipa int u tip unsigned neće imati smisla. Nakonizvršenja naredbi: unsigned u; int i = -3; u = i; printf("u=%u", u);bit će ispisano: u= 4294967293. Kada se u izrazima miješaju tipovi int i unsigned, logični rezultat možemo očekivati samo za pozitivne brojeve. Kada se s lijeve strane nalazi tip manje opsežnosti, pretvorba se vrši sa smanjenjemtočnošću. Često se vrijednost tipa float ili double pridjeljuje cjelobrojnoj varijabli, primjericeza 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 ucijeli broj obavi na način da se vrijednost cijelog broja što manje razlikuje od vrijednosti realnogbroja. To znači da ako je d=7.99, tada je poželjno da se ova vrijednost pretvori u cjelobrojnuvrijednost 8. To se može postići tako da se prije pretvorbe u cijeli broj decimalnom broju dodavrijednost 0.5, ako je pozitivan, odnosno da se od decimalnog broja odbije vrijednost 0.5 ako jenegativan. U tu svrhu može se definirati funkciju Double2Int(), koja vraća cjelobrojnuvrijednost 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 tipovaUkoliko se ispred nekog izraza ili varijable u zagradama zapiše oznaka tipa, primjerice (float) xtime se eksplicitno naređuje kompilatoru da se na tom mjestu izvrši pretvorba vrijednostivarijable 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 scijelim brojem pridijeljuje realnoj varijabli. int main() { int i1 = 100, i2 = 40; float f1; f1 = i1/i2; printf("%lfn", f1); return(0); }Dobije se ispis: 2.000000 Pri dijeljenju je izgubljen decimalni dio iako je rezultat izraza i1/i2 pridijeljen realnojvarijabli. 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 trioblika: 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čunajutako 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 intiznosi 32767. Stoga, ako bi se zbrojile dvije short int kodirane vrijednosti iznosa 32000rezultat bi bio veći od 32767. Operator (long) ispred jednog operanda osigurava da će sezbrajanje izvršiti na način kao da su operandi tipa long. 80
  • 81. 6.3 Definiranje sinonima tipa pomoću typedefKada 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 kaovarijabla 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 kasnijemož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 nekinovi tipovi. Njihova je upotreba korisna za povećanje apstraktnosti programskog zapisa. PremaANSI standardu, u C jeziku je definirano nekoliko typedef tipova kako bi se jasnije označilopodručje njihove primjene. Primjerice, size_t predstavlja tip unsigned int, kojim se čestooznačava veličina, u bajtima, objekata smještenih u datotakama ili u memoriji. Implementacijaje 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 uopisu standardne C-biblioteke).6.4 Formalni zapis sintakse C-jezikaPisanje 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 leksemijezika (niz znakova koji čini prepoznatljivu nedjeljivu cjelinu), na zadanom alfabetu (ASCIIskup 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 int , float , s definiranim operacijama char Konstante literalni zapis vrijednosti 0 , 123.6 , osnovnih tipova "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 sum = sum + i; kontrole toka programa while (--i) if(!x).. else ..; Funkcije imenovano grupiranje naredbi main() (potprogrami) printf(...) Kompilacijska skup međuovisnih varijabli i funkcija koji datoteka.c jedinica se kompilira kao jedinstvena cjelina 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, omorfologiji (tvorba riječi) i fonetskom naglasku. U programskim jezicima se ne koristimorfološka i fonetska komponenta jezika, pa se gramatika svodi na sintaksu, također, dozvoljenje samo onaj raspored riječi koji daje nedvosmisleno značenje. Uobičajeno se kaže da gramatikaprogramskih 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 seopisuje metajezikom naziva se ciljni jezik. Za opis semantike nekog jezika ne postoje prikladnimetajezici 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 kodmože biti spremljenu u jednoj datoteci ili u više datoteka koje se u toku jezičkogpretprocesiranja formiraju kao jedna datoteka, koja se naziva kompilacijska jedinica. Zatim sevrši leksička analiza izvornog koda, na način da se izdvoje leksemi (nizovi znakova kojipredstavljaju nedjeljivu cjelinu). Ukoliko je leksem zapisan u skladu s leksičkom strukturomjezika on predstavlja terminalni simbol jezika (token) kojem se u radu kompilatora pridjeljujejedinstveno značenje. U jezičke simbole spadaju: ključne riječi (if, else, while,...), specijalnisimboli (oznake operatora i separatora), identifikatori (imena varijabli, konstanti, funkcija,procedura i labele), literalne numeričke i tekstualne konstante. Pojedinom simbolu pridjeljuju serazličiti atributi koji se koriste u procesu generiranja koda. Primjerice, za varijable se unosiatribut koji opisuje tip varijable, ili uz literalno zapisanu numeričku konstantu se unosi i binarnokodirana numerička vrijednost konstante. Sintaktički analizator (parser) dobavlja jezičkesimbole i određuje da li su oni grupirani u skladu s definiranom sintaksom. Ukoliko je tozadovoljeno, 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). Njihmož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 pojavljujusljedeć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" predstavljaleksički separator. On se ne smatra simbolom jezika i može se umetnuti između leksemaproizvoljan 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 3Za 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, daklepredstavlja sintaktički entitet Izraz, a iskazi x=5.4; i x=a; predstavljaju iskaze dodjelevrijednosti, dakle pripadaju sintaktičkom entitetu Iskaz. Ako se izneseno sintaktičko praviloshvati kao zapis u nekom sintaksnom metajeziku onda IskazIf, Izraz i Iskaz predstavljajumetajezičke varijable koje u odnosu na ciljni jezik predstavljaju neterminalne simbole, "jedefiniran kao" i "ili kao" su metajezički operatori, a leksemi: if, then i else i znakovi zagrada sumetajezičke konstante koje odgovaraju simbolima ciljnog jezika, pa se nazivaju terminalnisimboli ili tokeni. Uočimo da "ili kao" operator ima značaj logičkog operatora ekskluzivnedisjunkcije. Sintaktička pravila, kojima se jedan neterminalni simbol definira pomoću niza terminalnihi/ili neterminalnih simbola, nazivaju se produkcije jezika. Prema ANSI/ISO standardu produkcije C-jezika se zapisuju na sljedeći način: 1. Operator "je definiran kao" je zamijenjen znakom dvotočke, a produkcije imaju oblik: neterminalni_simbol : niz terminalnih i/ili neterminalnih simbola 2. Alternativna pravila ("ili kao") se pišu u odvojenim redovima. 3. Neterminalni simboli se pišu kurzivom. 4. Terminalni simboli se pišu na isti način kao u ciljnom jeziku 5. Opcioni simboli se označavaju indeksom opt (Simbolopt ili Simbolopt).Primjerice, zapis produkcije if-else iskaza glasi IskazIf : if (Izraz) Iskaz else Iskaz if (Izraz) IskazOvo se pravilo može se napisati i na sljedeći način: IskazIf : if (Izraz) Iskaz ElseIskazopt 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 pravilopredstavlja pravilo proste uvjetne naredbe. Gornja pravila ćemo proširiti na način da se operator "ili kao" može eksplicitno označitiokomitom crtom (|), zbog dva razloga: 1. Na taj način gornja pravila (1-4) su ekvivalentna popularnoj BNF notaciji (BNF notacija je metajezik razvijen 1960. godine prilikom definicije programskog jezika ALGOL, pri čemu su bitne doprinose dali J.W.Bakus i P.Naur, pa BNF predstavlja kraticu za "Backus-ova normalna forma" ili "Backus-Naur-ova forma"). 2. Na taj način se alternativne produkcije mogu pisati u istom reduPomoću prethodno definiranih pravila lako se može definirati i leksička struktura jezika.Primjerice, temeljni se leksički objekti znamenka i slovo mogu definirati pravilima: slovo : A⎪B⎪C⎪D⎪E⎪F⎪G⎪H⎪I⎪J⎪K⎪L⎪M⎪N⎪O⎪P⎪Q⎪R⎪S⎪T⎪U⎪V⎪W⎪X⎪Y⎪Z ⎪a⎪b⎪c⎪d⎪e⎪f⎪g⎪h⎪i⎪j⎪k⎪l⎪m⎪n⎪o⎪p⎪q⎪r⎪s⎪t⎪u⎪v⎪w⎪x⎪y⎪z. znamenka : 0⎪1⎪2⎪3⎪4⎪5⎪6⎪7⎪8⎪9. heksa_znamenka : 0⎪1⎪2⎪3⎪4⎪5⎪6⎪7⎪8⎪9⎪A⎪B⎪C⎪D⎪E⎪F⎪a⎪b⎪c⎪d⎪e⎪f. oktalna_znamenka : 0⎪1⎪2⎪3⎪4⎪5⎪6⎪7⎪. Koristeći objekte znamenka i slovo može se definirati objekt znak (koji može biti slovo iliznamenka): znak : znamenka ⎪ slovo.Sintaksa znaka može po potrebi biti i drugačije definirana, naročito ukoliko se pod pojmomznak 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štenjemrekurzivnog 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 iliako sadrži niz znakova i s desne strane još jedan znak. Dakle, alternativno pravilo prepoznajesve 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 izaznaka sadrži niz znakova. Uočimo da alternativno pravilo, također, prepoznaje nizove kojisadrže dva ili više znakova. Identifikatori u C-jeziku (nazivi varijabli, labela, funkcija i tipova) moraju početi sa slovomili 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 razdvojenihzarezom. lista_identifikatora : identifikator ⎪ identifikator , lista_identifikatoraU Dodatku B dana je potpuna specifikacija sintakse C jezika. 86
  • 87. 7 Proste i strukturalne naredbe C jezikaNaglasci: • 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 raziniapstrakcije računarskog procesa, kojeg predstavljaju, dijele se na proste naredbe i strukturalnenaredbe. U prethodnim poglavljima se korištene strukturalne naredbe tipa sekvence, selekcije(if-else-naredba) i iteracije (while-petlja), te proste naredbe pridjele vrijednosti i pozivapotprograma. Interesantno je napomenuti da se pomoću tih naredbi može napisati bilo kojialgoritam koji je moguće izvršiti računalom. U ovom poglavlju će biti opisane sve naredbe Cjezika koje se koriste za kontrolu toka programa.7.1 Proste naredbe Proste ili primitivne naredbe su one naredbe koje odgovaraju naredbama strojnog jezika. UC jeziku "najprostija" naredba je izraz iza kojeg se napiše znak točka-zarez. Takova naredba senaziva naredbeni izraz. Sintaksa naredbe je: naredbeni izraz : izrazopt ;Primjerice, 1+3*7; je naredba izračun vrijednosti izraza 1+3*7. Kada računalo izvrši operacijeopisane ovim izrazom, rezultat će ostati negdje u memoriji ili u procesoru računala, stoga ovanaredba 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 jeovakva naredba nazivana naredba pridjele vrijednosti, jer se ona tako naziva u većiniprogramskih jezika. U C jeziku se ova naredba zove naredbeni izraz pridjele vrijednosti, jerznak = predstavlja operator pridjele vrijednosti koji se može koristiti u izrazima. Primjerice, unaredbi x = 3 + (a=7);znak = se koristi dva puta. Nakon izvršenja ove naredbe vrijednost varijable a je 7, a vrijednostvarijable 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 toizgledalo paradoksalno, ovu se naredbu često koristi, i često je uzrok logičkih pogreški uprogramu. Zašto se koristi i kako nastaju pogreške zbog korištenja ove naredbe bit će pokazanokasnije. 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 zapisaje potpuno ista, jer C prevodilac složene izraze razlaže u više prostih izraza koji se mogudirektno prevesti u strojni kôd procesora. U proste naredbe spadaju još naredbe bezuvjetnog i uvjetnog skoka. Pomoću ovih naredbise može eksplicitno zadati da se izvršenje programa nastavi naredbom koja je označena nekimimenom.Sintaksa označene naredbe je: označena_nareba : identifikator : naredbaIdentifikator 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 naredbomkoja je označena identifikatorom i znakom dvotočke. Primjerice, u nizu naredbi: goto next; naredba2 next: naredba3nikad se neće izvršiti naredba2, jer se u prethodnoj naredbi vrši bezuvjetni skok na naredbukoja 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 vrijednostinekog 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 akonije izvršava se slijedeća naredba. Naredbe uvjetnog i bezuvjetnog skoka vjerno opisuju procese u računalu, međutim njihovase upotreba ne preporučuje. Sljedeći primjer pokazuje zašto programeri "ne vole gotonaredbu". 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 naredbaoznačena s L1, u suprotnom izvršava se naredba goto L2. Primjenom logičke negacije naizraz 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) naredbaOva naredba spada u strukturalne naredbe selekcije. Ona, već na "prvi pogled", jasno iskazujekoji proces treba izvršiti. Ako se pak pogleda prethodna dva zapisa, u kojima je korištena goto-naredba, trebat će znatno više mentalnog napora za razumijevane opisanog procesa. Ovajproblem posebno dolazi do izražaja kod većih programa, gdje primjena goto naredbe dovodi dostvaranja nerazumljivih i "zamršenih" programa. Jedino kada se može opravdati upotreba goto naredbe jest kada se želi napisati algoritamkoji treba biti "ručno" preveden na asemblerski jezik. U svim ostalim slučajevima, uprogramiranju i u razvoju algoritama, treba koristiti naredbe selekcije i petlje kojima se dobijajasna i pregledna struktura programa.7.2 Strukturalne naredbe7.2.1 Složena naredba ili blok Pod pojmom složene naredbe podrazumijeva se niz naredbi i deklaracija napisan unutarvitičastih zagrada. Naziva se i blok jer se u okviru neke druge strukturalne naredbe možetretirati kao cjelina. Lijeva zagrada { označava početak, a desna zagrada } označava krajbloka.Sintaksa složene naredbe je: složena-naredba : { niz-deklaracijaopt niz-naredbiopt }Unutar bloka dozvoljeno je deklarirati varijable, ali samo na mjestu neposredno iza vitičastihzagrada. 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 kojaima karakter lokalne varijable, što znači da joj se ime može koristiti samo unutar bloka. Uočiteda se najprije vrijednost od x upisuje u tmp. Zatim se varijabli x pridjeljuje vrijednost varijabley, 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 obavljaodstranjenje iz memorije lokalnih varijabli po izlasku iz bloka u kojem su definirane. Kasnije ćeo ovom problemu biti više govora. Sa semantičkog stajališta blok analizirano kao niz deklaracija i naredbi, dok u analizi sintakse i strukture programa, blok predstavlja jedinstvenu naredbu. To ujedno znači da u zapisu sintakse, na svakom mjestu gdje pišemo naredba, podrazumijeva se da može biti napisana i prosta i složena naredba i ostale strukturalne naredbe.7.2.2 Naredbe selekcije Općenito se pod selekcijom nazivaju programske strukture u kojima dolazi do grananjaprograma, 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 nekogizraza, a u switch-case naredbi grananje može biti višestruko, ovisno o cjelobrojnoj vrijednostinekog selektorskog izraza.Uvjetna naredba (if-naredba)Sintaksa if-naredbe je : if_naredba: if ( izraz ) naredbagdje 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 nastavljanaredbom koja slijedi iza if-naredbe. 90
  • 91. Slika 7.1 Dijagram toka if- naredbeUzmimo primjer da analiziramo dvije varijable : x i y. Cilj je odrediti koja je od te dvijevrijednosti manja, a zatim tu manju vrijednost upisati u varijablu imena min. To se možeostvariti 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 naredba2gdje 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 senaredba2.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 granicaintervala, 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 >= 3) { if (x <= 9) if (x <= 9) unutar = 1; unutar = 1; else unutar = 0; else } unutar = 0; else else unutar = 0; 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 elsenaredbi, jer vitičaste zagrade označavaju da umetnuta if-else naredba predstavlja prosti umetnutiiskaz. Ta pripadnost nije očita u drugom, i posebno ne u trećem zapisu, iako su to sintaktičkipotpuno ispravni C iskazi, jer se u C-jeziku koristi pravilo da "else naredba" pripada najbližemprethodno napisanom "if uvjetu". Dobar je programerski stil da se umetnuti iskazi pišu uodvojenim redovima s uvučenim početkom reda, kako bi se naglasilo da se radi o umetnutomiskazu. “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četkuif 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 uvjetizvršava se naredba Ne. 92
  • 93. Slika 7.3 Dijagram toka za višestruku logičku selekcijuProgramski 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 NePrimjer: 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 ispisujeporuka "Otipkali ste pogrešno slovo". Za dobavu znaka s tipkovnice koristi se standardnafunkcija 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 Clintonn (b) Bill Gatesn (c) Bill Thirdn"); printf("nOtipkaj slovo.n"); ch=getchar(); if (ch ==A || ch ==a) printf ("Tocnon"); else if (ch ==B || ch ==b) printf ("Nije tocnon"); else if (ch ==C || ch ==c) printf ("Nije tocnon"); 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 temeljuispitivanja cjelobrojne vrijednosti nekog izraza kojeg se naziva selektorski izraz. Logika switch-case naredbe je prikazana na slici 7.4. Slika 7.4 Prikaz selekcije u switch-case naredbi U dijagramu toka "selektorski" izraz je označena sa sel. Skup {a,b,..z} podrazumjeva skuprazličitih konstanti cjelobrojnog tipa.U slučaju da izraz sel poprimi vrijednost konstante “a”izvršava se naredba Na. Ako izraz sel ima vrijednost konstante “b” izvršava se naredba Nb. Akoizraz sel ima vrijednost iz skupa {z1,z2,..z3} izvršava se naredba Nz, a ako vrijednostselektorskog izraza nije iz skupa {a,b,..,z1,z2,..z3} izvršava se naredba Nx. To se u C jezikuzapisuje 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 ispitivanjeslijedeće case-naredbe. Ukoliko u ni jednoj case-naredbi nije pronađena vrijednost konstantekoja 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 unosikorisnik. 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 ) naredbapa bi prema tom pravilu za naredbu mogla biti zapisana bilo koja naredba. Međutim, semantičkiima smisla koristiti samo tzv. označene-naredbe: case konstanti-izraz : naredba i default : naredbate 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 1n");Njome je iskazano da će biti ispisano "n je jednak 1" u slučaju kada je n jednako 1. Ovanaredba nema praktičnog smisla, jer switch-naredba nije efikasno rješenje za ispitivanje samojednog slučaja. Tada je bolje koristiti if-naredbu. U slučaju kada se koristi više označenihnaredbi (što je redovit slučaj) sintaksa switch-case naredbe se može zapisati u znatnorazumljivijem obliku: 95
  • 96. niz-case-naredbi: switch-case-naredba: case-naredba switch ( izraz ) { | niz-case-naredbi case-naredba niz-deklaracijaopt case-naredba: niz-case-naredbi case konstanti-izraz : niz-naredbiopt prekidopt default-naredbaopt default-naredba: } default: niz-naredbiopt prekid: break ;7.2.3 Naredbe iteracije - petljeIterativni procesi ili petlje su procesi u kojima se ciklički ponavlja programski kod koji jedefiniran unutar petlje, sve dok za to postoje potrebni uvjeti. Uvjete ponavljanja ili izlaska izpetlje 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 petljePokazat ćemo kako se ovi tipovi petlji realiziraju u C jeziku. Slika 7.5 PetljeZa iskaze petlje, kao i za sve strukturalne iskaze, vrijedi da unutar njih mogu biti definirani svitipovi strukturalnih iskaza. Prema tome, unutar petlje može biti definiran proizvoljan brojumetnutih 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 ) naredbaZnačenje je: dok je (eng. while) izraz različit od nule, izvršava se naredba. Izraz predstavljauvjet ponavljanja petlje. 96
  • 97. Slika 7.6 Dijagram toka while petljeUvjet ponavljanja petlje se ispituju na samom ulazu u petlju. Postoji mogućnost da se naredbauopć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; i = n; f = 1; f = 1; while (i < n) while (i > 1) { { i++; f *= i; f *= i; i–-; } }Ako je n<2 naredbe unutar while petlje se ne izvršavaju, stoga je prije početka petlje definiranoda f ima vrijednost 1. Kontrola izvršenja petlje obavlja se pomoću cjelobrojne kontrolnevarijable i, kojoj se vrijednost iterativno uvećava za 1 (u drugom iskazu se smanjuje za 1). Uprincipu, može se koristiti više kontrolnih varijabli. Prije početka while-petlje gotovo uvijek treba inicirati vrijednost neke kontrolne varijable,koja se koristi u uvjetu ponavljanja petlje. Stoga se može napisati obrazac korištenja while-petlje u obliku: iniciraj_kontrolne_varijable while ( izraz_s_kontrolnim_varijablama ) { niz_naredbiopt naredba_promjene_vrijednosti_kontrolnih_varijabliopt niz_naredbiopt }U nekim slučajevima je potrebno da naredba_promjene_vrijednosti_kontrolnih_varijabli budeprva naredba u petlji, u nekim slučajevima ona će biti posljednja naredba ili pak umetnutanaredba. Za zapis procesa u kojima je naredba_promjene_vrijednosti_kontrolnih_varijabliposljednja 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 izrazameđusobno odvojena točka-zarezom, a iza njih naredba koja čini tijelo petlje: for ( izrazopt ; izrazopt ; izrazopt ) naredba 97
  • 98. Primjerice, segment programa u kojem se računa n –faktorijela, se može zapisati pomoću for-petlje u obliku: f=1 for (i=n; i > 1; i--) f *= i; U prvom naredbenom izrazu se inicira kontrolna varijabla i na vrijednost n, u drugomizrazu se zapisuje uvjet ponavljanja pelje (i>1), a u trećem izrazu se zapisuje naredbeni izrazkojim se definira promjena kontrolne varijable pri svakom ponavljanju petlje(i--).Semantiku for-petlje može se objasniti pomoću ekvivalentne while-petlje izrazopt ; while ( izrazopt ) { naredba izrazopt; } U svakom od ovih izraza može se navesti više izraza odvojenih zarezom. Primjerice, zaprorač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 blokapetlje f=f*i uvrštena u listu izraza iteracije. Samim time, naredba petlje je transformirana unultu naredbu (tj. naredbu koja stvarno ne postoji, ali je sintaktički prisutna postavljanjemznaka 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 Cjeziku, a to je u slučaju kada se točka-zarez napiše odmah iza zagrada for naredbe. Time prestajedjelovanje for petlje na naredbu koja je definirana iza zagrada jer točka-zarez označava krajnaredbe 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 tunaredbu dok je izraz logički istinit. Temeljna karakteristika ove naredbe je da se naredbe u tijelupetlje izvršavaju barem jedan put. Slika 7.7 Dijagram toka do-while petljePrimjer: 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 Clintonn(2) Bill Gatesn(3) Bill Thirdn"); printf("nOtipkaj 1, 2 ili 3 <enter>!n"); ch = getchar(); }while( ch != 1 && ch != 2&& ch != 3); if(ch == 1) printf("Tocnon"); else printf("Nije tocnon"); 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; predstavljanaredbu za povrat na početak petlje. Logika break iskaza je: Pseudo-asembler C jezik L1: početak petlje početak petlje { ....... ....... 99
  • 100. if (L) goto L2; if (L) break; ....... ....... kraj petlje } kraj petlje L2: ........ ......Prekid petlje, sam za sebe nema nikakovog smisla već se uvijek iskazuje u sklopu neke uvjetnenaredbe. Ukoliko postoji više umetnutih petlji, prekida se izvršenje samo one unutarnje petlje ukojoj je definiran iskaz prekida.Logika continue iskaza je: Pseudo-asembler C jezik L1: početak petlje početak petlje { ....... ....... if (L) goto L1; if (L) continue; ....... ....... kraj petlje } kraj petljeBekonačne petljeUkoliko 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 jetrajanje njenog izvršenja beskonačno, to je struktura koja ima ulaz ali nema izlaza. Sprogramskog pak stajališta beskonačne petlje imaju smisla kod onih programa kod kojih seciklički ponavlja jedan ili više procesa za vrijeme dok je računalo uključeno. Primjerice, jedantakovi program je i operativni sustav računala. Od beskonačne petlje se uvijek može dobitipetlja 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>. Programkoristi break i continue naredbe u beskonačnoj pelji, koja se izvršava sve dok se ne otkuca 5malih slova. Nakon toga program ispisuje tih 5 malih slova. Ako se otkuca <enter> prije negoje 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 NizoviNaglasci: • 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. Uprogramiranju, kao i u matematici, zanimaju nas nizovi kao kolekcija istovrsnih elemenata kojisu poredani jedan za drugim. Elementi niza su varijable koje se označavaju indeksom: ai označava i-ti element niza u matematici a[i] označava i-ti element niza u C jeziku i=0,1,2....8.1 Jednodimenzionalni nizovi8.1.1 Definiranje nizova Niz je imenovana i numerirana kolekcija istovrsnih objekata koji se nazivaju elementi niza.Elementi niza mogu biti prosti skalarni tipovi i korisnički definirani tipovi podataka.Označavaju se imenom niza i cjelobrojnom izrazom – indeksom – koji označava pozicijuelementa 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_tipaPrvi 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 jeprethodno 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 Memorijski raspored niza Aelementi niza zauzimaju sukcesivne lokacije u memoriji.Vrijedi pravilo: adresa sadržaj adresa(A)=adresa(A[0]) 1000 data[0] 1004 data[1] adresa(A[n])=adresa(A[0]) + n*sizeof(A[0])) 1008 data[2] 1012 data[3]Elementima niza se pristupa pomoću cjelobrojnog indeksa, 1016 data[4]primjerice: 1020 data[6] 1024 data[5] A[0] = 7; 1028 data[7] int i=5; 1032 data[8] A[2]= A[i]; for(i=0; i<9; i++) Napomena: int zauzima printf("%d ", A[i]; 4 bajta U C jeziku se ne vrši provjera da li je vrijednost indeksnog izraza unutar deklariranogintervala. Primjerice, iskaz: A[12] = 5;je sintaktički ispravan i kompilator neće dojaviti grešku. Međutim, nakon izvršenja ove naredbemože doći do greške u izvršenju programa, ili čak do pada operativnog sustava. Radi se o tomeda se ovom naredbom zapisuje vrijednost 5 na memorijsku lokaciju za koju nije rezerviranomjesto 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 indekselementa 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 %fn", suma); /*3.odredi indeks(imax) i vrijednost(max) najvećeg elementa */ imax = 0; max = A[0]; for(i=1; i<N; i++) { if(A[i] > max ) { max = A[i]; imax=i; } } printf ("%d. element je najveci (vrijednost mu je %f)n", imax+1, max); return 0; }Izvršenje programa može izgledati ovako: Otkucaj 5 realnih brojeva: 5 6.78 7.1 8 0.17 Suma unesenih brojeva je 27.050000 4. element je najveci (vrijednost mu je 8.000000)8.1.2 Inicijalizacija nizova Za globalno i statičko deklarirane nizove automatski se svi elementi postavljaju navrijednost nula. Kod lokalo deklariranih nizova ne vrši se inicijalizacija početnih vrijednostielemenata 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 vrijednostelemenata niza.Ako se inicijaliziraju svi potrebni elementi niza, tada nije nužno u deklaraciji navesti dimenzijuniza. Primjerice, int A[]= {1,2,23,4,32,5,7,9,6};je potpuno ekvivalentno prethodnoj deklaraciji. Broj elemenata ovakvog niza uvijek se možeodrediti 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 vrijednostnula.Kada se inicijalizira znakovni niz, tada se u listi inicijalizacije mogu navesti znakovnekonstante: char znakovi[2]= {O,K};Primjer: U programu hex.c korisnik unosi cijeli broj bez predznaka, zatim se vrši ispis broja uheksadecimalnoj notaciji. /* Datoteka: hex.c * ispisuje broj, kojeg unosi korisnik, u heksadecimalnoj notaciji */ #include <stdio.h> int main() { int num, k; unsigned broj; char hexslova []= {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F} ; char reverse[8] = {0}; /* zapis do 8 heksa znamenki u reverznom redu */ printf("Otkucaj cijeli broj bez predznaka: "); scanf("%u", &broj); if(broj <0) broj = -broj; printf("Heksadecimalni zapis je: "); num=0; do { k = broj % 16; /* iznos heksa znamenke */ reverse[num++] = hexslova[k]; /* oznaka heksa znamenke */ broj /= 16; /* odstrani ovu znamenku */ } while(broj != 0); /* num sadrži broj heksa znamenki */ /* ispis od krajnjeg (n-1) do nultog znaka */ for(k=num-1; k>=0; k--) printf("%c", reverse[k]); printf("n"); return 0; }Pri izvršenju programa ispis može biti: Otkucaj cijeli broj bez predznaka: 256001 Heksadecimalni zapis je: 3E801 Algoritam pretvorbe u heksadecimalnu notaciju je jednostavan. Temelji se na činjenici daostatak dijeljenja s 16 daje numeričku vrijednost heksadecimalne znamenke na mjestunajmanjeg 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 heksadecimalnanotacija broja, ali obrnutm redoslijedom). Zatim se broj dijeli s 16 i dobavlja sljedećaheksadecimalna 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 tomnizu nalazi na indeksu 0).Primjer: Histogram U datoteci "ocjene.dat", u tekstualnom obliku, zapisane su ocjene u rasponu od 1 do 10.Sadržaj datoteke "ocjene.dat” neka čini sljedeći niz ocjena: 2 3 4 5 6 7 2 4 7 8 9 1 3 4 6 9 8 7 2 5 6 7 8 9 3 4 5 1 7 3 4 10 10 9 10 5 7 6 3 8 9 4 5 7 3 4 6 1 2 9 6 7 8 5 4 6 3 2 4 5 6 1 3 4 6 9 10 7 2 10 7 8 1 Zadatak je izraditi program imena hist.c pomoću kojeg se prikazuje histogram ocjena u10 grupa (1, 2,.. 10), i srednja vrijednost svih ocjena. Podaci iz datoteke "ocjene.dat" se predajuprogramu "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 %dn", 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.2fn", (float)suma / izbroj[0]); return 0; }Program se poziva komandom: c:>hist < ocjene.datDobije se ispis: Ukupan broj ocjena je 73 10 ***** 9 ******* 8 ****** 7 ********** 6 ********* 5 ******* 4 ********** 3 ******** 2 ****** 1 ***** Srednja ocjena je 5.79Analiza 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] ćesadržavati broj ocjena veličine 1, izbroj[2] će sadržavati broj ocjena veličine 2, itd., aizbroj[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 ocjenekoristi se scanf() funkcija sve dok se na ulazu ne pojavi znak EOF (end-of-file), koji značikraj datoteke. Nakon toga se ispisuje histogram na način da se uz oznaku ocjene ispiše onolikozvjezdica 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 sukupnim brojem ocjena. Uočite da je u naredbi printf ("Srednja ocjena je %4.2fn", (float)suma / izbroj[0]);izvršena eksplicitna pretvorba tipa operatorom (float), jer srednja vrijednost može biti realnibroj.8.2 Prijenos nizova u funkciju Nizovi mogu biti argumeti funkcije. Pri deklaraciji ili definiranju funkcije formalniargument, 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, programrač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 %gn", produkt(A, N)); return 0; } Uočite de se vrijednost elemenata niza može mijenjati unutar funkcije. Očito je da se niz neprenosi 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 sedefinira tri funkcije: pojavnost ocjena u nizu izbroj bilježit će se funkcijom registriraj(), crtanjehistograma vršit će funkcija histogram(), a proračun srednje vrijednosti vršit će funkcijasrednja_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.2fn",srednjaocjena(izbroj)); return 0; }Primjer: Često je potrebno odrediti da li u nekom nizu od N elemenata postoji elementvrijednosti 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 vrijednostx, 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 prvogpronađ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 iovako: definiran je niz kojem su elementi 3 niza s 4 elementa tipa int.Dvodimenzionalni nizovi se često koriste za rad s Memorijski raspored nizamatricama. U tom slučaju nije potrebno razmišljati o adresa sadržajtome kako je niz složen u memoriji, jer se elementima 1000 x[ 0][ 0]pristupa preko dva indeksa: prvi je oznaka retka, a 1004 x[ 0][ 1]drugi je oznaka stupca matrice. 1008 x[ 0][ 2] 1012 x[ 0][ 3] Matrični prikaz niza je: 1016 x[ 1][ 0]x[0][0] x[0][1] x[0][2] x[0][3] 1020 x[ 1][ 1]x[1][0] x[1][1] x[1][2] x[1][3] 1024 x[ 1][ 2]x[2][0] x[2][1] x[2][2] x[2][3] 1028 x[ 1][ 3] 1032 x[ 2][ 0] 1036 x[ 2][ 1] 1040 x[ 2][ 2]Memorijski raspored elemenata dvodimenzionalnog 1044 x[ 2][ 3]niza, koji opisuju neku matricu, je takovi da suelementi složeni po redovima matrice; najprije prviredak, zatim drugi, itd..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 poredovima i stupcima. 110
  • 111. Elementima se pristupa preko indeksa niza. U sljedećem primjeru računa se suma svihelemenata 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 nenavodi prva dimenzija (kao i kod jednodimenzionalnih nizova), ali ostale dimenzije trebadeklarirati, 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 elemenatadvodimenzionalne 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. Brojstupaca je fiksiran već u deklaraciji niza na vrijednost 4. Očito da ova funkcija ima ograničenuupotrebu jer se može primijeniti samo na matrice koje imaju 4 stupca. Kasnije, priproučavanju upotrebe pokazivačkih varijabli, bit će pokazano kako se ova funkcija možemodificirati tako da vrijedi za matrice proizvoljnih dimenzija. 111
  • 112. 9 Blokovi, moduli i dekompozicijaprogramaNaglasci: • 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 prepoznatljivublokovsku strukturu. Praksa je pokazala da se time dobiju “čitljivi” programi, koje je lakoodrž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 zagradamaBlok niže razine može biti umetnut unutar bloka više ili iste razine proizvoljan broj puta, jedinose 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 ostatkuprograma. Da bi se to postiglo potrebno je dobro razumjeti pravila dosega identifikatora ipostojanosti varijabli.9.1.1 Doseg Doseg nekog identifikatora (eng. scope) je dio programa u kojem se taj identifikator možekoristiti. Deklaracijom argumenata funkcije i lokalnih varijabli stvaraju se novi identifikatori. Zanjih 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) { doseg x int i; double ex = 1.0, preth_ex = 0.0, … ; ... doseg ex return ex; } Lokalne deklaracije imaju prednost nad vanjskim deklaracijama. Kažemo da lokalnadeklaracija 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 preklopljenimdosezima, iako je to sintaktički dozvoljeno.9.1.2 Automatske i statičke varijable Lokalne varijable imaju ograničeno vrijeme postojanja, pa se nazivaju i automatskevarijable. One nastaju (u memoriji) pozivom funkcije u kojoj su deklarirane, a nestaju (izmemorije) nakon povrata u pozivnu funkciju. Kada se argumenti prenose u funkcije može se uzeti da se tada vrijednost stvarnihargumenata kopira u formalne argumente, koji pak imaju lokalni doseg. Argumenti funkcije seinicijaliziraju kao lokalne varijable, pa za njihovu upotrebu vrijede pravila kao za lokalnevarijable. 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 = %dn",a, x); a = 3; { int x = 4; printf(" a = %d, x = %dn", a, x); } printf(" a = %d, x = %dn", a, x); x = 5; /*nema nikakovi efekt*/ } int main( void) { int a = 1, b = 2; f( a, b); printf(" a = %d, b = %dn", 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 trajnorezervira mjesto u memoriji (postoji i nakon izvršenja funkcije), iako je njen doseg ograničenunutar tijela funkcije. U sljedećem primjeru opisana je funkcija incrCounter(), kojom se realizira brojač pomodulu mod. U funkciji je definirana statička varijabla count, čija se vrijednost inkrementirapri svakom pozivu funkcije. Ako vrijednost postane jednaka argumentu mod, count sepostavlja 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. staticint count=0;), ta inicijalna vrijednost vrijedi samo pri prvom pozivu funkcije. Inicijalizacija nije ista kod lokalnih statičkih i lokalnih automatskih varijabli. Vrijednost lokalnih automatskih varijabli se inicijalizira na vrijednost opisanu inicijalizacijom pri svakom pozivu funkcije, a vrijednost lokalnih statičkih varijabli se inicijalizira na vrijednost opisanu inicijalizacijom samo pri prvom pozivu funkcije.9.1.3 Globalne varijable Globalne varijable su varijable koje se deklariraju izvan tijela funkcije. To su"permanentne" varijable koje trajno zauzimaju memoriju za vrijeme trajanja programa. One seuvijek inicijaliziraju na vrijednost 0. Doseg globalnih varijabli je od točke definiranja do krajadatoteke u kojoj su definirane. Kasnije će biti pokazano kako se njihov doseg može proširiti i nadruge 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 drugihkompilacijskih 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 prvojdatoteci, imena "counter.c", definirana je globalna varijabla count i funkcija incrCounter(). Udrugoj datoteci, imena "countmain.c", definirana je funkcija main() koji koristi vrijednost odcount 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.cU komandnoj liniji se navode imena obje izvorne datoteke. Konačni izvršni program će imatiime datoteke koja je prva zadana: countmod.exe. Rezultat izvršenja programa biti će isti kao iu prethodnom primjeru.Statičke globalne varijable Globalne varijable se također mogu deklarirati s prefiksom static. Takove varijable senazivaju 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 idejamodularnog programiranja i princip "skrivanja podataka" (eng. data hidding).9.1.4 Moduli Modul sadrži skup međuovisnih globalnih varijabli i funkcija zapisanih u jednoj ili višedatoteka. 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, adobiveni objektni kod se može pohraniti u biblioteku potprograma. Neposrednu korist odovakvog 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 bitiobrađ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 stanjebrojača. Stanje brojača se očitava pomoću funkcija getCount() i getModulo(). Nijepredviđeno da korisnik pristupa globalnim varijablama.2. Implementacija modula je opisana u datoteci "counter.c". U toj datoteci su definirane dvijestatičke globalne varijable _count i _mod. Prefiks static znači da su one vidljive samo uovoj datoteci. Početno je vrijednost _mod postavljena na maksimalnu moguću cjelobrojnuvrijednost. Zatim slijede definicije funkcija koje su određene specifikacijom. /* Datoteka: counter.c * Implementacija funkcija brojača po modulu: mod */ #include <limits.h> /* zbog definicija INT_MAX*/ /* globalne varijable */ static int _count = 0; /* početno stanje brojača */ static int _mod = INT_MAX; /*2147483647*/ void resetCounter(int mod) { _count= 0; if(mod <= 1) _mod = INT_MAX; else _mod = mod; } int getCount(void) { return _count;} int getModulo(void){ return _mod; } int incrCounter(void) { _count++; if(_count >= _mod) _count = 0; return _count; }3. Testiranje modula se vrši programom "testcount.c": /* Datoteka: testcount.c */ #include <stdio.h> #include "counter.h" int main(void) { int i; resetCounter(5); 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đuglavnog programa i modula je opisana u deklaracijama specifikacijske datoteke "counter.h", paza primjenu modula nije potrebno znati kako je implementiran, već samo kako se koristenjegove 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, panisu 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 jerse njime postiže neovisnost podataka iz različitih modula. Na ovaj je način lakše provoditiprojekte u kojima sudjeluje više programera.9.1.5 Formiranje programskih bibliotekaPrethodni program se može kompilirati na sljedeći način: c:>cl testcounter.c counter.cKada 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, bezstvaranja 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.objDobra strana ovakovog pristupa je da se ubrzava proces stvaranja programa, jer ne treba uvijekiznova kompilirati datoteku "counter.c".Više objektnih datoteka se može združiti u programske biblioteke. Uobičajeno, programskedatoteke imaju ekstenziju .lib (ili .a na Unixu). Pokazat ćemo kako se formira bibliotekapotprograma 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.libOč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 problemaodređuju se radnje i algoritmi kojima se može taj zadatak obaviti. U proceduralnim je jezicimanajbolji način programske realizacije neke radnje da se ona specificira i implementira pomoćuneke funkcije. Radnje, odnosno zadaci, koje treba obaviti neka funkcija, također se mogu realiziratipomoću niza funkcija. U tom slučaju kažemo da se razvoj programa vrši funkcionalnomdekompozicijom "od vrha prema dolje". Praksa je pokazala da je funkcionalna dekompozicijaprihvatljiva kao metoda programiranja u većini slučajeva. Kao studijski primjer razvoja programa funkcionalnom dekompozicijom izradit ćemoprogram pomoću kojeg se igra popularna igra "točkice i kružići" (ili tic-tac-toe).Tic-Tac-ToeTic-Tac-Toe je igra u kojoj se nadmeću dva igrača. Igrači, jedan za drugim označavaju polja u 9kvadratića, prvi s križićem, a drugi s točkicom. Pobjednik je onaj koji redno, stupčano ilidijagonalno prvi ispuni 3 polja. Prvi igrač unosi križić, a drugi točkicu. Primjer igre u kojoj je pobijedio drugi Označavanje pozicije kvadratića na igrač, tj. onaj koji unosi točkicu. igračkoj ploči | o | X | o | | 1 | 2 | 3 | |---|---|---| |---|---|---| | X | o | X | | 4 | 5 | 6 | |---|---|---| |---|---|---| | o | | X | | 7 | 8 | 9 |Kako napisati program u kojem će jedan od igrača biti računalo, a drugi igrač je čovjek.Problem se može formulirati na sljedeći način: 1. Nacrtaj igraču ploču, i upute za igru. 2. Postavi početno stanje (ploča je prazna). 3. Ispitaj da li prvi potez vuče računalo ili čovjek. 4. Prati igru sve dok se ne ostvari pobjednička kombinacija, ili dok se ne ispune svi kvadratići. 5. Ako korisnik želi ponoviti igru vrati se na korak 2, inače završi program. Problem je postavljen sasvim općenito. Sada treba izvršiti razradu problema, tako da sedetaljnije opišu pojedini koraci u opisanom apstraktnom algoritmu. Pored postupaka koje trebaobaviti, 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ć itoč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 ovihvarijabli, primjerice covjek, ima vrijednost X, to znači da prvi potez vuče čovjek, inače, prvipotez vuče računalo. Na temelju početnog algoritma može se zaključiti da se problem može realizirati pomoću 5neovisnih 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ženapisati 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 odovih problema se mogu odmah riješiti, primjerice funkcije Upute() i OcistiPlocu() semogu implementirati na sljedeći način:void Upute(){ printf("nIgra - tic tac toe - krizici i tockicenn"); printf("t | 1 | 2 | 3 | n"); printf("t |---|---|---| n"); printf("t | 4 | 5 | 6 | n"); printf("t |---|---|---| n"); printf("t | 7 | 8 | 9 | nn"); 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.nn");}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 definiranjefunkcije IgrajIgru() u kojoj se obavlja više operacija. Prije definiranja te funkcije izvršit ćese definiranje funkcija PostaviPrvogIgraca() i PonoviIgru() jer je njihovaimplementacija 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 potezenumeriramo 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 parnavrijednost 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 rednibroj 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!!!nn"); break; } else if (PostajeDobitnik(covjek)) { printf("nCestitam, pobijedio si!nn"); break; } potez++; } if (potez > 9) printf("nOvaj put nema pobjednika.nn"); }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 9.1. 127
  • 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-Toe9.3 ZaključakProgrami 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 jemanja od ukupnog programa, prevodi mnogo brže.U modulu se može definirati određeni skup funkcija koji se može koristiti i u drugimprogramima.Module, koji sadrže često korištene funkcije, može se u obliku strojnog koda uvrstiti ubiblioteke potprograma.Modul se može pisati, testirati, i ispravljati neovisno od ostatka programa. Proces ispravljanjaje pojednostavljen, jer se analizira manji dio programa.Moduli omogućavaju veću preglednost i logičku smislenost programskog koda, jer se u njimaobično obrađuje jedinstvena problematika. Primjerice, za obradu matematičkih problemapostoje 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 podatakakoji bilježe stanja objekta koji modul opisuje, dobivaju se moduli neovisni od programa u kojemse koriste. To znatno olakšava timski rad u razvoju softvera. 128
  • 129. 10 Rad s pokazivačimaNaglasci: • 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 ilifunkcije. Njihova primjena omogućuje napredne programske tehnike: dinamičko alociranjememorije, apstraktni tip podataka i polimorfizam funkcija. Da bi se moglo shvatiti ove tehnikeprogramiranja, u ovom će poglavlju biti pokazano kako se vrše temeljne operacije spokazivačima, a u sljedećim će poglavljima biti pokazana njihova primjena u programiranju.10.1 Tip pokazivačaPokazivačkim varijablama se deklaracijom pridjeljuje tip. Deklariranje pokazivačke varijable sevrš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 adresuobjekata 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 ostvarujetzv. adresnim operatorom &: p = &sum; /* p iniciran na adresu varijable sum */ q = &arr[2]; /* q iniciran na adresu trećeg elementa niza arr*/ Adresa Vrijednost Identifikator 0x09AC -456 sum 0x09B0 ...... ...... ...... ...... ...... 0x0F10 ...... arr[0] 0x0F14 arr[1] 0x0F18 arr[2] ...... ...... 0x1000 0x09AC p 0x1004 0x0F18 q Slika 10.1 Adresa i vrijednost varijabli 129
  • 130. 10.2 Operacije s pokazivačimaTemeljne 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 danaredba 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 & prefiksoperatora: 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 pripadnogtipa, 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 prenositivarijable u funkciju. To se postiže na način da se kao parametar funkcije deklarira pokazivač naneki objekt, primjerice: void Increment(int *pVar); Prema pravilu C jezika, pri pozivu funkcije se prenosi vrijednost stvarnog argumenta, a ufunkciji se parametar funkcije tretira kao lokalna varijabla koja ima vrijednost stvarnogargumenta. U slučaju kada je parametar funkcije pokazivač, stvarni argument funkcije je adresaobjekta koji ima isti tip kao pokazivač. Korištenjem indirekcije pokazivača može se pristupititom objektu i mijenjati njegov sadržaj, dakle taj se objekt može tretirati kao varijabla koju sekoristi i u funkciji. Ovaj način prenošenja parametara funkcije je prikazan u programu ptr-parm.c. U njemuse pomoću funkcije void Increment(int *pVar){(*pVAr)++;} može inkrementirativrijednost bilo koje cjelobrojne varijable imena var. To se vrši s pozivom funkcije u oblikuIncrement(&var);. Uočite da se u definiciji funkcije koristi pokazivački parametar, a pripozivu 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 = %dn", var); return 0; }Ispis je: var = 8 U nekim knjigama se ovaj način prijenosa argumenata naziva "prijenos reference" (call byreference), 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 onpokazuje 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 vrijednostdvije 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=%dn", a, b); swap(&a , &b); printf("Nakon poziva swap(&a, &b)n"); printf("a=%d b=%dn", a, b); }Ispis je: a=1 b=2Nakon poziva swap(&a, &b) a=2 b=110.4 Pokazivači i nizovi Potrebno je najprije navesti nekoliko pravila koje vrijede za reference niza i pokazivačekoji 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 naisti način kao i sa prostim varijablama.2. Ime niza, zapisano bez oznake indeksa je "pokazivačka konstanta - adresa" koja pokazuje naprvi 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činkao š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 inicijaliziranpokazivač p: p = array;tada vrijedi: p[0] ⇔ *p ⇔ array[0] p[1] ⇔ *(p + 1) ⇔ array[1] p[2] ⇔ *(p + 2) ⇔ array[2] ... p[n] ⇔ *(p + n) ⇔ array[n] Jedina razlika u korištenju reference niza i pokazivača na taj niz je u tome da se memorijskim referencama ne može mijenjati vrijednost adrese koju oni označavaju. a++; je nedozvoljen izraz, jer se ne može mijenjati konstanta 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++) for (i = 0; i < 10; i++) printf("%dn", *p); printf("%dn", a[i]); U prvoj petlji se koristi pokazivač p za pristup elementima niza a[]. Početno on pokazuje na prvi element niza a[]. U petlji se zatim ispisuje vrijednost tog elementa (dereferencirani pokazivač *p), i inkrementira vrijednost pokazivača, tako da se u narednom prolazu petlje s njime referira sljedeći element. Kompilator ne prevodi ove petlje na isti način, iako je učinak u oba slučaja isti: bit će ispisana vrijednost 10 elemenata niza a[]. Ako je učinak isti, možemo se dalje upitati koja će se od ovih petlji brže izvršavati. To ovisi o kvaliteti kompilatora i o vrsti procesora. Kod starijih procesora brže se izvršava verzija s pokazivačem, jer se u njoj u jednom prolazu petlje vrše dva zbrajanja, dok se u drugom slučaju mora (skriveno) izvršiti i jedno množenje. Ono je potrebno da bi se odredila adresa elementa a[i], jer je adresa(a[i])= adresa(a[0]) + i*sizeof(int). Kod novijih se procesora operacija indeksiranja izvršava veoma brzo, pa se u tom slučaju preporučuje korištenje verzije s indeksnim operatorom. 10.5 Pokazivači i argumenti funkcije tipa niza Pri pozivu funkcije, stvarni se argument kopira u formalni argument (parametar) funkcije. U slučaju da je argument funkcije ime niza kopira se adresa prvog elementa, dakle stvarni argument koji se prenosi u funkciju je vrijednost pokazivača na prvi element niza. Stoga se prijenos niza u funkciju može deklarirati i pomoću pokazivača. Primjer: Sljedeće tri funkcije imaju isti učinak i mogu se pozvati s istim argumentima:void print(int x[], void print(int *x, void print(int *x, int N ) int N) int N){ { {int i; while (N--) { int i;for (i = 0; i < N; i++) for (i=0; i<N;i++) printf("%dn", x[i]); printf("%dn", *x); printf("%dn", x[i]);} x++; } } } /* 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 nemijenja se njihova vrijednost. U takovim slučajevima je preporučljivo da se parametri funkcijedeklariraju 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 naredbakojom se mijenja sadržaja elemenata niza, a programeru služi kao dodatno osiguranje da ćeizvrš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 minimalnavrijednost niza. Testirat ćemo je programom u kojem korisnik unosi 5 brojeva, a programispisuje 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 = %lfn", min, max); return 0; } 135
  • 136. 10.6 Patrametri funkcije tipa void pokazivačaAko 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 kojegmemorijskog objekta, ali se ne može vršiti pristup memorijskim objektima pomoću operatoraindirekcije, jer on pokazuja na ništa. Većina današnjih kompilatora ne dozvoljava aritmetičkeoperacije s void pokazivačima. Očito je da nema smisla koristiti void pokazivače kao regularne varijable. Oni pak mogubiti korisni kao parametri funkcija. Kada se void pokazivač koristi kao parametar funkcije tadase pri pozivu funkcije tom pokazivaču može pridijeliti adresa bilo kojeg memorijskog objekta, aunutar same funkcije se može s prefiksom (tip *) vršiti forsirana pretvorba pokazivačkogtipa.Primjer: Definirana je funkcija void UnesiVrijednost(void *p, int tip), pomoćukoje 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 %dn" , ival); UnesiVrijednost(&dval, DOUBLE); printf("Vrijednost je %lgn" , dval); return 0; } Uočite kako je korištena funkcija scanf(). Ispred imena argumenta nije korišten adresnioperator 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 pozovukompatibilni argumenti, može doći do nepredvidivih rezultata, čak i do blokade računala.10.7 Pokazivači na funkcije Funkcije su također memorijski objekti pa se može deklarirati i inicijalizirati pokazivače nafunkcije. 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čamož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 indirekcijupokazivač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čkihfunkcija 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); /* ispisuje se vrijednost sinusne funkcije za vrijednost argumenta 3.1*/ Print(cos, 1.7) /* ispisuje se vrijednost kosinus funkcije za vrijednost argumenta 1.7*/Primjer: U programu pfunc.c koriste se pokazivači na funkciju za ispis vrijednostistandardnih i korisnički definiranih matematičkih funkcija. Izbor funkcije i argumenta funkcijevrš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 %lfn", 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 3n"); 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 tipadouble i koja vraća vrijednost tipa double. Također je izvršena i inicijalizacija elemenata nizana 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žeadrese funkcija sin(), cos() i korisnički definirane funkcije Kvadrat(). Zatim se odkorisnika traži da unese broj i da odabere funkciju. Na kraju se ispisuje rezultat primjenefunkcije 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 3n"); scanf("%d" ,&izbor); if (izbor >=1 && izbor <=3) printf( "nRezultat je %lfn", (*pF[izbor-1])(val)); return 0; }10.8 Kompleksnost deklaracijaOčito je da se u C jeziku koriste vrlo kompleksne deklaracije. One na prvi pogled ne otkrivaju okakovim 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)[]; pokazivač na niz objekata tipa T T x(); funkcija koja vraća objekt tipa T T *x() funkcija koja vraća pokazivač na objekt tipa T T (*x()); funkcija koja vraća pokazivač na objekt tipa T T (*x)(); pokazivač na funkciju koja vraća objekt tipa T T (*x[n])(); niz od n pokazivača na funkciju koja vraća objekt tipa TDalje ć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 straneovog izraza (double) znači da je x niz pokazivača na funkciju koja prima argument tipadouble i koja vraća tip T.Znatno jednostavniji i razumljiviji način zapisa kompleksnih deklaracija postiže se korištenjemsinonima 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 tipadouble.Dalje se može definirati tip t_pFdd koji je pokazivač na funkciju koja vraća double i primaargument 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 %lfn", 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 3n"); scanf("%d" ,&izbor); if (izbor >=1 && izbor <=3) printf( "nRezultat je %lfn", (*pF[izbor-1])(val)); return 0; }10.9 Polimorfne funkcije Funkcije koje se mogu prilagoditi različitim tipovima argumenata nazivaju se polimorfnefunkcije. Polimorfne funkcije se u C jeziku realiziraju pomoću parametara koji imaju tip voidpokazivača i pokazivača na funkcije. Dvije takove funkcije implementirane su u standardnojbiblioteci 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čineelsize bajta. Elementi se sortiraju od manje prema većoj vrijednosti, odnosno prema kriterijuusporedbe 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 vrijednostnula ako su ta dva elementa jednaka, pozitivnu vrijednost ako je prvi element veći od drugoga, anegativnu 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 sevrši tako da se najprije izvrši pretvorba void pokazivača u int*, a zatim se primijeniindirekcija 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šisortiranje 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 sortiranjan"); 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: 3 1 13 2 17 Nakon sortiranja 1 2 3 13 17Zadatak: Napišite program sortf.c u kojem se vrši sortiranje niza realnih brojeva. Koristiteqsort() 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 postojielement zadane vrijednosti. Funkcija vraća pokazivač na traženi element, ako postoji , ili NULLako u tom nizu ne postoji zadana vrijednost. Koristi se deklaracija funkcije slična funkcijiqsort(), jedino se još dodaje argument tipa void pokazivača na varijablu koja sadrži vrijednostkoju 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 %Fpn", *pEl, pEl); printf("n"); return 0; }Nakon izvršenja dobije se ispis: 3 1 13 2 17 7 0 11 Element vrijednosti 2, na adresi 0022FF4C U standardnoj biblioteci je implementirana funkcija bsearch() koja ima istu deklaracijukao funkcija search(). Razlika ove dvije funkcije je u tome što je bsearch() funkcijaspecijalizirana i optimirana za slučaj da se traženje elementa niza provodi na prethodnosortiranom nizu. Kasnije ćemo pokazati kako su realizirane funkcije bsearch() i qsort().10.10 Zaključak Pokazivači zauzimaju središnje mjesto u oblikovanju C programa. Pokazivač je varijablakoja 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 operatorindirekcije(*). Adresni operator napisan ispred imena varijable vraća u izraz adresu varijable, aoperator indirekcije *, postavljen ispred imena pokazivača, referira sadržaj varijable na kojupokazivač pokazuje. Pokazivači i nizovi su u specijalnom odnosu. Ime niza, napisano bez uglatih zagradapredstavlja pokazivačku konstanu koja pokazuje na prvi element niza. Indeksna notacija ima ekvivalentni oblik pokazivačke notacije. Uglate zagrade imajukarakter indeksnog operatora, jer kada se koriste iza imena pokazivača, uzrokuju da se timpokazivačem može operirati kao s nizom. Nizovi se prenose u funkcije na način da se u funkciju prenosi pokazivač na prvi elementniza, 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čkojnotaciji. Deklaracija niza kao parametra funkcije može se izvršiti u indeksnoj ili pokazivačkojnotaciji. Preporučuje se upotreba pokazivačke notacije jer sa tada poziv funkcije može vršiti sastatičkim nizovima i pokazivačima koji pokazuju na neki niz. Funkcija ne raspolaže s podatkom o broju elemenata niza. Zadatak je programera da tuvrijednost, ukoliko je potrebna, predvidi kao parametar funkcije. Korištenjem void pokazivača i pokazivača na funkcije mogu se realizirati polimorfnefunkcije. Ime funkcije je konstantni pokazivač na funkciju. 145
  • 146. 11 Nizovi znakova - stringNaglasci: • 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 sunizovi znakova koji imaju karakteristike stringa.11.1 Definicija stringa String je naziv za memorijski objekt koji sadrži niz znakova, a posljednji znak u nizu morabiti 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 nizomuvijek vodi računa o tome da posljednji element niza mora biti jednak nuli. Zbog ove sekarakteristike stringovi u C jeziku nazivaju ASCIIZ stringovi. Sastoje od niza ASCII znakova inule (eng. Z - zero). Duljina stringa je cjelobrojna vrijednost koja je jednaka broju znakova u stringu (bez nultogznaka). 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 duljinaje 13, jer je indeks nultog znaka jednak 13. 0 1 2 3 4 5 6 7 8 9 10 11 12 13 H e l l o , W o r l d ! 0Pogledajmo 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("%sn", 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 znakovnogniza 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 adresapočetnog elementa stringa. Funkcija vraća vrijednost duljine stringa (size_t je sinonim zaunsigned).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 inicijalnimliteralnim 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 imakarakteristike stringa. Stoga, i sljedeći iskaz predstavlja deklaraciju stringa: char *digits ="0123456789ABCDEF"; /* kompilator inicijalizira pokazivač */ /* na adresu od "0123456789ABCDEF"; */ Ovakva inicijalizacija je moguća jer kompilator interno literalni string tretira kao referencu,pa se njegova adresa pridjeljuje pokazivaču digits. Dozvoljeno je čak literalnu stringkonstantu koristiti kao referencu niza, primjerice printf("%c", "0123456789ABCDEF"[n]);ispisuje n-ti znak stringa "0123456789ABCDEF".11.2 Standardne funkcije za rad sa stringovima U standardnoj biblioteci postoji niz funkcija za manipuliranje sa stringovima. One sudeklarirane 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 tima 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 ustringu 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 ustringu 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 nijeprisutan 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 nekimsistemskim operacijama. Argument je obično globalna varijabla errno, čiju vrijednost takođerpostavlja kompilator pri sistemskim operacijama. char *strtok(char *s, const char *sep) vrši razlaganje stringa s na niz leksema koji su razdvojeni znakovima-separatorima. Skupznakova-separatora se zadaje u stringu sep. Funkcija vraća pokazivač na leksem ili NULL akonema leksema.Korištenje funkcije strtok() je specifično jer u stringu može biti više leksema, a ona vraćapokazivač na jedan leksem. Da bi se dobili sljedeći leksemi treba ponovo zvati istu funkciju, alis 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 trileksema (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 kopiranjestringa (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 s1leksički manji od s2, ili pozitivnu vrijednost ako je s1 leksički veći od s2. Leksička usporedbase 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 dostignutkraj niza (znak 0). Kad petlja završi, ako su oba znaka jednaka, to znači da su i svi prethodniznakovi jednaki. U tom slučaju funkcija vraća vrijednost 0. U suprotnom funkcija vraća 1 ili –1ovisno 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.11.3 Ulazno-izlazne operacije sa stringovima Najjednostavniji način dobave i ispisa stringa je da se koriste printf() i scanf()funkcije s oznakom formata %s. Primjerice, s char line[100]; scanf("%s",str); printf("%s", str);najprije se vrši unos stringa (korisnik otkuca niz znakova i <enter>). Zatim se vrši ispis togstringa pomoću printf() funkcije.Funkcija scanf() nije pogodna za unos stringova u kojima ima bijelih znakova, jer i oni značekraj 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 ulazun) i sprema je u string str, kojeg zaključuje s nul znakom. Podrazumijeva se da je prethodnorezervirano dovoljno memorije za smještaj stringa str. Funkcija vraća pokazivač na taj stringili 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 utome 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 ispisujeukupno uneseni tekst./* str-io.c */#include <stdio.h>#include <ctype.h>#include <string.h>#define MAXCH 2000int 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 kojeunosi korisnik programa u petlji: while (gets(str) != NULL )U tijelu petlje prvo se ispituje sadržaj unesenog stringa. Ako je unesen string "kraj", petlja seprekida. 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 sebilo 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 kolikoukupno ima riječi u stringu tekst s više od tri slova. U tu svrhu koristite funkciju strtok() istrlen().11.4 Korisnički definirane ulazne operacije sa stringovima Veliki nedostatak funkcija scanf() i gets() je u tome da se ne može ograničiti brojunesenih znakova, pa treba koristiti stringove za koje je rezerviran veliki broj bajta. Zbog ovogogranič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 maksimalnodozvoljeni broj znakova koji će biti spremljen u string. Funkcija vraća broj znakova ili 0 ako jesamo 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 linijateksta, 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 ustandardnoj 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 sekoriste za zapis numeričkih literala. U slučaju greške ove funkcije vraćaju nulu. Greška seuvijek 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 vrijednostipomoć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 %dn", i); puts("Otipkajte realni broj"); getstring(str, NCHARS); d = atof(str); printf("Otipkali ste %lgn", 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() primaznakove iz stringa str, dok funkcija scanf() prima znakove sa standardnog ulaza.Primjer: pretvorba stringa, koji sadrži znakovni zapis cijelog broja, u numeričku vrijednostcjelobrojne 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 semož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 standardniizlaz.11.6 Nizovi stringova Nizovi stringova se jednostavno deklariraju i inicijaliziraju kao nizovi pokazivača nachar. 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 seišč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 karataProgram karte.c služit će da se po slučajnom uzorku izmiješaju igrače karte. Za miješanjekarate koristi se proizvoljna metoda:Postoje 52 karte. Njih se registrira u nizu cijelih brojeva int karte[52]. Svaka karta[i]sadrži vrijednost iz intervala 0..51 koja označava kombinaciju boje i lika karte, prema pravilu : oznaka boje: boja[karte[i] / 13]; /* string boja[0 do 3] */ oznaka lika: lik [karte[i] % 13]; /* string lik[0 do 12] */jer ima 13 likova i 4 boje. To znači da ako se početni raspored inicijalizira s for (i = 0; i < BROJKARATA; i++) karte[i] = i;karte će biti složene po bojama, od asa prema kralju (prva će biti as-srce a posljednja kralj-pik).Za miješanje karata koristit će se standardna funkcija int rand();koja je deklarirana u <stdlib.h>. Ova funkcija pri svakom pozivu vraća cijeli broj izintervala 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 sedobije 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 - %sn", boja[karte[i]/13], lik[karte[i]%13]); return 0; }Dobije se ispis: Pik - 3 Srce - 9 Tref - 10 Srce - 10 Tref - 5 Tref - 8 Tref - Kralj . . . . . Pik - 7 Srce - 3 Tref - Dama Tref - Jack Tref - 6 Karo - 8 156
  • 157. 11.7 Generator slučajnih brojeva Znanstvenici su pokazali da se može realizirati niz slučajnih cijelih brojeva pomoću tzv.linearne kongruencijske sekvence; xi = (a * xi-1 + b) % cu kojoj se vrijednost i-tog broja dobije iz prethodno izračunate vrijednosti (xi-1) i tri konsatantea,b,c. Vrijednost ovih konstanti se određuje iz uvjeta da brojevi iz ove sekvence imaju jednakuvjerojatnost pojavljivanja, odnosno da imaju uniformnu razdiobu gustoće vjerojatnosti. UANSI/ISO standardnoj biblioteci implementiran je generator s konstantama a=1103515245,b=12345 i c= 232, pomoću jedne globalne varijable seed i dvije funkcije rand() isrand(). static unsigned int seed = 1; int rand(void) { /* vraća pseudo-slučajni broj iz intervala 0..32767 */ seed = seed * 1103515245 + 12345; return seed >> 16; } void srand(unsigned int start) { /* postavlja početnu vrijednost za rand() */ seed = start; }Slučajni broj generira funkcija rand() iskazom: seed = seed * 1103515245 + 12345;Uočite da se ne vrši operacija mod 232, jer maksimalna veličina unsigned long je 232-1, panije potrebno tražiti ostatak dijeljenja s tim brojem. Funkcija rand() vraća vrijednost koja jesadrž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 zagenerator slučajnih brojeva, ali ovaj način je odabran kao dobar kompromis između kvalitete ibrzine generiranja slučajnih brojeva. Početno, pri pokretanju programa, globalna varijabla seed ima vrijednost 1, a bilo bipoželjno da to bude uvijek druga vrijednost. U tu svrhu se koristi funkcija srand(), pomoćukoje 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čijuvrijednost. Programeri obično koriste činjenicu da će startanje programa biti u nekomjedinstvenom vremenu. Za očitanje vremena na raspolaganju je funkcija time() koja jedeklarirana u <time.h>: time_t time(time_t *tp)Tip time_t je typedef za cjelobrojnu vrijednost . U većini implementacija funkcija time()vraća vrijednost koja je jednaka vremenu u sekundama koje je proteklo od 01.10.1970. godine.Ista se vrijednost postavlja i u dereferencirani pokazivač tp, ukoliko on nije NULL.Sada se inicijalizacija generatora slučajnih brojeva može provesti sljedećim iskazom: 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 svakomizvršenju programa dobije drugačija podjela karata.11.8 Argumenti komandne linije operativnog sustava Funkcija main() također može imati argumente. Njoj argumente prosljeđuje operativnisustav s komandne linije. Primjerice, pri pozivu kompilatora (c:>cl karte.c ) iz komandne linijese kao argument prosljeđuje string koji sadrži ime datoteke koja se kompilira. Opći oblikdeklaracije 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: %sn", argv[0]); if (argc > 1) for (i = 1; i < argc; i++) printf("%d. argument: %sn", i, argv[i]); return 0; }Ako se otkuca: c:> cmdline Hello Worlddobije su ispis: Ime programa: C:CMDLINE.EXE 1. argument: Hello 2. argument: Worldjer je: argc = 3 argv[0] = "C:CMDLINE.EXE" argv[1] = "Hello" argv[2] = "World"Primjer: Program cmdsum.c računa sumu dva realna broja koji se unose u komandnoj liniji izaimena izvršnog programa. Primjerice, ako se u komandnoj liniji otkuca: 158
  • 159. C:>cmdsum 6.5 4e-2program treba dati ispis Suma: 6.500000 + 0.040000 = 6.540000 /* Datoteka: cmdsum.c */ #include <stdio.h> #include <stdlib.h> int main( int argc, char *argv[]) { float x,y; if (argc > 2) /* unose se dva argumenta */ { x = atof(argv[1]); y = atof(argv[2]); printf ("Suma: %f + %f = %fn", x, y, x+y); } else printf("Otkucaj: cmdsum broj1 broj2 n"); return 0; } 159
  • 160. 12 Dinamičko alociranje memorijeNaglasci: • 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 programudodijeli dio radne memorije koji čine tri cjeline: memorija izvršnog kôda programa, memorijaza statičke podatke (globalne i statičke varijable te literalne konstante), te memorija nazvanastog (eng. stack) koja se koristi za privremeni smještaj automatskih varijabli (lokalne varijable iformalni argumenti funkcija). Ostatak memorije se naziva slobodna memorija ili "heap". Operativni Korisnički Statički Stog Slobodna memorija program: sustav podaci automatske varijable "heap" strojni kod 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 nijeimplementirana mogućnost direktnog raspolaganja slobodnom memorijom, ali se, uz pomoćpokazivača i funkcija biblioteke: malloc(), calloc(), realloc() i free(), može naindirektan način koristiti slobodna memorija. Postupak kojim se dobiva na upotrebu slobodnamemorija naziva se dinamičko alociranje memorije. Pojam alociranje memorije asocira načinjenicu da se dio slobodne memorije dodjeljuje korisničkom programu, dakle apstraktno semijenja njegova lokacija. Alociranje memorije se vrši tijekom izvršenja programa, pa se kaže daje taj proces dinamički. Postupak kojim se alocirana memorija vraća na raspolaganjeoperativnom sustavu naziva se oslobađanje ili dealociranje memorije. U ovom poglavlju biti će opisano kako se vrši dinamičko alociranje memorije i kako taprogramska tehnika omogućuje efikasno korištenje memorijskih resursa računala.12.1 Funkcije za dinamičko alociranje memorijeNajprije će biti opisane funkcije za dinamičko alociranje memorije. One su deklarirane udatoteci <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 funkcijavrać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 seadresa, 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 dacalloc() 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 memorijukoja je prethodno alocirana fukcijom malloc(), calloc() ili realloc(), ova funkcijadealocira (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 pridijeljenapokazivač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 alokatormože prilagoditi veličinu zahtijevanog području slobodne memorije veličini newsize. Ukolikose to ne može ostvariti funkcija realloc() alocira novo područje memorije pa u njega kopira izatim oslobađa dio memorije na koju pokazuje ptr. Ukoliko se ne može izvršiti alokacijamemorije 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 memorijeprovjeri da li ona vraća NULL. Ako se to dogodi u većini je slučajeva najbolje odmah prekinutiprogram. U tu svrhu može se koristiti standardna funkcija exit() kojom se prekida korisničkiprogram, a operativnom se sustavu prosljeđuje argument ove funkcije. Primjerice, p = malloc(n); if(p == NULL) { printf("out of memoryn"); exit(1); } /* … koristi pokazivač p */Napomena: Prije uvođenja ANSI/ISO standarda prototip za malloc() je kod mnogihkompilatora bio deklariran s: char *malloc(size_t n). Kod ovakvih kompilatorapotrebno 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 nekompatibilnostitipova.Primjer: Dinamičko alociranje niza. 161
  • 162. U programu allocarr.c alocira se niz od maxsize=5 cjelobrojnih elemenata. Korisnikzatim unosi niz brojeva. Ako broj unesenih brojeva premaši maxsize, tada se povećavaalocirana memorija za dodatnih 5 elemenata. Unos se prekida kada korisnk otkuca slovo. Nakraju 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 pretvaracijeli 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("%sn", 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 lokalnavarijabla, nakon izlaza iz funkcije taj pokazivač više ne postoji (zašto?). Alocirana memorija ćei dalje postojati ukoliko nije prije završetka funkcije eksplicitno dealocirana funkcijomfree(). Može se dogoditi da ta memorija bude "izgubljena" ako je se ne dealocira, ili ako se izfunkcije 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, ivrati 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 ubiblioteci Visual C i gcc kompilatora.12.2 Kako se vrši alociranje memorije Da bi se bolje shvatio proces alociranja memorije, bit će ilustrirano stanje slobodnjememorije u slučaju kada se vrši višestruko alociranje/dealociranje memorije. Sivom bojomoznač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 ćememorija 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 istakloda 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 str2Kada 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žedoći do višestruke fragmentacije, čime se "gubi" jedan dio slobodne memorije.Zapamtite: • O stanju slobodne memorije vodi računa posebni proces – memorijski alokator. • Nakon što se alocira memorija pomoću funkcije malloc(), treba je inicijalizirati. • Ako se želi inicijalizirati sadržaj memorije na vrijednost nula, koristi se funkcija calloc(). • Ne smije se pretpostaviti da se sukcesivnim pozivima funkcije malloc() dobiva kontinuirani memorijski blok.12.3 Alociranje višedimenzionalnih nizova Korištenje pokazivača i funkcija za dinamičko alociranja memorije omogućuje stvaranjedinamičkih struktura podataka koje mogu imati promjenljivu veličinu tijekom izvršenjaprograma. Nema posebnih pravila za stvaranje dinamičkih struktura podataka. U ovom i unarednim poglavljima bit će pokazani različiti primjeri koji su korisni u programiranju i koji semogu 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 nizovistringova i matrice promjenljivih dimenzija.Dinamički nizovi stringovaPrije je pokazano da se u C jeziku pokazivači tipa char * mogu tretirati kao stringovi, uz uvjetda pokazuju na niz znakova koji završava s nultim znakom. To omogućuje da se niz stringovadeklarira 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 Npokazivač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 utome što se sada veličina niza može mijenjati tijekom izvršenja programa. Početno su svielementi niza jednaki NULL, što znači da niz nije inicijaliziran. Inicijalizacija niza se postižetako da se pokazivačima txt[i] pridijeli adresa nekog stringa. Primjerice, nakon sljedećihoperacija: 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 daljeneinicijalizirani.Stanje u memoriji se može ilustrirati slikom 12.2. 165
  • 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žetako 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 bititemeljna struktura u editoru teksta. U ovu strukturu je lako unositi tekst , brisati tekst ipovećavati broj linija teksta.Primjer: U programu alloctxt.c korisnik unosi proizvoljan broj linija teksta. Početno sealocira memorija za 5 linija teksta. Ako korisnik unese više linija, tada se pomoću funkcijerealloc() povećava potrebna memorija za dodatnih 5 linija. Unos završava kada se otkucaprazna linija. Nakon toga se vrši sortiranje teksta pomoću funkcije qsort(). Na krajuprograma 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]. Prviindeks predstavlja redak, a drugi indeks predstavlja stupac matrice. Matrica se možeprogramski realizirati kao dinamička struktura. Primjerice, matrica imena mat, koja ćesadržavati elemente tipa double, se sljedećim postupkom: Slika 12.3 Prikaz korištenja memorije za dinamičke matrice1. Identifikator matrice se deklarira kao pokazivač na pokazivač na double, a dvije varijablekoje 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 stupacamatrice: 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 smatricom. 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žetako 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 smatricama čije se dimenzije mogu mijenjati tijekom izvršenja programa. Također, pogodno jepisati 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 nijemoguće kod korištenja statičkih nizova je se tada u definiciji funkcije, u parametru kojioznač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čajnimbrojem i ispisuje vrijednost elemenata matrice. Zatim se ta matrica dealocira i ponovo alocira s2x2 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: 41.00 18467.00 6334.00 26500.00 19169.00 15724.00 11478.00 29358.00 26962.00 24464.00 5705.00 28145.00 23281.00 16827.00 9961.00 491.0012.4 Standardne funkcije za brzi pristup memorijiU standardnoj biblioteci je definirano nekoliko funkcija koje su optimirane za brzi pristupmemoriji. 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 nijeprisutan 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 strukturepodatakaNaglasci: • korisnički definirane strukture podataka • članovi strukture • prijenos struktura u funkcije • strukture i funkcije za očitanje vremena • unija podataka • bit polja • pobrojani tipoviU 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 konstanti13.1 Struktura (struct) Struktura je skup od jedne ili više varijabli, koje mogu biti različitog tipa, grupiranihzajedno 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 tippodataka - struktura - pomoću koje se mogu deklarirati varijable odnosno memorijski objektistrukturnog 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_članaPrimjerice, zapisom struct _tocka { int x; int y; };deklarirana je struktura imena _tocka, koja ima dva člana: x i y. Pomoću ove strukture zgodnoje 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 seiskazom struct _tocka T1, T2;a vrijednost koordinata se postavlja u sljedećoj sekvenci: T1.x = 2; T1.y = 1; T2.x = 6; T2.y = 4; Slika13. 1. Označavanje koordinata točke i pravokutnikaUkoliko 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 razinaapstrakcije može povećati na način da se deklarira struktura _pravokutnik koja ima dva članakoji 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 oznakastrukture, 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 deklarirativarijable (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 sepripadajuć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 navrijednost 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 sedefinirati niz: studentinfo_t student[30];Pojedinom članu strukture, koji je element niza, pristupa se na način da se točka operator pišeiza 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žepridijeliti vrijednost jedne strukturne varijable drugoj, da se vrijednost strukturne varijable možeprenositi 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: %dn", 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 ćekasnije služiti kao oznaka elementa niza, do kojeg su uneseni potpuni podaci. Ispis se vršipomoću funkcije display(studentinfo_t). U prethodnom programu strukturna varijabla se prenosi u funkciju po vrijednosti. Ovajnačin prijenosa strukture u funkciju nije preporučljiv jer se za prijenos po vrijednosti na stogmora kopirati cijeli sadržaj strukture. To zahtijeva veliku količinu memorije i procesorskogvremena (u ovom slučaju veličina strukture je 34 bajta). Mnogo bolji način prijenosa strukture ufunkciju je da se koristi pokazivač na strukturu, a da se zatim u funkciji elementima strukturepristupa 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 uzagradama, jer bi inače točka operator imao prioritet. Ovakav način označavanja indirekcije jedosta kompliciran, stoga je u C jeziku definiran operator ->, pomoću kojeg se prethodni iskazizapisuju 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 napisatimnogo 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: %dn", pS->ocjena ); } 177
  • 178. Prvo što se treba uočiti u ovom programu je način kako je deklarirana struktura _student . Unjoj sada nije rezerviran fiksni broj mjesta za član ime, već ime sada predstavlja pokazivač nachar (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 kolikoje upisano inicijalizacijom (plus nula!). Pomoćnim pokazivačem p, kojeg se početno inicira naadresu 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 nizapomoću funkcije display(), kojoj se kao argument prenosi pokazivač na tipstudentinfo_t. Unutar funkcije display() elementima strukture se pristupa indirekcijompokazivača strukture (->). Na kraju, da se zaključiti, da prijenos strukture u funkciju u pravilu treba vršiti pomoćupokazivača. Isto vrijedi za slučaj kada funkcija vraća vrijednost strukture. I u tom slučaju jebolje 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 imamalo 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 seocjenjuje da će dati najbrže izvršenje programa (većina procesora brže pristupa parnimadresama, nego neparnim adresama). karakteristika niz struktura strukturalni sadržaj kolekcija elemenata istog tipa kolekcija imenovanih članova koji mogu biti različitog tipa pristup elementima elementima se pristupa članovima se pristupa pomoću imena složenog tipa pomoću indeksa niza raspored elemenata u u nizu jedan do drugog u nizu, ali ne nužno jedan do drugog memoriji računala Tablica 13.1. Usporedba nizova i struktura Kod kompilatora Microsoft Visual C može se posebnom direktivom (#pragma pack(1))zadati da se članovi strukture "pakiraju" u memoriju jedan do drugog. To pokazuje programpack.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 bajtan", sizeof(char)+sizeof(short)+ sizeof(double)); printf("Zauzece memorije nepakiranom strukturom: %d bajtan", sizeof(struct s1)); printf("Zauzece memorije pakiranom strukturom: %d bajtan", sizeof(struct s2)); printf("nAdresa elementa : nepakirano pakiranon"); printf("Adresa od c : %p %pn", &v1.c, &v2.c); printf("Adresa od i : %p %pn", &v1.i, &v2.i); printf("Adresa od d : %p %pn", &v1.d, &v2.d); return 0; }Nakon izvršenja programa dobije se poruka: Minimalno zauzece memorije: 11 bajta Zauzece memorije nepakiranom strukturom: 16 bajta Zauzece memorije pakiranom strukturom: 11 bajta Adresa elementa : nepakirano pakirano Adresa od c : 00406B90 00406BA0 Adresa od i : 00406B92 00406BA1 Adresa od d : 00406B98 00406BA3Uočite da je zauzeće memorije pakirane strukture minimalno i jednako zbroju veličine zauzećamemorije pojedinog člana, dok kod nepakirane strukture ostaje neiskorišten jedan bajt izmeđuchar 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 nekommemorijskom objektu može pristupiti s različitim tipskim pristupom. Deklaracija unije sličideklaraciji strukture, primjerice, union skalar { char c; int i; double d; };međutim, njeno značenje je bitno drukčije.Ako se s unijom skalar deklarira objekt imena obj, tj. union skalar obj;tom se objektu može pristupiti na više načina, jer obj može sadržavati vrijednost tipa char,int ili double. Dozvoljeno je pisati: obj.c = A; obj.i = 1237; obj.d = 457.87Sve ove vrijednosti se upisuju u istu memorijsku lokaciju. Veličina zauzeća memorije jeodređena elementom unije koji zauzima najveći prostor memorije. U programiranju se često unija koristi unutar neke strukture kojoj posebni element služi zaoznačavanje tipa vrijednosti koji trenutno sadrži unija. Primjerice, #define char_type 0 #define int_type 1 #define double_type 2 struct variant { int var_type; /* oznaka tipa vrijednosti unije skalar */ union skalar var; }; struct variant obj; /* pored vrijednosti, zabilježimo i tip vrijednosti*/ obj.var.c = A; obj.var_type = char_type; obj.var.i =1237; obj.var_type = int_type; obj.var.d =457.87 obj.var_type = double_type; .......... /* uniju koristimo tako da se najprije provjeri tip vrijednosti */ switch(obj.val_type) { case char_type: printf("%c", obj.var.c); break; case int_type: printf("%d", obj.var.i); break; case double_type: printf("%f", obj.var.d); break; 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 članzauzima. 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, člani1b je 1-bitni kardinalni broj , a član i7b je 7-bitni cijeli broj. (uočite da 1-bitni član možeimati samo dvije vrijednosti 0 i 1) Ovakve strukture se koriste u cilju uštede memorijskog prostora, posebno u slučaju kad sevećina članova tretira kao logička vrijednost. Članovima strukture se pristupa kao da sunormalni 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 sene može koristiti pokazivače na članove strukture jer oni nisu direktno adresibilni. Iz istograzloga nije moguće koristiti nizove bitnih-polja. Kada se želi kontrolirati kako se slažu bit-polja unutar jedne riječi, na raspolaganju su dvamehanizma. Prvi je da se umetnu bezimeni članovi koji će predstavljati "prazne bitove". Drugije mehanizam da veličina bezimenog polja može biti 0. To je poruka kompilatoru da se na tommjestu završi pakiranje u jednu riječ, i da se od tog mjesta članovi strukture pakiraju u novuriječ. 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 kojemuse 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čkimkonstantama: Nedjelja, Ponedjeljak, Utorak, Srijeda, Cetvrtak, Petak,Subota. Pri kompiliranju programa ovim se konstantama pridjeljuje numerička vrijednostprema pravilu da prvo ime u listi ima numeričku vrijednost 0, drugo ime ima vrijednost 1, trećeime ima vrijednost 2 itd.Pomoću pobrojanog tipa mogu se deklarirati varijable, prema pravilu deklaracija_varijable: enum ime_tipa lista_varijabli;primjerice, enum dani_t danas, sutra; ....... danas = Srijeda; sutra = Cetvrtak; ........Vrijednost se neke ili svih simboličkih konstanti može inicijalizirati već pri samoj deklaracijipobrojanog 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 tretiratiravnopravno 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 3može se koristiti enum deklaracija: 182
  • 183. enum threenum {ONE=1, TWO, THREE};13.5 Strukture i funkcije za očitanje vremenaU datoteci <time.h> definirano je nekoliko funkcija i struktura imena tm, za očitanje imanipuliranje 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čnoje to broj sekundi od 1.1.1970.). Parametar tp, ako nije NULL, također prihvaća trenutnovrijeme u *tp. Da bi se olakšalo pretvorbu ovog vremena u stvarno vrijeme i datum, definirana je strukturatm sa sljedećim članovima: struct tm /* opisuje vrijeme i datum */ { int tm_sec; /* sekunde 0..61 */ int tm_min; /* minute 0..59 */ int tm_hour; /* sat 0..23 */ int tm_mday; /* dan 1..31 */ int tm_mon; /* mjesec 0..11 */ int tm_year; /* broj godina nakon 1900 */ int tm_wday; /* dan u sedmici 0..6 */ int tm_yday; /* dan u godini 0..365 */ int tm_isdst; /* da li je dan promjene sata 0..1 */ };Napomena: ako dan promjene sata nije implementiran tada tm_isdst ima negativnuvrijednost. Broj sekundi može biti veći od 59 u slučaju prestupnog vremena. Mjeseci su kodiranu takoda 0 označava siječanj, 1 veljaču itd. Dani u sedmici su kodirani tako da 0 označava nedjelju, 1ponedjeljak itd. Stvarna godina se dobije tako da se članu tm_year doda vrijednost 1900(primjerice u godini 2002. godini član tm_year sadrži vrijednost 102).localtime, gmtime Pretvorbu vremena iz formata time_t u struct tm vrši se funkcijom localtime(),kada se želi dobiti lokalno vrijeme, ili funkcijom gmtime() za dobiti univerzalno vrijeme unultom 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ćajupokazivač na statičku strukturu tipa tm (sadržaj se obnavlja pri svakom pozivu ovih funkcija) .ctime, asctimeAko 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ćajupokazivač statičkog stringa koji sadrži zapis vremena u standardnom formatu. Primjerice,sekvenca naredbi time_t t = time(NULL); char *s = ctime(&t); puts(s);generira ispis: Sat May 11 14:21:20 2002Uoč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 uformatu 11.05.2002 14:21tada se može koristiti sljedeće iskaze: /* ispisuje datum i vrijeme u formatu 11.05.2002 14:21 */ time_t t = time(NULL); struct tm *p = localtime(&t); printf("%.2d.%.2d.%.2d %2d:%.2dn", 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 kodprintf() 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 ispisanizom 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 formatiranistring. 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:%Mn", localtime(&vrijeme)); puts(datum_str); /* ispisuje datum i vrijeme u proizvoljnom formatu */ ptr = localtime(&vrijeme); printf("%.2d.%.2d.%.2d %2d:%.2dn", ptr->tm_mday, ptr->tm_mon+1, ptr->tm_year +1900, ptr->tm_hour, ptr->tm_min); return 0; }Dobije se ispis: Mon May 13 20:13:06 2002 13.05.02 20:13 13.05.2002 20:13Za 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 dobijetime_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 usekundama.clock clock_t clock(void);Funkcija clock() služi za preciznije mjerenje vremena nego je to moguće sa prethodnimfunkcijama. Ona vraća vrijednost procesorskog mjerača vremena, koji starta na početkuprograma, u jedinicama koje su znatno manje od sekunde (nekoliko milisekundi). Koliko je tihjedinica u jednoj sekundi određeno je konstantom CLOCKS_PER_SEC. To znači da izraz: (double)clock()/CLOCKS_PER_SECdaje 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 sekoliko 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 sekundin" , 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 sekundin" , 10*rezolucija/n); return 0; }Dobije se ispis: Rezolucija CLOCK-a je 0.015 sekundi Funkcija sin se izvrsava u 2.3543e-007 sekundiRezolucija 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 tajnačin mjerenje vremena izvršenja sinus funkcije vrši se s greškom koja je manja ili jednaka10%. 187
  • 188. 14 Leksički pretprocesorNaglasci: • 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 Ckompilatora pretprocesor je izveden kao zasebni program. Pretprocesor ne analizira sintaksuzapisanog koda, već koristeći leksičke direktive, vrši leksičku obradu izvornog kôda na trinač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 #endif14.1 Direktiva #includeKoriste 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 zapisanadirektiva. Ako je ime_datoteke zapisano unutar navodnih znakova, traženje datoteke počinje udirektoriju 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 deklaracijamastandardnih funkcija. Ime datoteke može sadržavati potpuni ili djelomični opis staze do datoteke. Zapis ovisi opravilima 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:srcincludeclassZ.h" #include "..includeclassZ.h"14.2 Direktiva #define za makro-supstitucije Leksička supstitucija teksta se definira pomoću direktive #define na dva načina. Prvinačin zapisa, koji se vrši prema pravilu: 188
  • 189. #define identifikator supstitucijski-tekstje 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, umetnesupstitucijski-tekst. Supstitucijski tekst se zapisuje u jednoj ili više linija. Znak nove linijeoznačava kraj zapisa, ali ako je posljednji znak obrnuta kosa crta tada supstitucijskom tekstupripada 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 iliviše makro argumenata, prema pravilu: #define identifikator(argument, ... , argument) supstitucijski-tekst-s-argumentimatako 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/yse 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 neodgovara 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 uzagradama, primjerice: #define KVADRAT(x) ((x)*(x)) #define POSTOTAK(x,y) ((x)*100.0/(y))Tada se ekspanzija makroa uvijek vrši s istim značajem, primjerice poziv makroa rezultira kodom Y = KVADRAT(a+b) y = ((a+b)*(a+b)) Y = POSTOTAK(a+b,c+d) y = ((a+b)*100.0/(c+d)) 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), štodaje vrijednost 6. Ovaj proračun se provodi tijekom kompiliranja.14.3 String operatori # i ## Ekspanzija makro argumenata se ne vrši ako je argument u supstitucijskom tekstu zapisan unavodnim znakovima, odnosno unutar stringa. Ponekad je potrebno da se neki argument uekspanziji makroa pojavljuje literalno kao string. To se postiže tako da se u supstitucujskomtekstu nepesredno ispred imena argumenta napiše znak # (tzv. string operator). U ekspanzijimakroa se tada taj argument pojavljuje zapisan unutar navodnih znakova. Primjerice, zadefiniciju #define PRINT(x) printf(#x "=%g", x)vrši se ekspanzija PRINT(max) u printf("max" "=%g", max), a pošto se dva uzastopnanavodnika 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 " = %gn", fun ((x))) int main() { PRINT_FUN(sin, 3); PRINT_FUN(cos, 3); return 0; }Ispis je: 190
  • 191. sin = 0.14112 cos = -0.989992 Operator ## djeluje na dva susjedna simbola na način da preprocesor izbaci taj operator isva prazna mjesta, pa se dobije jedan novi simbol s identifikatorom sastavljenim od ta dvasusjedna 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 "=%dn", 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]=1014.4 Direktiva #undef Ponekad je potrebno redefinirati značaj nekog identifikatora. U tu svrhu koristi se direktiva#undef: #undef identifikatorOvom se direktivom poništava prethodni značaj identifikatora. Primjerice, #define SIMBOL_X VALUE_FOR_X .... .... /* područje gdje se koristi SIMBOL_X */ .... 191
  • 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 ćedojaviti 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 svrhuse koriste direktive: #if, #ifdef, #ifndef, #elif, #else i #endif. Iza direktiva #if i #elif (else-if) mogu se koristiti prosti izrazi koji rezultirajucjelobrojnom 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 kojislijedi 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 datotekabiti 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 HDRPretpostavljeno je da je prethodno definirana vrijednost simbola: LINUX, WIN32, MSDOS iSISTEM.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 makrosimbole), inače je 0. Primjerice, da bi se osiguralo da se sadržaj neke datoteke može uključiti uizvorni 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 */ #endifPri prvom uključenju ove datoteke definira se simbol HEADER_H i sadržaj datoteke se uljučujeu izvorni kod. Ako se ova datoteka uključi po drugi put, neće se koristiti njen sadržaj jer tadapostoji definiran simbol HEADER_H. Direktive #ifdef i #ifndef su specijalizirane za ispitivanje da li je neki identifikatordefiniran (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 tokovimaNaglasci: • 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 datoteka15.1 Ulazno-izlazni tokovi U standardnoj je biblioteci implementiran niz funkcija koje na jedinstven način tretiraju sveulazno izlazne operacije: unos s tipkovnice, ispis na ekran te čitanje i pisanje informacija koje sepohranjuju na magnetskim i optičkim medijima. Komuniciranje s uređajima koji obavljaju oveoperacije vrši se sekvencijalno bajt po bajt, a programski mehanizam kojim se vrši ovakviprijenos informacije naziva se tok (eng. stream). U jednom se programu može raditi s više tokova. Svakom toku se pridjeljuje jednastruktura podataka imena FILE, koja je definirana u <stdio.h>. Temeljna namjena testrukture je da služi kao memorijski ulazno/izlazni međuspremnik (eng. I/O buffer) pri prijenosupodataka. Važno je znati da se pri izlaznim operacijama podaci ne šalju direktno vanjskimuređajima, već se najprije upisuju o ovaj međuspremnik. Kada se on ispuni, tada se sadržajcijelog međuspremnika šalje vanjskom uređaju. Na ovaj način se smanjuje broj pristupa disku iznatno ubrzava rad s datotekama. Sličnu namjenu ovaj međuspremnik ima i pri ulaznimoperacijama.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 pripokretanju 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 tokovamora 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 prijenospodataka. Ono što on mora znati je pokazivač toka, s kojim se komunicira, i funkcije pomoćukojih se ta komunikacija realizira. Pokazivača toka (FILE *) se mora navesti kao argumentsvake funkcije s kojom se vrše ulazno/izlazne operacije. Primjerice, za formatirani ispispodataka 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 spromjenjljivim brojem argumenata. Formatirani ispis se vrši prema obrascu koji se zapisuje ustringu format, na isti način kako se zapisuje format ispisa u printf()funkciji. Primjerice,za ispis stringa "Hello World" na standardnom izlazu može se koristiti naredba: fprintf(stdout, "Hello World");koja ima isti učinak kao naredba: printf("Hello World");Zapravo, printf() funkcija je interno realizirana kao fprintf() funkcija kojoj je pokazivačtoka jednak stdout.15.2 Binarne i tekstualne datoteke Informacije se u datoteke zapisuju u kodiranom obliku. Temeljna su dva načina kodiranogzapisa: binarni i tekstualni (ili formatirani). Kaže se da je zapis izvršen u binarnu datoteku kadase 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činkako se vrši tekstualni ispis na video monitoru, primjerice printf() funkcijom. Sadržajtekstualnih datoteka se može pregledati bilo kojim editorom teksta, dok sadržaj binarnihdatoteka 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 sene 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 dase ne unose linije koje sadrže više od 256 znakova, jer se time osigurava da će tekstualnadatoteka biti ispravno očitana s gotovo svim programima koji manipuliraju s tekstualnimzapisom. Potrebno je napomenuti da se na MS-DOS računalima tekstualne datoteke zapisujutako da se svaki znak n (CR) pretvara u dva znaka "rn" (CR-LF), a kada se vrši očitavanje sdiska tada se ""rn" prevodi u jedan znak n. Ova se operacija obavlja na razini operativnogsustava. Programer o njoj ne mora voditi računa ukoliko koristi funkcije koje su u C jezikupredviđene za rad s tekstualnim datotekama.. Iznimka je slučaj kada se datoteka, koja jezapisana u tekst modu, tretira kao binarna datoteka. Na UNIX sustavima se ne vrši ovapretvorba. Prema ANSI/ISO standardu u datoteka <stdio.h> sadrži prototipove funkcija za rad sdatotekama. Neke od ovih funkcija predviđene su za rad s binarnim datotekama, a neke za rad stekstualnim datotekama. Prije nego se opiše te funkcije najprije će biti pokazano kako sepristupa datotekama.15.3 Pristup datotekama Svaka datoteka ima ime. Ime datoteke je spremljeno na disku kao tekstualni zapis uposebnoj 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 datotekei atributi pristupa datoteci (read, write, hidden). Da bi se moglo koristiti neku datoteku potrebno je od operativnog sustava zatražiti dozvolupristupa toj datoteci. Taj proces se zove otvaranje datoteke. Isto tako se za kreiranje novedatoteke mora zatražiti dozvola od operativnog sustava. Tu funkciju obavlja standardna funkcijafopen(). Ona pored komunikacije s operativnim sustavom kreira datotečni tok koji sadržimemorijski 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:datalist.txt"; bi koristili na Windows računalima za otvoriti datoteku imena list.txt koja se nalazi na disku c: u direktoriju imena data. Napomenimo da se u imenu datoteke ne smiju koristiti znakovi : /, , :, *, ?, ", <, > i |. Ako se zapiše samo ime datoteke, podrazumijeva se da se datoteka nalazi u tekućem direktoriju.mod je string koji se opisuje način otvaranja datoteke. Zapisuje s jednim ili više znakova: r, w, a i +, čije značenje je dano u tablici 15.1. mod značenje "r" Otvori datoteku za čitanje (eng. read). Ako datoteka ne postoji fopen() vraća NULL. Otvori datoteku za pisanje (eng. write). Ako ne postoji datoteka zadanog imena kreira se nova datoteka. "w" 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 "a" 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. "r+" 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+ Otvori datoteku za čitanje i dopunu . Ako ne postoji datoteka zadanog imena kreira se nova datoteka. "a+" 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 "b" modu, inače se datoteka otvara u tekstualnom modu. Tablica 15.1. Značaj znakova u stringu mod funkcije fopen() Funkcija fopen() vraća pokazivač toka (FILE *). U slučaju greške vrijednost togapokazivač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, zaotvoriti 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, "%sn" "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 sejednim dijelom nalaze u međuspremniku strukture FILE, bili spremljeni na disk, te da bi seispravno 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, avraća vrijednost nula ako je proces zatvaranja uspješan ili EOF ako pri zatvaranju nastanepogreška.Ukoliko se ne zatvori datoteka pomoću ove funkcije, ona će biti prisilno zatvorena po završetkuprograma. Ipak se preporučuje da se uvijek zatvori datoteka čim se s njome završi rad, jer setime štede resursi operativnog sustava i osigurava od mogućeg gubitka podataka (primjerice, priresetiranju računala dok je program aktivan, pri nestanku električnog napajanja, ili ako nastupiblokada programa). U nekim će programima biti potrebno da datoteke budu otvorene cijelo vrijeme. U tomslučaju je zgodno koristiti funkciju fflush() kojom se forsira pražnjenje datotečnogmeđuspremnika i ažurira stanje datoteke na disku, bez zatvaranja datoteke. Prototip funkcijefflush() je int fflush(FILE *fp);funkcija prima argument koji je pokazivač toka prethodno otvorene datoteke, a vraća vrijednostnula ako je proces pražnjenja međuspremnika uspješan ili EOF ako pri zapisu podataka izmeđuspremnika nastane greška.Funkcija fflush(stdin) s također često koristi za odstranjivanje viška znakova izstandardnog ulaza.15.4 Formatirano pisanje podataka u datoteku Formatirano pisanje se vrši pomoći fprintf() funkcije. Pokažimo to sljedećimprogramom: /* 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 3.14159 9.99 1.50 3. 1000.01 Otipkaj ime datoteke brojevi.txt data[0] = 3.141590 data[1] = 9.990000 data[2] = 1.500000 data[3] = 3.000000 198
  • 199. data[4] = 1000.010 Sada procitaj datoteku: brojevi.txt, nekim editorom15.5 Formatirano čitanje podataka iz datoteke Za formatirano čitanje sadržaja datoteke koristi se fscanf() funkcija, koja je poopćenioblik 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 dobijekada se datoteka otvori s atributom "r", "r+" ili "w+". String fmt služi za specifikaciju formatapo kojem se učitava vrijednost varijabli, čije adrese se koriste kao argumenti funkcije. Tri točkeoznačavaju proizvoljan broj argumenata, uz uvjet da svakom argumentu mora pripadati po jedanspecifikator formata u stringu fmt.Primjer: Program txtfile-read.c čita sadržaj datoteke "input.txt", a zatim ga ispisuje nastandardni izlaz. Prethodno je potrebno nekim editorom teksta formirati datoteka imena"input.txt", sa sljedećim sadržajem: 6.8 5.89 67 1.099010 67.001 /* Datoteka: txtfile-read.c */ /* Demonstrira se upis u tekstualnu datotke */ /* 5 realnih brojeva, koje unosu korisnik */ #include <stdio.h> int main() { float f1, f2, f3, f4, f5; FILE *fp; if ( (fp = fopen("INPUT.TXT", "r")) == NULL) { fprintf(stderr, "Greska pri otvaranju datoteke.n"); exit(1); } fscanf(fp, "%f %f %f %f %f", &f1, &f2, &f3, &f4, &f5); fprintf(stdout, "Vrijednosti u datoteci su:n"); fprintf(stdout, "%f, %f, %f, %f, %fn.", f1, f2, f3, f4, f5); fclose(fp); return(0); }Ispis je: Vrijednosti u datoteci su: 6.800000, 5.890000, 67.000000, 1.099010, 67.000999 199
  • 200. Uočite da je posljednji broj u datoteci 67.0001 pročitan kao 67.000999. Očito je da se upretvorbi koja se vrši pri formatiranom unosu gubi na točnosti. Funkcija fscanf() je pogodna za formatirani unos brojeva, ali nije pogodna za unosstringova i znakova. To je pokazano u prethodnim poglavljima, kada je analizirana upotrebafunkcije scanf().Dalje će biti opisane funkcije koje omogućuju dobavu znakova i linija teksta.15.6 Znakovni ulaz/izlaz U pristupu datotekama pojam znakovni ulaz/izlaz se koristi za transfer pojedinačnog znakaili jedne linije (linija je nula ili više znakova zaključenih s znakom nove linije). Znakovniulaz/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 sefunkcija 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 standardnimulazom, 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 kojistring prihvaća (uključujući i nul znak), a fp je pokazivač toka. Znakovi se uzimaju iz toka svedo pojave znaka nove linije ili dok se ne prenese n-1 znakova (fgets() postavlja n-ti znak0). Ako je transfer uspješan fgets() vraća pokazivač str, a ako je transfer neuspješan iliako 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 ovafunkcija koristi za dobavu linije teksta. Preporuka je da se uvijek alocira dovoljno memorije zastring, kako be se učitala čitava linija. Ponekad je pri dobavi znaka potrebno vratiti taj znak u tok. U tu svrhu se može koristitifunkcija int ungetc(int c , FILE *fp);ungetc() je korisna pri leksičkoj analizi. Primjerice, ako se iz toka dobavlja niz znamenki možeih se pridijeliti nekom broju sve dok se ne pojavi znak koji nije znamenka. Tada je zgodnovratiti taj znak u tok kako bi bio na raspolaganju u nekoj drugoj operaciji. Primjerice, sljedećiiskazi 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 samojednog znaka. Ukoliko se ne može izvršiti ova operacija funkcija ungetc() vraća EOF, a ako jeoperacija 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 tefunkcije 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 utok 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. Ovafunkcija zapisuje string bez zaključnog nul znaka. Ako je transfer uspješan vraća pozitivnuvrijednost, 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 mogukoristiti funkcije putc() i fgetc() ali se tada ne vrijedi da se vrijednost EOF (-1) koristi zadojavu 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 nijemoguće čitanje iz datoteke.Primjer: kopiranje datotekaKopiranje sadržaja jedne datoteke u drugu datoteku provodi se prema sljedećem algoritmu: 1. Otvori izvornu datoteku u binarnom modu (tj. datoteku iz koje se kopira) 2. Otvori odredišnu datoteku u binarnom modu (tj. datoteku u koju se kopira) 3. Učitaj znak iz izvorne datoteke 4. Ako funkcija feof() vrati vrijednost različitu od nule (nije dosegnut kraj datoteke) tada upiši znak u odredišnu datoteku i vrati se na korak 3. 5. Ako funkcija feof() vrati 0 to znači da je dosegnut kraj datoteke. U tom slučaju zatvori obje datoteke.Ovaj algoritam je implementiran u funkciji 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 akonije 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šnedatoteke 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 ime2n"); return 1; } ime1 = argv[1]; ime1 = argv[2]; if( kopiraj_datoteke(ime1, ime1 ) ) 202
  • 203. puts("Kopiranje zavrseno uspjesnon"); else puts("Kopiranje neuspjesno!n"); return(0); } U radu s binarnim datotekama za detekciju kraja datoteke isključivo se koristi funkcija feof(), dok se u radu s tekstualnim datotekama kraj datoteke može detektirati i kada funkcije vrate EOF (obično je to vrijednost -1).15.7 Direktni ulaz/izlaz za memorijske objekte Najefikasniji i najbrži način pohrane bilo kojeg složenog memorijskog objekta je da sezapisuje u binarne datoteke. Na taj način datoteka sadrži sliku memorijskog objekta, pa se istimož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().fwriteFunkcija fwrite() služi za zapis u datoteku proizvoljnog broja bajta s neke memorijskelokacije. 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 argumentcount označava ukupni broj takovih elemenata koji se zapisuju. Primjerice, ako je potrebnozapisati 100 elemenata niza cijelih brojeva, tada je size jednak 4 (sizeof int), a count jejednako 100. Dakle, ukupno se zapisuje 400 bajta.Funkcija vraća vrijednost koja je jednaka broju elemenata koji su uspješno zapisani. Ako je tavrijednost različita od count, to znači da je nastala pogreška. Uobičajeno je da se pri svakomtransferu 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 memorijskulokaciju. 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 argumentcount 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 tavrijednost 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 datotekuimena "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("%dt%dn", niz1[i], niz2[i]); return(0); }Dobije se ispis: 0 0 7 7 14 14 21 21 28 28 35 35 42 42 49 49 56 56 63 63 Prednost zapisa u binarnom modu u odnosu na formatirani zapis nije samu u efikasnom korištenju resursa i brzini rada. U binarnom modu nema gubitka informacije (smanjenja točnosti numeričkih zapisa) koje se javljaju u formatiranom zapisu. Nije preporučljivo direktno zapisivanje u datoteku struktura koji sadrže pokazivačke članove, jer pri ponovnom učitavanju takovih struktura pokazivači neće sadržavati ispravne adrese.Funkcije rewind() i ftell() U prethodnom programu bilo je potrebno dva puta otvoriti i zatvoriti datoteku istog imena.U oba slučaja se čitanje/pisanje vršilo od početka datoteke. U slučaju kada se s nekomdatotekom vrši čitanje i pisanje ona se može otvoriti u modu "w+b". Da bi ovi procesi startali odpočetka datoteke tada je potrebno koristiti funkciju rewind() kojom se mjesto pristupadatoteci 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žefunkcijom ftell() kojoj je prototip: long ftell(FILE *fp);Funkcija ftell() vraća cjelobrojnu vrijednost koja odgovara poziciji (u bajtima) slijedećegpristupa datoteci. U slučaju pogreške funkcija vraća vrijednost -1L.Primjer: Program binio1.c ima isti učinak kao i program binio.c, ali se koristi mod "w+b"i funkcija rewind(). Također, demonstrira se upotreba funkcije ftell(). /*Datoteka: binio1.c */ #include <stdlib.h> #include <stdio.h> #define SIZE 10 205
  • 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 bajtan", 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("%dt%dn", 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 ilipisanje 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 proizvoljnomjesto u datoteci. Svakoj se otvorenoj datoteci dodjeljuje jedan pozicijski indikator koji označava poziciju (ubajtima) od početka datoteke na kojoj će biti izvršeno čitanje ili pisanje. U svim dosadašnjimprimjerima korišten je sekvencijalni pristup datoteci. U tom slučaju, nakon otvaranja datotekepozicijski indikator ima vrijednost 0, a kada se datoteka zatvori pozicijski indikator imavrijednost koja je jednaka broju bajta koji su zapisani u datoteci, ukoliko je posljednja operacijabila pisanje u datoteku. Proizvoljni pristup datoteci ima smisla samo kod binarnih datoteka, kod kojih se čitanje ipisanje vrši kontrolirano bajt po bajt. On se ostvaruje pomoću funkcije fseek().fseekFunkcija 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, atreći argument određuje od koje startne pozicije se vrši pomak pozicijskog indikatora. Ovastartna pozicija se određuje pomoću tri konstante koje su definirane u <stdio.h>, a njihovznač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šnavrać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 korisniktako 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 datotekaTemeljne operacije za održavanje datoteka su brisanje datoteka, promjena imena datoteka ikopiranje datoteka. Prije je definirana funkciju za kopiranje datoteke. Za brisanje datotekekoristi se funkcija remove(), a za promjenu imena datoteke koristi se funkcija rename().Ove funkcije su deklarirane u datoteci <stdio.h>.removePrototipe 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. Funkcijavraća vrijednost 0 ako je operacija brisanja uspješna, a ako je operacija neuspješna vraćavrijednost -1. Razlog neuspješnog brisanja može biti kada datoteka ne postoji ili kada jespremljena s atributom read-only.renamePrototipe 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 imeza 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 diskaPrivremene datotekePonekad je potrebno formirati tzv. privremenu datoteku koja će poslužiti za smještaj podatakakoji se vrši samo za vrijeme izvršenja programa. Tada nije bitno kako se datoteka zove, jer senju treba izbrisati prije nego završi program. Za formiranje imena privremene datoteke može sekoristiti 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štajimena datoteke. Ako je taj pokazivač jednak NULL tada funkcija tmpname() koristi vlastitistatič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 jednomprogramu 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: %sn", 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 - ADTNaglasci: • 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 čekanje16.1 Koncept apstraktnog dinamičkog tipa podatakaPomoću struktura, pokazivača i funkcija mogu se realizirati apstraktni dinamički tipovipodataka (ADT – eng. abstract data type). Mi smo, na neki način, već do sada koristiliapstraktne tipove. Primjerice, int, char ili float apstraktno označavaju karakteristike nekogmemorijskog objekta i operacije koje se mogu izvršavati s tim objektom. Pošto smo se na tetipove 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 predstavljaoznaku 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. ModelPrvi stupanj definiranja ADT-a je izrada modela podataka i operacija kojima se opisuje objektapstrakcije, neovisno o načinu kako će biti implementiran u C jeziku. Model se opisujealgoritamskim zapisima i matematičkom aksiomatikom operacija.2. SpecifikacijaNa 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. ImplementacijaNa temelju specifikacije vrši se implementacija modela, odnosno definiranje potrebnih Cfunkcija i struktura podataka. Implementacija se zapisuje u jednoj ili više datoteka kaosamostalni modul koji se može kompilirati neovisno od programa u kojem se koristi.4. AplikacijaKorisnik upotrebljava ADT modul na temelju opisa koji je dan specifikacijom, i ne zanima gakako je izvršena programska implementacija modela. 210
  • 211. Objekt apstrakcije ADT - model podataka i operacija kojima se opisuje objekt apstrakcije Izrada programa Sučelje s aplikacijom: Implementacija - oznaka tipa ADT-a funkcija i struktura - specifikacija funkcija i podataka potrebnih Izvršni program uvjeta za primjenu ADT-a za realizaciju ADT-a Slika 16.1. Konceptualni model ADT-a Kako se realizira apstraktni tip bit će najprije pokazano na primjeru apstraktnog objektabrojača. Primjer programske realizacije objekta brojača već je prije opisan u lekciji omodularnom programiranju. Tada je model rada brojača bio sljedeći - stanje objekta brojačaopisivale su dvije statičke varijable: count (koja pokazuje izbroj) i mod (koja određuje modulbrojač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 programuomogućuje postojanje samo jednog apstraktnog objekta brojača. Sada će biti pokazano kako seprogramski može omogućiti višestruka pojavnost objekta brojača.Stanje apstraktnog objekta brojača bit će opisano strukturom _counter, pomoću koje sedefinira 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 tipaCOUNTER, određuju dvije funkcije: new_counter(), koja vrši dinamičko alociranje objektabrojač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 primjenufunkcije (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 specifikacijifunkcija pojavljuje preduvjet PRE: pc != NULL. Ovaj će preduvjet biti uvijek ispunjen ako sepri 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 seprogram i izvještava u kojoj datoteci i u kojem retku izvornog koda je došlo do greške. Poštoovo 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 simbolNDEBUG, primjerice: c:> cl /D"NDEBUG" ime_datoteke.cili 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 svimfunkcijama koje kao argument imaju pokazivač pc. Slijedi opis implementacije: /* Datoteka: counter-adt.c * Implementacija ADT brojača po modulu mod */ #include <limits.h> /* zbog definicija INT_MAX*/ #include <stdlib.h> /* zbog definicija malloc i free*/ #include <assert.h> #include "counter-adt.h" typedef struct _counter { int count; int mod; } counter; /* typedef struct _counter *COUNTER; definiran u counter-adt.h */ COUNTER new_counter(int mod) { COUNTER pc = malloc(sizeof(counter)); if(pc != NULL) { if(mod <= 1) mod = INT_MAX; pc->mod = mod; pc->count=0; } return pc; } void delete_counter(COUNTER pc) { assert(pc != NULL); free(pc); } void reset_counter(COUNTER pc, int mod) { assert(pc != NULL); if(mod <= 1) mod = INT_MAX; pc->mod = mod; pc->count=0; } int incr_count(COUNTER pc) { assert(pc != NULL); pc->count++; if(pc->count >= pc->mod) pc->count = 0; return pc->count; 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 dvaneovisna 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("%dtt %dn", get_count(pc1),get_count(pc2)); } printf("itd........n"); delete_counter(pc1); delete_counter(pc2); return 0; }Nakon izvršenja ovog programa dobije se ispis: brojac(mod=5), brojac(mod=2147483647) 1 1 2 2 3 3 4 4 0 5 1 6 2 7 3 8 4 9 0 10 1 11 itd........ 214
  • 215. Na kraju razmatranja važno je uočiti da u specifikaciji (counter-adt.h) nije navedenastruktura 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, ane onome tko ga koristi. Na ovaj način se ostvaruje princip enkapsulacije – skrivanja detaljaimplementacije od korisnika modula. Početnicima ovaj princip nema posebno značenje, aliiskusnim 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 bitiprimijenjen.16.2 Stog i STACK ADT Stog je naziv za kolekciju podataka kojoj se pristupa po principu LIFO – last in first out.Primjerice, kada slažemo tanjure tada stavljamo jedan tanjur poviše drugog – tu operacijuzovemo push(), a kada uzimamo tanjur tada uvijek uzimamo onaj tanjur kojeg smo posljednjegstavili u stog – tu operaciju nazivamo pop(). Pored ove dvije temeljne operacije, obično se upristupu stogu koriste još dvije operacije: top() – vraća vrijednost elementa koji je na vrhu stogai 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_ADTtypedef 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 */ Slika 16.2. Stog - operacije/* PRE: stog postoji *//* POST: na stogu je jedan element vise */#endif 215
  • 216. Implementacija se može izvršiti na više načina. Sada će biti opisana implementacija u kojojse za spremanje elemenata stoga koristi podatkovna struktura tipa dinamičkog niza, a upoglavlju 18 bit će pokazana implementacija pomoću strukture podataka tipa vezane liste.Implementacija ADT STACK pomoću dinamičkog niza je opisana u datoteci "stack-arr.c". /* Datoteka: stack-arr.c: * Implementacija ADT STACK pomoću niza */ #include <stdlib.h> #include "stack.h" #define STACK_GROW 10U #define STACK_SIZE 100U /* typedef int stackElemT; definirano in stack.h*/ /* typedef struct stack *STACK; definirano in stack.h*/ struct stack { stackElemT *A; unsigned top; /* indeks poviše stvarnog vrha stoga*/ unsigned size; /* veličina niza*/ }; static void stack_error(char *s) { printf("nGreska: %sn", 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 stogn" "2 - pop - skini vrijednost sa stogan" "0 - kraj programan"); } 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!nn"); 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čkihizraza u postfiksnoj notaciji. Postfiksna notacija izraza se piše tako da se najprije napišuoperandi, 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. Svojstvapostfiksne 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 zagradePretvorba izraza (8+2*5)/(1+3*2-4), prema gornjem pravilu, je sljedeća: prema 1. ( ( 8 + ( 2 * 5 ) ) / ( 1 + ( ( 3 * 2 ) - 4 ) ) ) prema 2. i 3. ( ( 8 + ( 2 * 5 ) ) ( 1 + ( ( 3 * 2 ) - 4 ) ) / ( 8 ( 2 * 5 ) + 1 ( ( 3 * 2 ) - 4 ) + / 8 2 5 * + 1 ( ( 3 * 2 ) 4 - + / 8 2 5 * + 1 3 2 * 4 - + / daje notaciju: 8 2 5 * + 1 3 2 * + 4 - / 218
  • 219. Za izračun postfiksnog izraza, koji ima n simbola, vrijedi algoritam:1. Neka je k =1 indeks prvog simbola2. 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 13. Algoritam završava s rezultatom na stogu.Primjer: Izraz zapisan infiksnoj notaciji: (8+2*5) / (1+3*2-4).ima postfiks notaciju: 8 2 5 * + 1 3 2 * + 4 - /Proračun ovog izraza pomoću stoga ilustriran je na slici 16.3: Neobrađeni ulazni niz Operacija Sadržaj stoga1 8 2 5 * + 1 3 2 * + 4 - / push 8 82 2 5 * + 1 3 2 * + 4 - / push 2 8 23 5 * + 1 3 2 * + 4 - / push 5 8 2 54 * + 1 3 2 * + 4 - / pop(b), pop(a),push (a*b) 8 105 + 1 3 2 * + 4 - / pop(b), pop(a),push(a+b) 186 1 3 2 * + 4 - / push 1 18 17 3 2 * + 4 - / push 3 18 1 38 2 * + 4 - / push 2 18 1 3 29 * + 4 - / pop(b), pop(a),push (a*b) 18 1 610 + 4 - / pop(b), pop(a),push(a+b) 18 711 4 - / push 4 18 7 412 - / pop(b), pop(a),push(a-b) 18 313 / pop(b), pop(a),push(a/b) 6 Slika 16.3. Korištenje stoga za proračun izraza koji je zapisan u postfiksnoj notaciji Proračun izraza pomoću postfiksne notacije je jedan od uobičajenih načina kakointerpreteri izračunavaju izraze zapisane u višim programskim jezicima - najprije se vršipretvorba infiksnog zapisa izraza u postfiksni zapis, a zatim se proračun izraza vrši pomoćustoga. U ovom slučaju ne koristi se stog kojim upravlja procesor već se rad stoga simuliraprogramski.Primjer: Datoteka "polish.c" sadrži jednostavni interpreter aritmetičkih izraza. Izraz trebazapisati u komandnoj liniji unutar navodnika, primjerice c:> polish "8 2 5 * + 1 3 2 * + 4 - /"U izrazu se smiju koristiti cijeli brojevi i operatori zbrajanja, oduzimanja, množenja i dijeljenja.Kada se program izvrši dobije se ispis: Rezultat: 8 2 5 * + 1 3 2 * + 4 - / = 6 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 "30 5 2 - / 10 *"n"); exit(1); } str = argv[1]; len = strlen(str); stack = stack_new(); for (i = 0; i < len; i++) { if (str[i] == +) Push(Pop()+ Pop()); if (str[i] == *) Push(Pop()* Pop()); if (str[i] == -) { tmp = Pop(); Push(Pop()- tmp); } if (str[i] == /) { int tmp = Pop(); if (tmp==0) {printf("Djeljenje s nulomn"); 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 prviu red ušao. Ovaj princip pristupa podacima se naziva FIFO – first in first out. Temeljne suoperacije: ADT QUEUE get(Q ) - dobavi element iz reda Q. put( Q , el) - stavi element el u red Q. empty( Q ) - vraća 1 ako je red Q prazan, inače vraća 0. full( Q ) - 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); #endifImplementacija se može provesti na više načina. Za smještaj elemenata reda najčešće se koristiniz ili linearna lista. Najprije ćemo upoznati implementaciju pomoću niza, i to implementacijukoja koristi tzv. cirkularni spremnik. On se realizira pomoću niza, kojem dva indeksa: back ifront, označavaju mjesta unosa (back) i dobave (front) iz reda. Niz je veličineQUEUEARRAYSIZE. Operacije put() i get() se mogu ilustrirati na sljedeći način: 221
  • 222. Početno je red prazan - - - - - - Front=back (red prazan)Front = back;Nakon operacija put() povećava put(a); put(b)se indeks back za 1 a b - - - - front backOperacija get() dobavlja element x = get() /* x sadrži vrijednost a */kojem je indeks jednak front, a a b - - - -zatim se front povećava za front backjedan.Što napraviti kada, nakon put(c); put(d); put(e);višestrukog unosa, back postane - b c d E -jednak krajnjem indeksu niza? front backIdeja je da se back ponovo put(f)postavi na početak niza (takovi - b C d e fspremnik se nazivaju cirkularni back front (red popunjen)spremnik). Time se maksimalnoiskorištava prostor spremnika za Red popunjen ako je:smještaj elemenata reda. (back+1) % QUEUEARRAYSIZE == frontU 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 Redn" " 2 Odstrani broj iz Redan" " 0 Krajn"); } 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.nn"); upute(); break; } printf("? "); scanf("%d", &izbor); } return 0; }16.5 ZaključakOpisana je metoda programiranja, pomoću koje se sustavno analizira, specificira i implementiraprogramske objekte kao apstraktne dinamičke tipove podataka – ADT.Izrada specifikacije operacija s apstraktnim objektima sve više postaje temeljni elementprogramiranja. To osigurava da se maksimalna pažnja posveti onome što treba programirati.Neovisnost specifikacije od implementacije ADT, osigurava fleksibilan i pouzdan razvojprograma.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 algoritamaNaglasci: • rekurzija • podijeli pa vladaj • kompleksnost algoritama • binarno pretraživanje niza • sortiranje17.1 Rekurzivne funkcije U programiranju i matematici često se koriste rekurzivne definicije funkcija. Direktnarekurzija nastaje kada se u definiciji funkcije poziva ta ista funkcija, a indirektna rekurzijanastaje kada jedna funkcija poziva drugu funkciju, a ova ponovo poziva funkciju iz koje jepozvana. Definicija rekurzivne funkcije u pravilu se sastoji od dva dijela: temeljnog slučaja i pravilarekurzije. 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>0Sve 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 prosturadnju nakon koje prestaje rekurzija. Programski se funkcija n! realizira kao funkcija fact(), koja prima argument tipa int ivraća vrijednost tipa int. Koristeći prethodnu matematičku definiciju , funkcija fact() seimplementira 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 razinistrojnog 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. stanje stoga koji se izvršenje funkcije fact(4) koristi za prijenos argumenata funkcije fact(4)= ... 4 4 * fact(3)= ... 4 3 3 * fact(2) ... 4 3 2 2 * fact(1) ... 4 3 2 1 1 * fact(0) ... 4 3 2 1 0 return 1 ... 4 3 2 1 return 1*1 ... 4 3 2 return 2*1 ... 4 3 return 3*2 ... 4 return 4*6 ... => 24 Slika 17.1. Redoslijed izvršenja rekurzivne funkcije fact(4) Poziv funkcije fact(4) počinje tako da se argument vrijednosti 4 stavlja na vrh izvršnogstoga, a zatim se izvršavaju naredbe iz tijela funkcije. Pošto je argument različit od nuleizvršava se naredba return fact(n-1) * n;. Da bi se ona mogla izvršiti, najprije se vršipoziv funkcije fact(n-1). Zbog toga se na izvršni stog stavlja vrijednost argumenta 3 i pozivafunkcija 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 odstranjujevrijednost s vrha stoga (0), a u povratni registar se upisuje vrijednost 1. Program nastavljaizvršenje naredbom koja slijedi iza pozivne funkcije, a to je zapravo onaj dio naredbe returnfact(n-1) * n; u kojoj se vrši množenje povratne vrijednosti od fact(n-1) i argumentan, čija se vrijednost nalazi na vrhu stoga (u ovom slučaju to je vrijednost 1). Zatim se u povratniregistar stavlja vrijednost 1*1 i skida argument s vrha stoga, pa na vrhu stoga ostaje vrijednost2. Program se ponovo vraća na izvršenje u naredbu return 1 * 2;. U povratni registar sesada upisuje vrijednost 2, na vrhu stoga ostaje vrijednost 3 i izvršenje se vraća u naredbureturn 2 * 3; jer je iz nje vršen poziv fact(3). Nakon toga se izvršava naredba return6 * 4;. Ovo je posljednja naredba koja će se izvršiti. Nakon nje je vrh stoga prazan, aizvršenje programa se vraća na naredbu iz pozivne funkcije koja slijedi iza poziva fact(4). Upovratnom 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 najprijevišestrukim pozivom funkcije vrši "traženje" temeljnog slučaja, pri čemu se pamte sva stanjaprocesa, 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 ispunjenjaizvrš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 jepredviđ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 svihkardinalnih 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)+n17.2 Matematička indukcija Rekurzija se koristi i pri dokazivanju teorema indukcijom. Princip matematičke indukcijase koristi kod problema čija se zakonitost može označiti cijelim brojem n, kao S(n). Definira sena sljedeći način. Da bi dokazali da vrijedi zakonitost S(n), za bilo koju vrijednost n: 1. Dokaži da zakonitost S(n) vrijedi u trivijalnom slučaju za n=0 2. Zatim dokaži da vrijedi S(n), ako se pretpostavi da vrijedi S(n-1).Primjer: Suma od n prirodnih brojeva se može izračunati prema izrazu: 1 + 2 + 3 + … + n = n (n +1) / 2Dokaz: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 jednakin, koristeći prethodno izvedenu formulu.17.3 Kule Hanoja Čovjek nije sposoban razmišljati i rješavati probleme na rekurzivan način. U programiranjuse 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 bezsumnje rješenje inteligentne igre koja se naziva Kule Hanoja. Problem je predstavljen na slici 2. Slika 17.2. Kule Hanoia Postoje tri štapa označena s A, B i C. Na prvom štapu su nataknuti cilindrični diskovipromjenljive 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štovatisljedeć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 samojedan 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 uvijekpomiče samo jedan disk, ali ako se prisjetimo da se rekurzivni problemi počinju rješavati tekkad je pronađen temeljni slučaj, u kojem se pomiče samo jedan disk, i da su prije togazapamć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 argumentefunkcije su: broj diskova koje treba pomaknuti, ime početnog štapa , ime ciljnog štapa, imepomoć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 prebacivanjejednog diska. Nju se može odmah definirati u obliku: 228
  • 229. void pomakni_disk(char sa_kule, char na_kulu) { printf("%c -> %cn", sa_kule, na_kulu); }Korištenjem ove funkcije i pravila rekurzije, funkciju pomakni_kulu() se može napisati nasljedeć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 utom 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 -> BU ovom primjeru je očito da se pomoću rekurzije dobije fascinantno jednostavno rješenjeproblema. Teško da postoji neka druga metoda kojom bi se ovaj problem mogao riješiti najednako efikasan način.Zadatak: Provjerite izvršenje programa za slučaj da broj diskova iznosi: 2, 3, 4 i 5. Pokazat ćese da broj operacija iznosi 2n-1, što se može i logično zaključiti, jer se povećanjem brojadiskova za jedan udvostručuje broj rekurzivnih poziva funkcije pomakni_kulu(), a utemeljnom slučaju se vrši samo jedna operacija.Procijenite koliko bi trajalo izvršenje programa pod uvjetom da izvršenje jedne operacije traje1us 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 serekurzija nameće kao prirodni način rješenja problema. Opći princip metode je da se problemlogično podijeli u više manjih problema, tako da se rješenje dalje može odrediti rješavanjemjednog od tih "manjih" problema.17.4.1 Drugi korijen brojaMetodu 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 togazadovoljava postupak u kojem se određuje da vrijednost x predstavlja drugi korijen od n, ako jerazlika (n – x2) približno jednaka nuli, odnosno manja od po volji odabrane vrijednosti epsilon.Točno rješenje se nalazi unutar nekog intervala [d,g]. Primjerice, sigurno je da se rješenje nalaziu intervalu [0,n] ako je n>1, odnosno u intervalu [0,1] ako je n<1. Interval može biti i uži akosmo sigurni da obuhvaća točno rješenje.Do rješenja se dolazi rekurzivno:Temeljni slučaj: Ukoliko se uzme da je vrijednost od x u sredini intervala [d,g], tj. x=(d+g)/2, može se prihvatiti da je to zadovoljavajuće rješenje, ako je širina intervala manja od neke po volji odabrane vrijednosti epsilon (pr. 0,000001) , tj. ako je je g-d < epsilon. Ako je širina intervala veća od epsilon, do rješenje se dolazi koristeći rekurziju prema pravilu (2).Pravilo rekurzije: Ako je n < x2 rješenje se traži u intervalu [d, x], inače, rješenje se traži u intervalu [x, g].Implementacija: U programu korijen.c implementirana je i testrirana funkcijaDrugiKorijen(n), koja vraća drugi korijen od n. U toj funkciji se prvo određuje donja igornja granica intervala unutar kojega se nalazi rješenje, a zatim se poziva funkcijakorijen_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 funkcijasqrt(n). 230
  • 231. /* Program korijen.c */ #include <stdio.h> #include <math.h> #define EPSILON 0.000001 /* proizvoljni kriterij točnosti */ double korijen_rek(double n, double d, double g) { double x = (d + g)/ 2.0; if (g - d < EPSILON) /* temeljni slučaj */ return x; else if (n < x*x ) /* pravilo rekurzije */ return korijen_rek (n, d, x); else return korijen_rek (n, x, g); } double DrugiKorijen(double n) { double g, d=0.0; /* početne granice d=0.0, g=n ili 1 ako je n<1*/ if(n < 0) n= -n; /* samo za pozitivne vrijednosti */ if(n>1) g=n; else g=1.0; return korijen_rek(n, d, g); } int main( int argc, char *argv[]) { int i; double n; for (i = 1; i < argc; i++) { sscanf( argv[i], "%lf", &n); printf("Drugi korijen(%f) = %f (treba biti %f)n", n, DrugiKorijen(n), sqrt(n)); } return 0; }Nakon poziva programa: c:>korijen 5 7.6 3.14Dobije se ispis: Drugi korijen (5.000000) = 2.236068 (treba biti 2.236068) Drugi korijen (7.600000) = 2.756810 (treba biti 2.756810) Drugi korijen (3.140000) = 1.772004 (treba biti 1.772005)17.4.2 Binarno pretraživanje niza Drugi primjer primjene metode podijeli pa vladaj je traženje elementa sortiranog nizametodom koja se naziva binarno pretraživanje niza.Zadatak: Zadan je niz cijelih brojeva a[n] kojem su elementi sortirani od manje prema većojvrijednosti, 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. Akovrijednost od x nije jednaka ni jednom elementu niza, funkcija binSearch() vraća negativnuvrijednost -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 elementjednak traženoj vrijednosti x, njegov indeks je iz intervala [d,g], gdje mora biti istinito g >= d.Trivijalni slučaj je za d=0, g=n-1, koji obuhvaća cijeli niz.Temeljni slučaj: Razmatra se element niza indeksa i = (g+d)/2 (dijelimo niz na dva podniza). Ako je a[i] jednak x, pronađen je traženi element niza, a funkcija vraća indeks i.Pravilo rekurzije: Ako je a[i] <x rješenje se traži u intervalu [i+1, g], inače je u intervalu [d, i-1].Implementacija: int binSearch( int a[],int x, int d, int g) { int i; if (d > g) return –1; i = (d + g)/ 2; if (a[i] == x) return i; if (a[i] < x) return binSearch( a, x, i + 1, g); else return binSearch( a, x, d, i - 1); }Proces traženja vrijednosti x=23 u nizu od 14 elemenata, ilustriran je na slici 17.3. 0 1 2 3 4 5 6 7 8 9 10 11 12 13 1 2 3 5 6 8 9 12 23 26 27 31 34 42 D i g 1 2 3 5 6 8 9 12 23 26 27 31 34 42 d i g 1 2 3 5 6 8 9 12 23 26 27 31 34 42 d i g 1.korak: d=0, g=13, i=6, a[6]<23 2.korak: d=i+1=7,g=14, i=10, a[10]>23 3.korak: d=7, g=i-1=9, i=8, a[8]==23 Slika 17.3. Binarno pretraživanje niza17.5 Pretvorba rekurzije u iteraciju Neke rekurzivne funkcije se mogu transformirati u funkcije koje umjesto rekurzije koristeiteraciju. To su funkcije u kojima je rekurzivni poziv funkcije posljednja naredba u tijelufunkcije, 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četaktijela funkcije, zbog toga se ekvivalentna verzija funkcije može napisati tako da se umjestopoziva 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 "repnarekurzija". (eng. tail recursion). Neki optimizirajući kompilatori mogu prepoznati ovakav oblikrekurzivne funkcije i transformirati rekurzivno tijelo funkcije u iterativnu petlju. Na taj načinse dobije efikasnija funkcija, jer se ne gubi vrijeme i prostor na izvršnom stogu koji su potrebniza 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(), kojaje opisana u prethodnom odjeljku, može se transformirati u funkciju s repnom rekurzijom, nasljedeć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 kaoargument funkcije nije potrebna donja granica indeksa, jer je ona jednaka nuli, a umjesto gornjegranice 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() funkcijaU standardnoj biblioteci implementirana je polimorfna funkcija bsearch(). Služi za binarnopretraž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č naprvi element niza od n elemenata koji zauzimaju el_size bajta. Posljednji parametar jepokazivač 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 programombinsearch.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 %Fpn", *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 0022FF4817.7 Složenost algoritama - "Veliki - O" notacija Pri analizi složenosti algoritama uvodi se mjera "dimenzije problema". Primjerice, kada seobrađuju nizovi, onda dimenziju problema predstavlja duljina niza n, a kada su u pitanjukvadratne matrice, onda je dimenzija matrice istovremeno i dimenzija problema. Ako je ndimenzija problema, onda se efikasnost korištenja memorije može opisati funkcijom M(n), avrijeme izvršenja algoritma funkcijom T(n). Funkcija T(n) se često naziva vremenska složenost algoritma (engl. time complexity), dokje M(n) prostorna složenost. Funkciji T(n) se pridaje znatno više značaja nego veličini M(n), pase pod skraćenim pojmom "složenost" (eng. complexity) obično podrazumijeva vremenskasloženost. Razlog tome je iskustvo u razvoju algoritama, koje je pokazalo da je zauzećememorije znatno manji problem od postizanja prihvatljivog vremena obrade. Analiza brzine izvršenja programa se radi tako da se odredi ukupan broj operacija kojedominantno troše procesorsko vrijeme. Primjerice, u programu za zbrajanje elemenatakvadratne matrice, dimenzija nxn, označen je broj ponavljanja naredbi u kojima se vršizbrajanje. NAREDBE PROGRAMA BROJ PONAVLJANJA S = 0 ; . for(i=0; i<n; i++) n for(j=0; j<n; j++) n*n S = S + M[i][j] n*nAko se uzme da prosječno vrijeme izvršenja jedne naredbe iznosi t0, dobije se da ukupnovrijeme izvršenja algoritma iznosi: T(n) = (2n² + n)*t0Velič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 kojiovisi 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 n0, takove da je 0 ≤ T(n) ≤ C * f(n), za sve vrijednosti n ≥ n0. U prethodnom je primjeru funkcija složenosti određena razmatrajući broj operacija ukojima se vrši zbrajanje. To ne treba uzeti kao pravilo, već za pojedini problem treba sagledatikoje su operacije dominantne po zauzeću procesorskog vremena. Primjerice, kod metodapretraživanja niza to će biti naredbe usporedbe i pridjele vrijednosti. Klasifikacija algoritama prema redu funkcije složenosti za najpoznatije klase algoritamaprikazana je u tablici 17.1. Ova je tablica uređena po kriteriju rastuće složenosti algoritama. Tip algoritma f(n) Konstantan const. Logaritamski log2n Linearan N Linearno-logaritamski nlog2n Kvadratni n2 k Stupanjski n (k>2) Eksponencijalni kn (k>1) Faktorijelni n! Tablica 17.1. Red složenosti algoritama Pri izradi algoritama cilj je nalaženje rješenja koje je manjeg reda složenosti, a posebno jeznačajan rezultat kada se pronađe polinomsko ili logaritamsko rješenje nekog odeksponencijalnih 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 neovisi od veličine problema. Primjerice, program koji računa kvadratni korijen realnog broja dorezultata po pravilu dolazi poslije približno istog vremena rada bez obzira kolika je veličinabroja koji je uzet za ulazni podatak.Logaritamski algoritmi Kod logaritamskog algoritma vrijeme izvršenja programa proporcionalno je (najčešćebinarnom) logaritmu veličine problema. Imajući u vidu sporost porasta logaritamske funkcijevidi se da se ovdje radi o najefikasnijim i stoga najpopularnijim algoritmima. Tipičanpredstavnik logaritamskih algoritama je binarno pretraživanje sortiranog niza prikazano uprethodnom odjeljku.Opća koncepcija logaritamskih algoritama je sljedeća: 1. Obaviti postupak kojim se veličina problema prepolovi. 2. Nastaviti razlaganje problema dok se ne dođe do veličine 1. 3. Obaviti završnu obradu s problemom jedinične veličine. Ukupan broj razlaganja k dobije se iz uvjeta: n / 2k = 1, odnosno k=log2 n. Kako je popretpostavci broj operacija koji se obavlja pri svakom razlaganju približno isti, to je i vrijemerada 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 istovjetnihpodataka 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 petljemože se umjesto vremena rada T(n) statistički odrediti srednji broj naredbi I(n) koje programobavlja. Tada se podrazumijeva da je T(n)=t1*I(n), gdje je vrijeme izvršavanja prosječnenaredbe 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 brojanaredbi: NAREDBE PROGRAMA BROJ PONAVLJANJA max = a[0] 1 for(i=1; i<n; i++) (n-1) if (a[i] > max) (n-1) max = a[i] (n-1)p (0<=p<=1)Ovdje p označava vjerojatnost da dođe do ispunjenja uvjeta a[i]>max ( p ne ovisi od n).Sumirajući broj ponavljanja pojedinih naredbi dobije se da je ukupan broj izvršenih naredbi I(n) = 1 + (n-1)(3+p) = (3+ p) n - 1 - p.U ovom je izrazu dominantan samo prvi član polinoma, pa je f(n) = n, odnosno T(n) = O(n).Linearno-logaritamski algoritmi Linearno-logaritamski algoritmi, složenosti O(n log n) ), spadaju u klasu veoma efikasnihalgoritama jer im složenost, za veliki n, raste sporije od kvadratne funkcije. Primjer za ovakavalgoritam je Quicksort, koji će biti detaljno opisan kasnije. Bitna osobina ovih algoritama jesljedeć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 kakose nakon svakog dijeljenja sekvencijalno obrade svi podaci, to je ukupan broj elementarnihobrada jednak nlog2n, i to predstavlja red funkcije složenosti. 238
  • 239. Kvadratni i stupanjski algoritmi Kvadratni algoritmi, složenosti O(n2), najčešće se dobivaju kada se koriste dvije for petljejedna unutar druge. Primjer je dat na početku ovog poglavlja. Stupanjski algoritmi nastaju kodalgoritama koji imaju k umetnutih petlji, pa je složenost O(nk).Eksponencijalni algoritmi Eksponencijalni algoritmi O(kn) spadaju u kategoriju problema za koje se suvremenaračunala ne mogu koristiti, izuzev u slučajevima kada su dimenzije takvog problema veomamale. 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 udaljenostizmeđu svake dvije točke j i k. Polazeći od jedne točke potrebno je formirati putanju kojom seobilaze 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đivanjuduljina svih mogućih putanja. Broj mogućih putanja iznosi n!. Polazeći iz početne točke postojin putanja do n preostalih točaka. Kada odaberemo jednu od njih i dođemo u prvu točku ondapreostaje n-1 mogućih putanja do druge točke, n-2 putanja do treće točke, itd., n+1-k putanja dok-te točke, i na kraju samo jedna putanja do n-te točke i natrag u polaznu točku. Naravno dapostoje 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, algoritamima složenost O(n!).17.8 Sortiranje Sortiranje je postupak kojim se neka kolekcija elemenata uređuje tako da se elementiporedaju po nekom kriteriju. Kod numeričkih nizova red elemenata se obično uređuje odmanjeg prema većim elementima. Kod nizova stringova red se određuje prema leksikografskomrasporedu. Kod kolekcija strukturnog tipa obično se odabire jedan član strukture kao ključnielement sortiranja. Sortiranje je važno u analizi algoritama, jer su analizom različitih metodasortiranja postavljeni neki od temeljnih kompjuterskih algoritama. Za analizu metoda sortiranja postavlja se sljedeći problem: odredite algoritam pomoćukojeg se ulazni niz A[0..n-1], od n elemenata, transformira u niz kojem su elementi poredani uredu 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 sortiranjeumetanjem, kojima je složenost O(n2). Zatim će biti analizirane dvije napredne metode,quicksort i mergesort, koje koriste metodu "podijeli pa vladaj". Njihova je složenost O(n log2n). U analizi će biti korištena oznaka A[d..g] za označavanje da se analizira neki niz od indeksad do indeksa g. Ovaj način označavanja ne vrijedi u C jeziku.17.8.1 Selekcijsko sortiranjeIdeja 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 1. 4. Ponovi postupak (3) za sve indekse (2..n-2).Primjerice za sortirati niz od 6 elemenata: 6, 4, 1, 5, 3 i 2 , treba izvršiti sljedeće operacije: 6 4 1 5 3 2 -> min od A[0..5] zamijeni sa A[0] 1 4 6 5 3 2 -> min od A[1..5] zamijeni sa A[1] 1 2 6 5 3 4 -> min od A[2..5] zamijeni sa A[2] 1 2 3 5 6 4 -> min od A[3..5] zamijeni sa A[3] 1 2 3 4 6 5 -> min od A[4..5] zamijeni sa A[4] 1 2 3 4 5 6 -> niz je sortiranOvaj 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 najmanjihelemenata, 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 sortiranjaSvaka iteracija vanjske petlje (indeks i) traje konstantno vrijeme t1 plus vrijeme izvršenjaunutarnje petlje (indeks j). Svaka iteracija u unutarnjoj petlji traje konstantno vrijeme t2 .Broj iteracija unutarnje petlje ovisi u kojoj se iteraciji nalazi vanjska petlja: Broj operacija u i unutarnjoj petlji 0 n-1 1 n-2 2 n-3 ... ... n-2 1Ukupno vrijeme je: T(n) = [t1 + (n-1) t2] + [t1 + (n-2) t2] + [t1 + (n-3) t2] + ... + [t1 + (1) t2]odnosno, grupirajući članove u oblik t1 ( …) + (...) t2 dobije se T(n) = (n-1) t1 + [ (n-1) + (n-2) + (n-3) + ... + 1 ] t2Izraz u uglatim zagradama predstavlja sumu aritmetičkog niza 1 + 2 + 3 + ... + (n-1) = (n-1)n/2 = (n2-n)/2,pa je ukupno vrijeme jednako: T(n) = (n-1) t1 + [(n2-n)/2] t2 = - t1 + t1 n - t2 n/2 + t2 n2/2Očito je da dominira član sa n2 , pa je složenost selekcijskog sortiranja jednaka O(n2) .17.8.2 Sortiranje umetanjemAlgoritam sortiranja umetanjem (eng. insertion sort) se temelji na postupku koji je sličan načinukako se slažu igraće karte. Algoritam se vrši u u n-1 korak. U svakom koraku se umeće i-tielement u dio niza koji mu prethodi (A[0..i-1]), tako taj niz bude sortiran. Primjerice, sortiranjeniza od n=6 brojeva izgleda ovako: 6 4 1 5 3 2 -> ako je A[1]< A[0], umetni A[1] u A[0..0] 4 6 1 5 3 2 -> ako je A[2]< A[1], umetni A[2] u A[0..1] 1 4 6 5 3 2 -> ako je A[3]< A[2], umetni A[3] u A[0..2] 1 4 5 6 3 2 -> ako je A[4]< A[3], umetni A[4] u A[0..3] 1 3 4 5 6 2 -> ako je A[5]< A[4], umetni A[5] u A[0..4] 1 2 3 4 5 6 -> niz je sortiranAlgoritam 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 umetanjemSortiranje umetanjem je primjer algoritma u kojem prosječno vrijeme izvršenja nije puno kraćeod vremena izvršenja koje se postiže u najgorem slučaju.Najgori slučajVanjska petlja se izvršava u n-1 iteracija, što daje O(n) iteracija. U unutarnjoj petlji se vrši od0 do i<n iteracija, u najgorem slučaju vrši se također O(n) iteracija. To znači da se u najgoremslučaju vrši O(n) ⋅O(n) operacija, dakle složenost je O(n2).Najbolji slučajU najboljem slučaju u unutarnjoj petlji se vrši 0 iteracija. To nastupa kada je niz sortiran. Tadaje složenost O(n). Može se uzeti da to vrijedi i kada je niz "većim dijelom sortiran" jer se tadarijetko izvršava unutarnja petlja.Prosječni slučajU prosječnom se slučaju vrši n/2 iteracija u unutarnjoj petlji. To također daje složenostunutarnje petlje O(n), pa je ukupna složenost: O(n2).17.8.3 Sortiranje spajanjem sortiranih podnizova (merge sort)Sortiranje metodom spajanja sortiranih podnizova (eng. merge sort) temelji se na ideji da se nizrekurzivno 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 gornjegindeksa 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 podjednakeveličine (indeks s se odredi kao srednja vrijednost s = (d+g)/2), dolazi se do temeljnog slučajakada u svakom nizu ima samo jedan element. Taj jedno-elementni niz je već sortiran, pa se pri"izvlačenju" i