Professional Documents
Culture Documents
Demistificirani
Zagreb, 1997.
Roditeljima i bratu
Boris
Roditeljima, supruzi, a naroito Dori i Mateju
Julijan
Sadraj
2. To C++ or not to C++?.......................................................... 10
2.1.
2.2.
2.3.
2.4.
2.5.
2.6.
Identifikatori .....................................................................................................33
Varijable, objekti i tipovi ...................................................................................35
Operator pridruivanja .....................................................................................37
Tipovi podataka i operatori ..............................................................................38
4.4.1.
Brojevi......................................................................................................38
4.4.2.
Aritmetiki operatori.................................................................................43
4.4.3.
Operator dodjele tipa ...............................................................................48
4.4.4.
Dodjeljivanje tipa brojanim konstantama ...............................................49
4.4.5.
Simbolike konstante...............................................................................50
4.4.6.
Kvalifikator volatile.............................................................................52
4.4.7.
Pobrojenja ...............................................................................................53
4.4.8.
Logiki tipovi i operatori ...........................................................................55
4.4.9.
Poredbeni operatori .................................................................................56
4.4.10.
Znakovi ....................................................................................................58
4.4.11.
Bitovni operatori.......................................................................................60
4.4.12.
Operatori pridruivanja (2 ) ...................................................................67
4.4.13.
Alternativne oznake operatora.................................................................68
4.4.14.
Korisniki definirani tipovi i operatori .......................................................69
4.4.15.
Deklaracija typedef...............................................................................69
4.5.
Operator sizeof ............................................................................................71
4.6.
Operator razdvajanja .......................................................................................72
4.7.
Hijerarhija i redoslijed izvoenja operatora ......................................................72
6.3.
6.4.
6.5.
6.6.
6.7.
Polja podataka...............................................................................................102
6.1.1.
Jednodimenzionalna polja .....................................................................102
6.1.2.
Dvodimenzionalna polja.........................................................................108
Pokazivai .....................................................................................................114
6.2.1.
Nul-pokazivai .......................................................................................119
6.2.2.
Kamo sa zvijezdom (petokrakom) .........................................................120
6.2.3.
Tajna veza izmeu pokazivaa i polja ...................................................121
6.2.4.
Aritmetike operacije s pokazivaima....................................................124
6.2.5.
Dinamiko alociranje memorije operatorom new ...................................127
6.2.6.
Dinamika alokacija polja ......................................................................129
6.2.7.
Dinamika alokacija viedimenzionalnih polja .......................................133
6.2.8.
Pokazivai na pokazivae .....................................................................134
Nepromjenjivi pokazivai i pokazivai na nepromjenjive objekte...................136
Znakovni nizovi..............................................................................................139
6.4.1.
Polja znakovnih nizova ..........................................................................143
Reference ......................................................................................................144
6.5.1.
Reference za hakere .............................................................................145
Nevolje s pokazivaima .................................................................................147
Skraeno oznaavanje izvedenih tipova ......................................................148
7. Funkcije............................................................................... 150
7.1.
7.2.
7.4.8.
Funkcije s neodreenim argumentima...................................................176
Pokazivai i reference kao povratne vrijednosti.............................................179
ivot jednog objekta ......................................................................................181
7.6.1.
Lokalni objekti........................................................................................181
7.6.2.
Globalni objekti ......................................................................................182
7.6.3.
Statiki objekti u funkcijama ..................................................................186
7.7.
Umetnute funkcije..........................................................................................187
7.8.
Preoptereenje funkcija................................................................................189
7.9.
Rekurzija........................................................................................................195
7.10.
Pokazivai na funkcije ...............................................................................196
7.11.
Funkcija main() .......................................................................................202
7.12.
Standardne funkcije...................................................................................204
7.12.1.
Funkcije za rukovanje znakovnim nizovima...........................................206
7.12.2.
Funkcija exit() ...................................................................................210
7.13.
Predloci funkcija.......................................................................................211
7.14.
Pogled na funkcije ispod haube ..............................................................212
7.5.
7.6.
8.12.
Pokazivai na lanove klase .....................................................................271
8.12.1.
Pokazivai na podatkovne lanove .......................................................272
8.12.2.
Pokazivai na funkcijske lanove ..........................................................277
8.13.
Privremeni objekti ......................................................................................280
8.13.1.
Eksplicitno stvoreni privremeni objekti ...................................................281
8.13.2.
Privremeni objekti kod prijenosa parametara u funkciju.........................284
8.13.3.
Privremeni objekti kod vraanja vrijednosti ..........................................288
10.
10.1.
10.1.1.
10.1.2.
10.1.3.
10.2.
10.3.
10.3.1.
10.3.2.
10.3.3.
10.3.4.
10.3.5.
10.3.6.
10.4.
11.
11.1.
11.2.
11.3.
11.4.
11.4.1.
11.4.2.
11.4.3.
11.4.4.
11.4.5.
11.4.6.
11.5.
11.6.
11.7.
11.8.
11.8.1.
11.8.2.
11.9.
11.9.1.
11.9.2.
Standardne konverzije ...........................................................................364
11.9.3.
Korisniki definirane pretvorbe ..............................................................366
11.10.
Nasljeivanje preoptereenih operatora...................................................368
11.11.
Principi polimorfizma .................................................................................371
11.11.1.
Virtualni funkcijski lanovi..................................................................374
11.11.2.
Poziv virtualnih funkcijskih lanova....................................................378
11.11.3.
iste virtualne funkcije .......................................................................380
11.11.4.
Virtualni destruktori ............................................................................380
11.12.
Virtualne osnovne klase.............................................................................382
11.12.1.
Deklaracija virtualnih osnovnih klasa .................................................384
11.12.2.
Pristup lanovima virtualnih osnovnih klasa ......................................385
11.12.3.
Inicijalizacija osnovnih virtualnih klasa...............................................387
12.
12.1.
Uporabna vrijednost predloaka ................................................................390
12.2.
Predloci funkcija.......................................................................................391
12.2.1.
Definicija predloka funkcije ..................................................................392
12.2.2.
Parametri predloka funkcije .................................................................394
12.2.3.
Instanciranje predloka funkcije.............................................................397
12.2.4.
Eksplicitna instantacija predloka ..........................................................402
12.2.5.
Preoptereivanje predloaka funkcija...................................................403
12.2.6.
Specijalizacije predloaka funkcija ........................................................405
12.2.7.
Primjer predloka funkcije za bubble sort ..............................................406
12.3.
Predloci klasa ..........................................................................................408
12.3.1.
Definicija predloka klase ......................................................................409
12.3.2.
Instanciranje predloaka klasa ..............................................................412
12.3.3.
Eksplicitna instantacija predloaka klasa...............................................416
12.3.4.
Specijalizacije predloaka klasa ............................................................416
12.3.5.
Predloci klasa sa statikim lanovima .................................................419
12.3.6.
Konstantni izrazi kao parametri predloaka ...........................................420
12.3.7.
Predloci i ugnijeeni tipovi..................................................................422
12.3.8.
Ugnijeeni predloci.............................................................................423
12.3.9.
Predloci i prijatelji klasa........................................................................426
12.3.10.
Predloci i nasljeivanje ....................................................................427
12.4.
Mjesto instantacije .....................................................................................429
12.5.
Realizacija klase Lista predlokom.........................................................431
13.
13.1.
Problem podruja imena............................................................................434
13.2.
Deklaracija imenika ...................................................................................435
13.3.
Pristup elementima imenika.......................................................................437
13.3.1.
Deklaracija using .................................................................................439
13.3.2.
Direktiva using.....................................................................................443
14.
14.1.
14.2.
14.3.
14.4.
14.5.
14.6.
15.
15.1.
15.2.
15.3.
15.4.
15.4.1.
15.4.2.
16.
16.1.
16.2.
16.3.
16.3.1.
16.3.2.
16.3.3.
16.3.4.
16.4.
16.4.1.
16.5.
16.6.
17.
17.1.
17.2.
17.2.1.
17.3.
17.4.
17.5.
17.6.
17.6.1.
17.6.2.
18.
18.1.
18.2.
18.3.
18.4.
18.4.1.
18.4.2.
18.4.3.
18.5.
to su tokovi ..............................................................................................513
Biblioteka iostream................................................................................515
Stanje toka.................................................................................................516
Ispis pomou cout ..................................................................................517
Operator umetanja << ...........................................................................517
Ispis korisniki definiranih tipova ...........................................................519
Ostali lanovi klase ostream................................................................520
Uitavanje pomou cin ...........................................................................521
18.5.1.
18.5.2.
18.5.3.
18.5.4.
18.6.
18.6.1.
18.6.2.
18.6.3.
18.6.4.
18.6.5.
18.6.6.
18.7.
18.7.1.
18.7.2.
18.7.3.
18.7.4.
18.7.5.
18.8.
18.9.
19.
19.1.
19.2.
19.3.
19.4.
19.5.
19.5.1.
19.5.2.
19.5.3.
19.6.
19.6.1.
19.7.
19.8.
19.9.
10
Prilikom pisanja ove knjige mnogi su nas, s podsmijehom kao da gledaju posljednji
primjerak snjenog leoparda, pitali: No zato se bavite C++ jezikom? To je
kompliciran jezik, spor, nedovoljno efikasan za primjenu u komercijalnim programima.
Na kraju, ja to sve mogu napraviti u obinom C-u. Prije nego to ponemo objanjavati
pojedine znaajke jezika, nalazimo vanim pokuati dati koliko-toliko suvisle odgovore
na ta i slina pitanja.
Dakle, osnovno pitanje je to C++ ini boljim i pogodnijim openamjenskim
jezikom za pisanje programa, od operacijskih sustava do baza podataka. Da bismo to
razumjeli, pogledajmo kojim putem je tekao povijesni razvoj jezika. Na taj nain e
moda biti jasnija motivacija Bjarne Stroustrupa, oca i majke jezika C++.
11
pojave C++ programskog jezika: potrebno je razviti programerski alat koji e osloboditi
programera rutinskih poslova te mu dopustiti da se usredotoi na problem koji rjeava.
Zbog toga se pojavljuje niz viih programska jezika, koji preuzimaju na sebe neke
dosadne programerske poslove. Tako je FORTRAN bio posebno pogodan za
matematike proraune, zatim BASIC koji se vrlo brzo uio, te COBOL koji je bio u
pravilu namijenjen upravljanju bazama podataka.
Oko 1972. se pojavljuje jezik C, koji je direktna pretea dananjeg jezika C++. To
je bio prvi jezik ope namjene te je postigao nevien uspjeh. Vie je razloga tome: jezik
je bio jednostavan za uenje, omoguavao je modularno pisanje programa, sadravao je
samo naredbe koje se mogu jednostavno prevesti u strojni jezik, davao je brzi kd. Jezik
nije bio optereen mnogim sloenim funkcijama, kao na primjer skupljanje smea (engl.
garbage collection): ako je takav podsustav nekome trebao, korisnik ga je sam napisao.
Jezik je omoguavao vrlo dobru kontrolu strojnih resursa te je na taj nain omoguio
programerima da optimiziraju svoj kd. Do unatrag nekoliko godina, 99% komercijalnih
programa bili su pisani u C-u, ponegdje dopunjeni odsjecima u strojnom jeziku kako bi
se kritini dijelovi sustava uinili dovoljno brzima.
No kako je razvoj programske podrke napredovao, stvari su se i na podruju
programskih jezika poele mijenjati. Sloeni projekti od nekoliko stotina tisua, pa i
vie redaka vie nisu rijetkost, pa je zbog toga bilo potrebno uvesti dodatne mehanizme
kojima bi se takvi programi uinili jednostavnijima za izradu i odravanje, te kojima bi
se omoguilo da se jednom napisani kd iskoristi u vie razliitih projekata.
Bjarne Stroustrup je zapoeo 1979. godine rad na jeziku C s klasama (engl. C
with Classes). Prije toga, u Computing Laboratory of Cambridge on je radio na svom
doktoratu te istraivao distribuirane sustave: granu raunalne znanosti u kojoj se
prouavaju modeli obrade podataka na vie jedinica istodobno. Pri tome koristio se
jezikom Simula, koji posjedovao neka vana svojstva koja su ga inila prikladnim za taj
posao. Na primjer, Simula je posjedovala pojam klase: strukture podataka koje
objedinjavaju podatke i operacije nad podacima. Koritenje klasa omoguilo je da se
koncepti problema koji se rjeava izraze direktno pomou jezinih konstrukcija.
Dobiveni kd je bio vrlo itljiv i razumljiv, a g. Stroustrup je bio posebno fasciniran
nainom na koji je sam programski jezik upuivao programera u razmiljanju o
problemu. Takoer, jezik je posjedovao sustav tipizacije, koji je esto pomagao
korisniku u pronalaenju pogreaka ve prilikom prevoenja.
No naoko idealan u teoriji, jezik Simula je posrnuo u praksi: prevoenje je bilo
iznimno dugotrajno, a dobiveni kd se izvodio ekstremno sporo. Dobiveni program je
bio neupotrebljiv, i da bi ipak poteno zaradio svoj doktorat, gospodin Stroustrup se
morao potruditi i ponovo napisati cjelokupan program u jeziku BCPL jeziku niske
razine koji je omoguio vrlo dobre performanse prevedenog programa. No iskustvo
pisanja sloenog programa u takvom jeziku je bilo uasno i g. Stroustrup je, po
zavretku svog posla na Cambridgeu, vrsto sebi obeao da vie nikada nee takav
sloen problem pokuati rijeiti neadekvatnim alatima poput BCPL-a ili Simule.
Kada se 1979. zaposlio u Bell Labs u Murray Hillu, zapoeo je rad na onome to e
kasnije postati C++. Pri tome je iskoristio svoje iskustvo steeno prilikom rada na
doktoratu te pokuao stvoriti univerzalni jezik koji e udovoljiti dananjim zahtjevima.
12
Pri tome je uzeo dobra svojstva niza jezika: Simula, Clu, Algol68 i Ada, a kao osnovu je
uzeo jezik C.
13
14
15
16
vam ba sad treba, a vie ete razmiljati o tome kako da ubrzate postupak sjenanja ili
kako da ga uinite realistinijim.
Objektna tehnologija je pokuala dati odgovore na neke potrebe ljudi koji rjeavaju
svoje zadatke raunalom; na vama je da procijenite koliko je to uspjeno, a u svakom
sluaju da prije toga proitate ovu knjigu do kraja i preporuite ju prijateljima, naravno.
Jer tak dobru i guba knjigu niste vidli ve sto godina i ba vam je bilo fora ju itat.
2.5. Literatura
Tijekom pisanja knjige koristili smo sljedeu literaturu (navodimo ju abecednim
slijedom autora):
[ANSI95]
Working Paper for Draft Proposed International Standard for
Information Systems Programming Language C++, Technical Report
X3J16/95-0087, American National Standards Institute (ANSI), April
1995
[Barton94]
John J. Barton, Lee R. Nackman: Scientific and Engineering C++ An
Introduction with Advanced Techniques and Examples, AddisonWesley, Reading, MA, 1994, ISBN 0-201-53393-6
[Borland94]
Borland C++ 4.5 Programmers Guide, Borland International, Scotts
Valley, CA
[Carroll95]
Martin D. Carroll, Margaret A. Ellis: Designing and Coding Reusable
C++, Addison-Wesley, Reading, MA, 1995, ISBN 0-201-51284-X
[Ellis90]
Margaret A. Ellis, Bjarne Stroustrup: The Annotated C++ Reference
Manual, Addison-Wesley, Reading, MA, 1990, ISBN 0-201-51459-1
17
[Hanly95]
[Horstmann96]
[Kernighan88]
[Kukrika89]
[Liberty96]
[Lippman91]
[Lippman96]
[Murray93]
[Schildt90]
[Stroustrup91]
[Stroustrup94]
Iako se radi o nacrtu standarda, on u potpunosti definira sve karakteristike programskog jezika
kakav e on biti kada se (prema planu ANSI komiteta za standardizaciju jezika C++) u
prosincu 1998. godine objavi konana verzija standarda. Sve promjene koje e se dogaati do
tada odnosit e se iskljuivo na tekst standarda sintaksa jezika i definicije biblioteka ostaju
nepromijenjene.
18
mree trebalo oko 6 sati vremena da se prebaci na nae raunalo. Najnovija verzija
nacrta standarda moe se naruiti i potom (po cijeni od 50 USD + potarina) na
sljedeoj adresi:
X3 Secretariat
CBEMA
1250 Eye Street NW
Suite 200
Washington, DC 20005
Naravno da ga ne preporuujemo za uenje jezika C++, a takoer vjerujemo da nee
trebati niti iskusnijim korisnicima Standard je prvenstveno namijenjen proizvoaima
softvera. Svi noviji prevoditelji (engl. compilers) bi se trebali ponaati u skladu s tim
standardom.
Zadnje poglavlje ove knjige posveeno je principima objektno orijentiranog
programiranja. Naravno da to nije dovoljno za ozbiljnije sagledavanje detaljnije o
objektno orijentiranom programiranju zainteresirani itatelj moe proitati u referentnoj
knjizi Grady Booch: Object-Oriented Analysis and Design with Applications (2nd
Edition), Benjamin/Cummings Publishing Co., Redwood City, CA, 1994, ISBN 0-80535340-2, kao i u nizu lanaka istog autora u asopisu C++ Report.
2.6. Zahvale
Zahvaljujemo se svima koji su nam izravno ili posredno pomogli pri izradi ove knjige.
Posebno se zahvaljujemo Branimiru Pejinoviu (Portland State University) koji nam
je omoguio da doemo do Nacrta ANSI C++ standarda, Vladi Glaviniu (Fakultet
elektrotehnike i raunarstva) koji nam je omoguio da doemo do knjiga
[Stroustrup94] i [Carroll95] te nam je tijekom pripreme za tisak dao na raspolaganje
laserski pisa, te Zdenku imiu (Fakultet elektrotehnike i raunarstva) koji nam je,
tijekom svog boravka u SAD, pomogao pri nabavci dijela literature.
Posebnu zahvalu upuujemo Ivi Mesari koja je proitala cijeli rukopis te svojim
korisnim i konstruktivnim primjedbama znatno doprinijela kvaliteti iznesene materije.
Takoer se zahvaljujemo Zoranu Kalafatiu (Fakultet elektrotehnike i raunarstva) i
Damiru Hodaku koji su itali dijelove rukopisa i dali na njih korisne opaske.
Boris se posebno zahvaljuje gospoama ribar na tonama kolaa pojedenih za
vrijeme dugih, zimskih noi itanja i ispravljanja rukopisa, a koje su ga kotale kure
mravljenja.
I naravno, zahvaljujemo se Bjarne Stroustrupu i dekima iz AT&T-a to su izmislili
C++, na najdrai programski jezik. Bez njih ne bi bilo niti ove knjige (ali moda bi bilo
sline knjige iz FORTRAN-a).
19
20
Poetak
Upis i ispravke
izvornog kda
Prevoenje
Da
Pogreke tijekom
prevoenja?
Ne
Povezivanje
Da
Pogreka tijekom
povezivanja?
21
Ovdje neemo opisivati konkretno kako se pokreu postupci prevoenja ili povezivanja, jer to
varira ovisno o prevoditelju, odnosno povezivau.
22
23
U nekim programskim jezicima glavna funkcija se zove glavni program, a sve ostale funkcije
potprogrami.
24
pkunzip -t mojzip
Ovom naredbom se pokree program pkunzip i pritom mu se predaju dva parametra: t i mojzip. U naem C++ primjeru unutar zagrada nema nita, to znai da ne
prenosimo nikakve argumente. Tako e i ostati do daljnjega, tonije do poglavlja 5.11 u
kojem emo detaljnije obraditi funkcije, a posebice funkciju main().
Slijedi otvorena vitiasta zagrada. Ona oznaava poetak bloka u kojem e se
nalaziti naredbe glavne funkcije, dok zatvorena vitiasta zagrada u zadnjem retku
oznaava kraj tog bloka. U samom bloku prosjeni itatelj uoit e samo jednu naredbu,
return 0. Tom naredbom glavni program vraa pozivnom programu broj 0, a to je
poruka operacijskom sustavu da je program uspjeno okonan, togod on radio.
Uoimo znak ; (toka-zarez) iza naredbe return 0! On oznaava kraj naredbe te
slui kao poruka prevoditelju da sve znakove koji slijede interpretira kao novu naredbu.
Znak ; mora zakljuivati svaku naredbu u jeziku C++.
Radi kratkoe emo u veini primjera u knjizi izostavljati uvodni i zakljuni dio
glavne funkcije te emo podrazumijevati da oni u konkretnom kdu postoje.
Pogledajmo jo na trenutak to bi se dogodilo da smo kojim sluajem napravili
neku pogreku prilikom upisivanja gornjeg kda. Recimo da smo zaboravili desnu
vitiastu zagradu na kraju kda:
int main() {
return 0;
neki prevoditelji e javiti upozorenje oblika Funkcija bi trebala vratiti vrijednost. Iako
se radi o pogreki, prevoditelj e umetnuti odgovarajui kd prema svom nahoenju i
prevesti program. Ne mali broj korisnika e zbog toga zanemariti takva upozorenja.
Meutim, valja primijetiti da esto taj umetnuti kd ne odgovara onome to je
programer zamislio. Zanemarivanjem upozorenja programer gubi pregled nad
korektnou kda, pa se lako moe dogoditi da rezultirajui izvedbeni kd daje na prvi
pogled neobjanjive rezultate.
25
U ovom primjeru uoavamo dva nova retka. U prvom retku nalazi se naredba
#include <iostream.h> kojom se od prevoditelja zahtijeva da u na program
ukljui biblioteku iostream. U toj biblioteci nalazi se izlazni tok (engl. output stream)
te funkcije koje omoguavaju ispis podataka na zaslonu. Ta biblioteka nam je
neophodna da bismo u prvom retku glavnoga programa ispisali tekst poruke. Naglasimo
da #include nije naredba C++ jezika, nego se radi o pretprocesorskoj naredbi.
Naletjevi na nju, prevoditelj e prekinuti postupak prevoenja kda u tekuoj datoteci,
skoiti u datoteku iostream.h, prevesti ju, a potom se vratiti u poetnu datoteku na
redak iza naredbe #include. Sve pretprocesorske naredbe poinju znakom #.
iostream.h je primjer datoteke zaglavlja (engl. header file, odakle i slijedi
nastavak .h). U takvim datotekama se nalaze deklaracije funkcija sadranih u
odgovarajuim bibliotekama. Jedna od osnovnih znaajki (zli jezici e rei mana) jezika
C++ jest vrlo oskudan broj funkcija ugraenih u sam jezik. Ta oskudnost olakava
uenje samog jezika, te bitno pojednostavnjuje i ubrzava postupak prevoenja. Za
specifine zahtjeve na raspolaganju je veliki broj odgovarajuih biblioteka funkcija i
klasa.
cout je ime izlaznog toka definiranog u biblioteci iostream, pridruenog zaslonu
raunala. Operatorom << (dva znaka manje od) podatak koji slijedi upuuje se na
izlazni tok, tj. na zaslon raunala. U gornjem primjeru to je kratka promidbena poruka:
Ja sam za C++!!! A vi?
26
Zadatak. Kako bi izgledao izvorni kd ako bismo eljeli ispis endl znaka staviti u
posebnu naredbu? Dodajte u gornji primjer ispis poruke Imamo C++!!! u sljedeem
retku (popratno sklapanje ruku iznad glave nije neophodno). Nemojte zaboraviti dodati
i ispis endl na kraju tog retka!
27
Ona kae raunalu da treba zbrojiti vrijednosti varijabli a i b, te njihov zbroj pohraniti u
varijablu c, tj. u memoriju na mjesto gdje je prevoditelj rezervirao prostor za tu
varijablu.
U poetku e itatelj zasigurno imati problema s razluivanjem operatora << i >>.
U vjeri da e olakati razlikovanje operatora za ispis i uitavanje podataka, dajemo
sljedei naputak.
Operatore << i >> moemo shvatiti kao da pokazuju smjer prijenosa
podataka [Lippman91].
Primjerice,
>> a
preslikava vrijednost iz c.
28
3.4. Komentari
Na nekoliko mjesta u gornjem kdu moemo uoiti tekstove koji zapoinju dvostrukim
kosim crtama (//). Radi se o komentarima. Kada prevoditelj naleti na dvostruku kosu
crtu, on e zanemariti sav tekst koji slijedi do kraja tekueg retka i prevoenje e
nastaviti u sljedeem retku. Komentari dakle ne ulaze u izvedbeni kd programa i slue
programerima za opis znaenja pojedinih naredbi ili dijelova kda. Komentari se mogu
pisati u zasebnom retku ili recima, to se obino rabi za dulje opise, primjerice to
odreeni program ili funkcija radi:
//
// Ovo je moj trei C++ program, koji zbraja
// dva broja unesena preko tipkovnice, a zbroj
// ispisuje na zaslonu.
//
Autor: N. N. Hacker III
//
Za krae komentare, na primjer za opise varijabli ili nekih operacija, komentar se pie u
nastavku naredbe, kao to smo vidjeli u primjeru.
Uz gore navedeni oblik komentara, jezik C++ podrava i komentare unutar para
znakova /* */. Takvi komentari zapoinju slijedom /* (kosa crta i zvjezdica), a
zavravaju slijedom */ (zvjezdica i kosa crta). Kraj retka ne znai podrazumijevani
zavretak komentara, pa se ovakvi komentari mogu protezati na nekoliko redaka
izvornog kda, a da se pritom znak za komentiranje ne mora ponavljati u svakom retku:
/*
Ovakav nain komentara
preuzet je iz programskog
jezika C.
*/
29
/**********************************************************
Program:
Datoteka:
Funkcije:
Opis:
Autori:
// zbraja a i b
// uveava i za 1
// poziva funkciju sqrt
mogli pisati
30
c=a+b;
31
pri emu nastavak niza ne smijemo uvui, jer bi prevoditelj dotine praznine prihvatio
kao dio niza. Stoga je u takvim sluajevima preglednije niz rastaviti u dva odvojena
niza:
cout << "Pojavom jezika C++ ostvaren je "
"tisuljetni san svih programera" << endl;
Pritom se ne smije zaboraviti staviti prazninu na kraju prvog ili poetak drugog niza.
Gornji niz mogli smo rastaviti na bilo kojem mjestu:
cout << "Pojavom jezika C++ ostvaren je tis"
"uljetni san svih programera" << endl;
Program e biti preveden bez pogreke i raditi e ispravno. No, oito je da je ovako
pisani izvorni kd daleko neitljiviji pisanje vie naredbi u istom retku treba
prakticirati samo u izuzetnim sluajevima.
32
Svaki program sadri u sebi podatke koje obrauje. Njih moemo podijeliti na
nepromjenjive konstante, odnosno promjenjive varijable (promjenjivice).
Najjednostavniji primjer konstanti su brojevi (5, 10, 3.14159). Varijable su podaci koji
openito mogu mijenjati svoj iznos. Stoga se oni u izvornom kdu predstavljaju ne
svojim iznosom ve simbolikom oznakom, imenom varijable.
Svaki podatak ima dodijeljenu oznaku tipa, koja govori o tome kako se dotini
podatak pohranjuje u memoriju raunala, koji su njegovi dozvoljeni rasponi vrijednosti,
kakve su operacije mogue s tim podatkom i sl. Tako razlikujemo cjelobrojne, realne,
logike, pokazivake podatke. U poglavlju koje slijedi upoznat emo se ugraenim
tipovima podataka i pripadajuim operatorima.
4.1. Identifikatori
Mnogim dijelovima C++ programa (varijablama, funkcijama, klasama) potrebno je dati
odreeno ime. U tu svrhu koriste se identifikatori, koje moemo proizvoljno nazvati. Pri
tome je bitno potivati tri osnovna pravila:
1. Identifikator moe biti sastavljen od kombinacije slova engleskog alfabeta (A - Z, a z), brojeva (0 - 9) i znaka za podcrtavanje '_' (engl. underscore).
2. Prvi znak mora biti slovo ili znak za podcrtavanje.
3. Identifikator ne smije biti jednak nekoj od kljunih rijei (vidi tablicu 4.1) ili nekoj od
alternativnih oznaka operatora (tablica 4.2). To ne znai da kljuna rije ne moe biti
dio identifikatora moj_int je dozvoljeni identifikator. Takoer, treba izbjegavati
da naziv identifikatora sadri dvostruke znakove podcrtavanja (_ _) ili da zapoinje
znakom podcrtavanja i velikim slovom, jer su takve oznake rezervirane za C++
implementacije i standardne biblioteke (npr. _ _LINE_ _, _ _FILE_ _).
Stoga si moemo pustiti mati na volju pa svoje varijable i funkcije nazivati svakojako.
Pritom je vjerojatno svakom jasno da je zbog razumljivosti kda poeljno imena
odabirati tako da odraavaju stvarno znaenje varijabli, na primjer:
Pribrojnik1
Pribrojnik2
rezultat
33
34
else
enum
explicit
extern
false
float
for
friend
goto
if
inline
int
long
mutable
namespace
new
operator
private
protected
public
register
reinterpret_cast
return
short
signed
sizeof
static
static_cast
struct
switch
template
this
throw
true
try
typedef
typeid
typename
union
unsigned
using
virtual
void
volatile
wchar_t
while
bitand
bitor
compl
not
not_eq
or
or_eq
xor
xor_eq
35
nekoliko je ovdje prilino rastezljiv), pa ako prevoditelj kojeg koristite uzima u obzir
samo prvih 14 znakova, tada e identifikatore
Snjeguljica_i_7_patuljaka
Snjeguljica_i_sedam_patuljaka
interpretirati kao iste, iako se razlikuju od petnaestog znaka ('7' odnosno 's') na dalje.
Naravno da dugaka imena treba izbjegavati radi vlastite komocije, posebice za
varijable i funkcije koje se esto ponavljaju.
Ponekad je pogodno koristiti sloena imena zbog boljeg razumijevanja kda. Iz
gornjih primjera itateljica ili itatelj mogli su razluiti dva najea pristupa
oznaavanju sloenih imena. U prvom pristupu rijei od kojih je ime sastavljeno
odvajaju se znakom za podcrtavanje, poput
Snjeguljica_te_sedam_patuljaka
Mi emo u primjerima preferirati potonji nain samo zato jer tako oznaene varijable i
funkcije zauzimaju ipak neto manje mjesta u izvornom kdu.
Iako ne postoji nikakvo ogranienje na poetno slovo naziva varijabli, veina
programera preuzela je iz programskog jezika FORTRAN naviku da imena cjelobrojnih
varijabli zapoinje slovima i, j, k, l, m ili n.
36
Varijable postaju realna zbiljnost tek kada im se pokua pristupiti, na primjer kada im se
pridrui vrijednost:
a = 2;
b = a;
c = a + b;
int
int
aa
int
int
22
tada se u memoriju raunala na mjestu gdje je prije bio smjeten broj 2 pohranjuje broj
5, kako je prikazano slikom 4.2.
37
int
int
aa
int
int
55
// pogreka!!!
// pogreka!!!
// Bingooo!!! Trea pogreka!
Pokuamo li prevesti ovaj kd, prevoditelj e javiti pogreke. Objekti koji se smiju
nalaziti s lijeve strane znaka pridruivanja nazivaju se lvrijednosti (engl. lvalues, kratica
od left-hand side values). Pritom valja znati da se ne moe svakoj lvrijednosti
pridruivati nova vrijednost neke varijable se mogu deklarirati kao konstantne (vidi
poglavlje 4.4.4) i pokuaj promjene njihove vrijednosti prevoditelj e naznaiti kao
pogreku. Stoga se posebno govori o promjenjivim lvrijednostima. S desne strane
operatora pridruivanja mogu biti i lvrijednosti i konstante.
Evo tipinog primjera pridruivanja kod kojeg se ista varijabla nalazi i s lijeve i s
desne strane znaka jednakosti:
38
int i = 5;
i = i + 3;
// OK!
// pogreka: b + 1 nije lvrijednost
39
Takva varijabla e zauzeti u memoriji vie prostora i operacije s njom e dulje trajati.
Cjelobrojne konstante se mogu u izvornom kdu prikazati u razliitima brojevnim
sustavima:
dekadskom,
oktalnom,
heksadekadskom.
Dekadski prikaz je razumljiv veini obinih nehakerskih smrtnika koji se ne sputaju na
razinu raunala. Meutim, kada treba raditi operacije nad pojedinim bitovima, oktalni i
heksadekadski prikazi su daleko primjereniji.
U dekadskom brojevnom sustavu, koji koristimo svakodnevno, postoji 10 razliitih
znamenki (0, 1, 2,..., 9) ijom kombinacijom se dobiva bilo koji eljeni vieznamenkasti
broj. Pritom svaka znamenka ima deset puta veu teinu od znamenke do nje desno. U
oktalnom brojevnom sustavu broj znamenki je ogranien na 8 (0, 1, 2,..., 7), tako da
svaka znamenka ima osam puta veu teinu od svog desnog susjeda. Na primjer, 11 u
oktalnom sustavu odgovara broju 9 u dekadskom sustavu (118 = 910). U
heksadekadskom sustavu postoji 16 znamenki: 0, 1, 2,..., 9, A, B, C, D, E. Budui da za
prikaz brojeva postoji samo 10 brojanih simbola, za prikaz heksadekadskih znamenki
iznad 9 koriste se prva slova engleskog alfabeta, tako da je A16 = 1010, B16 = 1110, itd.
Oktalni i heksadekadski prikaz predstavljaju kompromis izmeu dekadskog prikaza
kojim se koriste ljudi i binarnog sustava kojim se podaci pohranjuju u memoriju
raunala. Naime, binarni prikaz pomou samo dvije znamenke (0 i 1) iziskivao bi puno
prostora u programskom kdu, te bi bio nepregledan. Oktalni i heksadekadski prikazi
iziskuju manje prostora i iskusnom korisniku omoguuju brzu pretvorbu brojeva iz
dekadskog u binarni oblik i obrnuto.
Oktalne konstante se piu tako da se ispred prve znamenke napie broj 0 iza kojeg
slijedi oktalni prikaz broja:
40
// odgovara dekadsko 8!
// dekadsko 12
i = 0x20;
cout << i << endl;
Bez obzira to smo varijabli i pridruivali broj 32 u tri razliita prikaza, u sva tri sluaja
na zaslonu e se ispisati isti broj, u dekadskom formatu.
Ako je potrebno raunati s decimalnim brojevima, najee se koriste varijable tipa
float:
float pi = 3.141593;
float brzinaSvjetlosti = 2.997925e8;
float nabojElektrona = -1.6E-19;
41
Prevoditelj e broj
float PlanckovaKonst = 6.626 e -34;
// pogreka
interpretirati samo do etvrte znamenke. Prazninu koja slijedi prevoditelj e shvatiti kao
zavretak broja, pa e po nailasku na znak e javiti pogreku.
Osim to je ogranien raspon vrijednosti koje se mogu prikazati float tipom (vidi
tablicu 4.3), treba znati da je i broj decimalnih znamenki u mantisi takoer ogranien na
7 decimalnih mjesta. ak i ako se napie broj s vie znamenki, prevoditelj e
zanemariti sve nie decimalne znamenke. Stoga e ispis varijabli
float pi_tocniji
= 3.141592654;
float pi_manjeTocan = 3.1415927;
dati potpuno isti broj! Usput spomenimo da su broj i ostale znaajnije matematike
konstante definirani u biblioteci math.h, i to na veu tonost, tako da ukljuivanjem te
datoteke moete prikratiti muke traenja njihovih vrijednosti po raznim prirunicima i
srednjokolskim udbenicima.
Ako tonost na sedam decimalnih znamenki ne zadovoljava ili ako se koriste
brojevi vei od 1038 ili manji od 1038, tada se umjesto float mogu koristiti brojevi s
dvostrukom tonou tipa double koji pokrivaju opseg vrijednosti od 1.710308 do
1.710308, ili long double koji pokrivaju jo iri opseg od 3.4104932 do 1.1104932.
Brojevi vei od 1038 vjerojatno e vam zatrebati samo za neke astronomske proraune,
ili ako ete se baviti burzovnim meetarenjem. Realno gledano, brojevi manji od 1038
nee vam gotovo nikada trebati, osim ako elite izraunati vjerojatnost da dobijete
glavni zgoditak na lutriji.
U tablici 4.3 dane su tipine duljine ugraenih brojevnih tipova u bajtovima,
rasponi vrijednosti koji se njima mogu obuhvatiti, a za decimalne brojeve i broj tonih
decimalnih znamenki. Duljina memorijskog prostora i opseg vrijednosti koje odreeni
tip varijable zauzima nisu standardizirani i variraju ovisno o prevoditelju i o platformi
za koju se prevoditelj koristi. Stoga itatelju preporuujemo da za svaki sluaj
konzultira dokumentaciju uz prevoditelj koji koristi. Relevantni podaci za cjelobrojne
tipove mogu se nai u datoteci limits.h, a za decimalne tipove podataka u values.h.
Ako elite sami provjeriti veliinu pojedinog tipa, to moete uiniti i pomou
sizeof operatora:
cout
cout
long
cout
Broj tonih znamenki ovisi o prevoditelju i o raunalu. U veini sluajeva broj tonih
znamenki i dozvoljene pogreke pri osnovnim aritmetikim operacijama ravnaju se po IEEE
standardu 754 za prikaz brojeva s pominim decimalnim zarezom.
42
Tablica 4.3. Ugraeni brojevni tipovi, njihove tipine duljine i rasponi vrijednosti
tip konstante
bajtova
raspon vrijednosti
tonost
char
short int
int
2
2
(4)
long int
float
3,41038 do 3,41038
3,41038 do 3,41038
7 dec. znamenki
double
1,710308 do 1,710308
1,710308 do 1,710308
15 dec. znamenki
long double
10
1,1104932 do 3,4104932 i
3,4104932 do 1,1104932
18 dec. znamenki
32768 do 32767
32768
do
32767
2147483648 do 2147483647
2147483648 do 2147483647
Svi navedeni brojevni tipovi mogu biti deklarirani i bez predznaka, dodavanjem rijei
unsigned ispred njihove deklaracije:
unsigned int i = 40000;
unsigned long int li;
unsigned long double BrojZvijezdaUSvemiru;
43
unarni operatori
x++
++x
x---x
x + y
x - y
binarni operatori
x * y
x / y
x % y
unarni plus
unarni minus
uveaj nakon
uveaj prije
umanji nakon
umanji prije
zbrajanje
oduzimanje
mnoenje
dijeljenje
modulo
objekta. Osim unarnog plusa i unarnog minusa koji mijenjaju predznak broja, u jeziku
C++ definirani su jo i unarni operatori za uveavanje (inkrementiranje) i umanjivanje
vrijednosti (dekrementiranje) broja. Operator ++ uveat e vrijednost varijable za 1,
dok e operator --) umanjiti vrijednost varijable za 1:
int i = 0;
++i;
cout << i << endl;
--i;
cout << i << endl;
//
//
//
//
uvea za 1
ispisuje 1
umanji za 1
ispisuje 0
Pritom valja uoiti razliku izmeu operatora kada je on napisan ispred varijable i
operatora kada je on napisan iza nje. U prvom sluaju (prefiks operator), vrijednost
varijable e se prvo uveati ili umanjiti, a potom e biti dohvaena njena vrijednost. U
drugom sluaju (postfiks operator) je obrnuto: prvo se dohvati vrijednost varijable, a tek
onda slijedi promjena. To najbolje doarava sljedei kd:
44
int i =
cout <<
cout <<
cout <<
cout <<
cout <<
1;
i << endl;
(++i) << endl;
i << endl;
(i++) << endl;
i << endl;
//
//
//
//
//
= 2.;
= 3.;
(a + b)
(a - b)
(a * b)
(a / b)
<<
<<
<<
<<
endl;
endl;
endl;
endl;
//
//
//
//
ispisuje
ispisuje
ispisuje
ispisuje
5.
-1.
6.
0.666667
Operator modulo kao rezultat vraa ostatak dijeljenja dva cijela broja:
int i = 6;
int j = 4;
cout << (i % j) << endl;
// ispisuje 2
On se vrlo esto koristi za ispitivanje djeljivosti cijelih brojeva: ako su brojevi djeljivi,
ostatak nakon dijeljenja e biti nula.
Za razliku od veine matematiki orijentiranih jezika, jezik C++ nema ugraeni
operator za potenciranje, ve programer mora sam pisati funkciju ili koristiti funkciju
pow(). iz standardne matematike biblioteke deklarirane u datoteci zaglavlja math.h.
Valja uoiti dva sutinska problema vezana uz aritmetike operatore. Prvi problem
vezan je uz pojavu numerikog preljeva, kada uslijed neke operacije rezultat nadmai
opseg koji dotini tip objekta pokriva. Drugi problem sadran je u pitanju kakvog e
tipa biti rezultat binarne operacije s dva broja razliitih tipova?.
Razmotrimo prvo pojavu brojanog preljeva. U donjem programu e prvo biti
ispisan broj 32767, to je najvei mogui broj tipa int. Izvoenjem naredbe u treem
retku, na zaslonu raunala e se umjesto oekivanih 32768 ispisati 32768, tj. najvei
negativni int!
int i = 32766;
cout << (++i) << endl;
cout << (++i) << endl;
Uzrok tome je preljev podataka do kojeg dolo zbog toga to oekivani rezultat vie ne
stane u 15 bita predvienih za int varijablu. Podaci koji su se prelili uli su u bit za
predznak (zato je rezultat negativan), a raspored preostalih 15 bitova daje broj koji
odgovara upravo onom to nam je raunalo ispisalo. Slino e se dogoditi oduzmete li
od najveeg negativnog broja neki broj. Ovo se moe ilustrirati brojevnom krunicom
45
-1
-2
zbrajanje
oduzimanje
-3
Ako postoji mogunost
pojave numerikog 3preljeva, tada deklarirajte
varijablu s veim opsegom u gornjem primjeru umjesto int uporabite
long int.
int
32766
-32767
-32768
32767
46
uzrokovat e ispis broja 1.5 na zaslonu, jer je od tipova int i float sloeniji tip
float. Cjelobrojnu varijablu i prevoditelj prije mnoenja pretvara u float (prema
pravilu u toki 3), tako da se provodi mnoenje dva broja s pominom tokom, pa je i
rezultat tipa float. Da smo umnoak prije ispisa pridruili nekoj cjelobrojnoj varijabli
int c = a * i;
cout << c << endl;
dobili bismo ispis samo cjelobrojnog dijela rezultata, tj. broj 1. Decimalni dio rezultata
se gubi prilikom pridruivanja umnoka cjelobrojnoj varijabli c.
Problem konverzije brojevnih tipova najjae je izraen kod dijeljenja cijelih
brojeva, to poetnicima (ali i nepaljivim C++ guruima) zna prouzroiti dosta
glavobolje. Pogledajmo sljedei jednostavni primjer:
int Brojnik = 1;
int Nazivnik = 4;
float Kvocijent = Brojnik / Nazivnik;
cout << Kvocijent << endl;
Brojeve 3 i 2 prevoditelj e shvatiti kao cijele, jer ne sadre decimalnu toku. Zato e
primijeniti cjelobrojni operator /, pa e rezultat toga dijeljenja biti cijeli broj 1.
47
Dovoljno bi bilo decimalnu toku staviti samo uz jedan od operanada prema pravilima
aritmetike konverzije i drugi bi operand bio sveden na float tip. Iako je kd ovako
dulji, izaziva manje nedoumica i stoga svakom poetniku preporuujemo da ne tedi na
decimalnim tokama.
Neupueni poetnik postavit e logino pitanje zato uope postoji razlika izmeu
cjelobrojnog dijeljenja i dijeljenja decimalnih brojeva. Za programera bi bilo
najjednostavnije kada bi se oba operanda bez obzira na tip, prije primjene operatora
svela na float (ili jo bolje, na double), na takve modificirane operande primijenio
eljeni operator, a dobiveni rezultat se pretvorio u zajedniki tip. U tom sluaju
programer uope ne bi trebao paziti kojeg su tipa operandi rezultat bi uvijek bio
korektan (osim ako nemate procesor s pogrekom, to i nije nemogue). Nedostatak
ovakvog pristupa jest njegova neefikasnost. Zamislimo da treba zbrojiti dva broja tipa
int! U gornjem programer-ne-treba-paziti pristupu, izvedbeni kd dobiven nakon
prevoenja trebao bi prvo jedan cjelobrojni operand pretvoriti u float. Budui da se
razliiti tipovi podataka pohranjuju u memoriju raunala na razliite naine, ta pretvorba
nije trivijalna, ve iziskuje odreeni broj strojnih instrukcija. Istu pretvorbu treba
ponoviti za drugi operand. Ve sada uoavamo dvije operacije konverzije tipa koje kod
izravnog zbrajanja cijelih brojeva ne postoje! tovie, samo zbrajanje se provodi na dva
float-a, koji u memoriji zauzimaju vei prostor od int-a. Takvo zbrajanje iziskivat e
puno vie strojnih instrukcija i strojnog vremena, ne samo zbog vee duljine float
brojeva u memoriji, ve i zbog sloenijeg naina na koji su oni pohranjeni. Za mali broj
operacija korisnik zasigurno ne bi osjetio razliku u izvoenju programa s izravno
primijenjenim operatorima i operatorima la programer-ne-treba-paziti. Meutim, u
sloenim programima s nekoliko tisua ili milijuna operacija, ta razlika moe biti
zamjetna, a esto i kritina. U krajnjoj liniji, shvatimo da prevoditelj poput vjernog psa
nastoji to bre ispuniti gospodarevu zapovijed, ne pazei da li e pritom protrati kroz
upravo ureen susjedin cvjetnjak ili zagaziti u svjee betoniran plonik.
Zadatak. to e ispisati sljedee naredbe:
int a = 10;
float b = 10.;
cout << a / 3 << endl;
cout << b / 3 << endl;
48
= a
c *
3.;
c *
a;
c /
/ 3;
b << endl;
b << endl;
3 << endl;
49
long c = a * b;
cout
cout
cout
cout
cout
<<
<<
<<
<<
<<
c << endl;
(a * b) << endl;
((float)a * b) << endl;
(long)(a * b) << endl;
(a * (long)b) << endl;
// long int
// unsigned int
sufiks
rezultirajui tip
cijeli
L, l
U, u
int
long int
unsigned int
decimalni
F, f
L, l
double
float
long double
50
// unsigned int
* r * 3.14159265359;
r * r * 3.14159265359;
4. * r * r * 3.14159265359;
4. / 3. * r * r * r * 3.14159265359;
51
kojom ste promijenili vrijednost varijable pi. Prevoditelj vas nee upozoriti (Opet taj
prokleti kompjutor!) i dobit ete da zemaljska kugla ima dva puta vei volumen nego
to ga je imala proli puta kada ste koristili isti program, ali bez inkriminirane naredbe.
Koristite li program za odreivanje putanje svemirskog broda, ovakva pogreka sigurno
e rezultirati odailjanjem broda u bespua svemirske zbiljnosti.
Da bismo izbjegli ovakve peripetije, na raspolaganju nam je kvalifikator const
kojim se prevoditelju daje na znanje da varijabla mora biti nepromjenjiva. Na svaki
pokuaj promjene vrijednosti takve varijable, prevoditelj e javiti pogreku:
const double pi = 3.14159265359;
pi = 2 * pi;
// sada je to pogreka!
Ona daje uputu prevoditelju da, prije nego to zapone prevoenje izvornog kda, sve
pojave prvog niza (PI) zamijeni drugim nizom znakova (3.14159265359). Stoga e
prevoditelj naredbe
double Volumen = 4. / 3. * r * r * r * PI;
PI = 2 * PI;
// pogreka
52
// pogreka
53
4.4.7. Pobrojenja
Naredbom
cout << HvalaBoguDanasJe << endl;
54
<<
<<
<<
<<
<<
//
//
//
//
//
ispisuje
ispisuje
ispisuje
ispisuje
ispisuje
0
3
4
5
7
Ako nam ne treba vie varijabli tog tipa, ime tipa iza rijei enum moe se izostaviti:
enum {NE = 0, DA} YesMyBabyNo, ShouldIStayOrShouldIGo;
enum {TRUE = 1, FALSE = 0};
// ispisuje 10
// ispisuje 9.42477
// pogreka
Programski jezik C dozvoljava da se pobrojanom tipu pridrui int vrijednost. Zato, radi
prenosivosti kda pisanog u programskom jeziku C, te kda pisanog u starijim varijantama
jezika C++, ANSI/ISO C++ standard dozvoljava da se u nekim implementacijama prevoditelja
omogui to pridruivanje, uz napomenu da to moe prouzroiti neeljene efekte.
55
Logiki podaci su takvi podaci koji mogu poprimiti samo dvije vrijednosti, na primjer:
da/ne, istina/la, dan/no. Jezik C++ za prikaz podataka logikog tipa ima ugraen tip
bool, koji moe poprimiti vrijednosti true (engl. true tono) ili false (engl. false
pogreno):
bool JeLiDanasNedjelja = true;
bool SunceSije = false;
Pri ispisu logikih tipova, te pri njihovom koritenju u aritmetikim izrazima, logiki
tipovi se pravilima cjelobrojne promocije (vidi prethodno poglavlje) pretvaraju u int:
true se pretvara u cjelobrojni 1, a false u 0. Isto tako, logikim varijablama se mogu
pridruivati aritmetiki tipovi: u tom sluaju se vrijednosti razliite od nule pretvaraju u
true, a nula se pretvara u false.
Gornja svojstva tipa bool omoguavaju da se za predstavljanje logikih podataka
koriste i cijeli brojevi. Broj 0 u tom sluaju odgovara logikoj neistini, a bilo koji broj
razliit od nule logikoj istini. Iako je za logiku istinu na raspolaganju vrlo iroki
raspon cijelih brojeva (zlobnici bi rekli da ima vie istina), ona se ipak najee zastupa
brojem 1. Ovakvo predstavljanje logikih
podataka je vrlo esto, budui da je tip bool
vrlo kasno uveden u standard jezika C++.
Tablica 4.6. Logiki operatori
Za logike podatke definirana su svega
!x
logika negacija
tri operatora: ! (logika negacija), &&
(logiki i), te || (logiki ili) (tablica 4.6).
x && y
logiki i
Logika negacija je unarni operator koji
x || y
logiki ili
mijenja logiku vrijednost varijable: istinu
pretvara u neistinu i obrnuto. Logiki i daje
kao rezultat istinu samo ako su oba operanda istinita; radi boljeg razumijevanja u tablici
4.7 dani su rezultati logikog i za sve mogue vrijednosti oba operanda. Logiki ili daje
istinu ako je bilo koji od operanada istinit (vidi tablicu 4.8).
Razmotrimo primjenu logikih operatora na sljedeem primjeru:
enum Logicki{NEISTINA, ISTINA, DRUGAISTINA = 124};
Logicki a = NEISTINA;
tono
(1)
tono
(1)
pogreno (0)
pogreno
(0)
Rije bool dolazi
od prezimena
utemeljitelja logike algebre.
tono
pogreno
tono
pogreno
engleskog
a .i. b
(1)
tono
(1)
(0)
pogreno (0)
(1)
pogreno (0)
(0)
pogreno
matematiara
Georgea(0)Boolea (18151864),
56
tono
tono
pogreno
pogreno
(1)
(1)
(0)
(0)
tono
pogreno
tono
pogreno
a .ili. b
(1)
(0)
(1)
(0)
tono
tono
tono
pogreno
(1)
(1)
(1)
(0)
Logicki b = ISTINA;
Logicki c = DRUGAISTINA;
cout << "a = " << a << ", b = " << b
<< ", c = " << c << endl;
cout << "Suprotno od a = " << !a << endl;
cout << "Suprotno od b = " << !b << endl;
cout << "Suprotno od c = " << !c << endl;
cout << "a .i. b = " << (a && b) << endl;
cout << "a .ili. c = " << (a || c) << endl;
Osim aritmetikih operacija, jezik C++ omoguava i usporedbe dva broja (vidi tablicu
4.9). Kao rezultat usporedbe dobiva se tip bool: ako je uvjet usporedbe zadovoljen,
rezultat je true, a ako nije rezultat je false. Tako e se izvoenjem kda
cout
cout
cout
cout
<<
<<
<<
<<
(5
(5
(5
(5
//
//
//
//
je
je
je
je
li
li
li
li
5
5
5
5
vee od 4?
vee ili jednako 4?
manje od 4?
manje ili jednako 4?
57
< y
<= y
> y
>= y
== y
!= y
manje od
manje ili jednako
vee od
vee ili jednako
jednako
razliito
// je li 5 jednako 4?
// je li 5 razliito od 4?
to upozorenje e izostati, jer ovdje poredbeni operator moe imati smisla. Za poetnike
jedno od nezgodnih svojstava jezika C++ jest da trpi i besmislene izraze, poput:
i + 5;
ili
58
i == 5 == 6;
Znakovne konstante tipa char piu se uglavnom kao samo jedan znak unutar
jednostrukih znakova navodnika:
char SlovoA = 'a';
cout << 'b' << endl;
novi redak
horizontalni tabulator
vertikalni tabulator
pomak za mjesto unazad (backspace)
povrat na poetak retka (carriage return)
nova stranica (form feed)
zvuni signal (alert)
kosa crta ulijevo (backslash)
upitnik
jednostruki navodnik
dvostruki navodnik
zavretak znakovnog niza
znak iji je kd zadan oktalno s 1, 2 ili 3 znamenke
znak iji je kd zadan heksadekadski
59
cout
cout
cout
cout
<<
<<
<<
<<
endl;
endl;
endl;
<< endl; // usporedba jednostrukog
// i dvostrukog navodnika
// usporedba broja 32 i
// praznine
// ASCII kd 104
// ispisuje 105
b = a + 1;
cout << b << endl;
// ispisuje 'i'
Kod prvog ispisa znakovna varijabla a (koja ima vrijednost slova h) se, zbog operacije
sa cijelim brojem 1, pretvara se u ASCII kd za slovo h, tj. u broj 104, dodaje joj se 1, te
ispisuje broj 105. Druga naredba za ispis daje slovo i, jer se broj 105 pridruuje
znakovnoj varijabli b, te se 105 ispisuje kao ASCII znak, a ne kao cijeli broj.
Zadatak. Razmislite i provjerite to e se ispisati izvoenjem sljedeeg kda:
char a = 'a';
char b = a;
int asciiKod = a;
cout << a << endl;
cout << b << endl;
60
Za pohranjivanje znakova koji ne stanu u tip char moe se koristiti tip wchar_t.
Duljina tipa wchar_t nije definirana standardom; na prevoditelju koji smo koristili pri
pisanju knjige ona je iznosila dva bajta, tako da smo u jedan znak tipa wchar_t mogli
smjestiti dva znaka:
wchar_t podebljiZnak = L'ab';
Slovo L ispred pridruene eksplicitno odreuje da je znak koji slijedi tipa wchar_t.
4.4.11. Bitovni operatori
B6
B5
B4
B3
B2
B1
~i
i & j
i | j
i ^ j
i << n
i >> n
komplement
binarni i
binarni ili
iskljuivi ili
pomakni ulijevo
pomakni udesno
B0
1
~00100101 = 11011010
B7
B6
B5
B4
B3
B2
B1
B0
// 00100101 u heksadec.prikazu
// pridrui bitovni komplement
61
B7
B6
B5
B4
B3
B2
B1
B0
1
Slika 4.5. Bitovni operator i
Bitovni i se najee koristi kada se ele pojedini bitovi broja postaviti u 0 (obrisati) ili
ako se ele izdvojiti samo neki bitovi broja. Drugi operand je pritom maska koja
odreuje koji e se bitovi postaviti u nulu, odnosno koji e se bitovi izluiti. Za
postavljanje odreenih bitova u nulu svi bitovi maske na tim mjestima moraju biti
jednaki 0. Ako u gornjem primjeru uzmemo da je maska donji operand, tj. 000011112,
tada moemo rei da smo kao rezultat dobili gornji broj u kojem su etiri najznaajnija
(tj. lijeva) bita obrisana. etiri preostala bita su ostala nepromijenjena, jer maska na tim
mjestima ima jedinice, iz ega odmah slijedi zakljuak da u maski za izluivanje svi
62
bitovi koje elimo izluiti iz prvog broja moraju biti jednaki 1. Ako u gornjem primjeru
uzmemo da je maska donji operand, kao rezultat smo dobili stanja etiri najnia bita
gornjeg operanda. Zbog svojstva komutativnosti moemo rei i da je prvi broj maska
pomou koje briemo, odnosno vadimo odreene bitove drugog broja. Izvorni kd za
gornju operaciju mogli bismo napisati kao:
int a = 0x0025;
int maska = 0x000f;
cout << hex << (a & maska) << endl;
B7
B6
B5
B4
B3
B2
B1
B0
63
B7
B6
B5
B4
B3
B2
B1
B0
int a = 0x0025;
int maska = 0x000f;
cout << hex << (a ^ maska) << endl;
// ispisuje 0x2a
Ova injenica esto se koristi za jednostavnu zatitu podataka ili kda od nepoeljnog
itanja, odnosno koritenja: primjenom iskljuivog ili na podatke pomou tajne maske
podaci se ifriraju. Ponovnom primjenom iskljuivog ili s istom maskom podaci se
vraaju u izvorni oblik.
Operatori << (pomak ulijevo, engl. shift left) i >> (pomak udesno, engl. shift right)
pomiu sve bitove lijevog operanda za broj mjesta odreen desnim operandom, kako je
prikazano na slici 4.8.
Pri pomaku ulijevo najznaajniji (tj. krajnji lijevi) bitovi se gube, dok najmanje
znaajni bitovi poprimaju vrijednost 0. Slino, pri pomaku udesno, gube se najmanje
znaajni bitovi, a najznaajniji bitovi poprimaju vrijednost 0. Uoimo da pomak bitova
u cijelim brojevima za jedno mjesto ulijevo odgovara mnoenju broja s 2 situacija je
potpuno identina dodavanju nula iza dekadskog broja, samo to je kod dekadskog broja
baza 10, a u binarnoj notaciji je baza 2. Slino, pomak bitova za jedno mjesto udesno
odgovara dijeljenju cijelog broja s 2:
int a = 20;
cout << (a << 1) << endl;
cout << (a >> 2) << endl;
// pomak za 1
//
ispisuje
// pomak za 2
//
ispisuje
bit ulijevo 40
bita udesno 5
64
B7
B6
0
B7
B6
B5
B4
B3
B2
B1
B5
B4
B3
B2
B1
B4
B3
B2
B1
B0
1
B6
0
B6
B0
B7
B7
B5
B5
B4
B3
B2
B1
B0
B0
0
B7
B6
B5
B4
B3
B2
B1
B0
Meutim, pri koritenju pomaka valja biti krajnje oprezan, jer se kod cjelobrojnih
konstanti s predznakom najvii bit koristi za predznak. Ako je broj negativan, tada se
pomakom ulijevo bit predznaka e se izgubiti, a na njegovo mjesto e doi najvia
binarna znamenka. Kod pomaka udesno bit predznaka ulazi na mjesto najvie
znamenke. Istina, neki prevoditelji vode rauna o bitu predznaka, ali se zbog
prenosivosti kda ne treba previe oslanjati na to.
Mogunost izravnog pristupa pojedinim bitovima esto se koristi za stvaranje
skupova logikih varijabli pohranjenih u jednom cijelom broju, pri emu pojedini bitovi
tog broja predstavljaju zasebne logike varijable. Takvim pristupom se tedi na
memoriji, jer umjesto da svaka logika varijabla zauzima zasebne bajtove, u jednom je
bajtu pohranjeno osam logikih varijabli. Ilustrirajmo to primjerom u kojem se
definiraju parametri za prijenos podataka preko serijskog prikljuka na raunalu. Radi
lakeg praenja, izvorni kd emo ralaniti na segmente, koje emo analizirati zasebno.
Za serijsku komunikaciju treba, osim brzine prijenosa izraene u baudima,
definirati broj bitova po podatku (7 ili 8), broj stop-bitova (1 ili 2), te paritet (parni
paritet, neparni paritet ili bez pariteta). Uzmimo da na raspolaganju imamo osam brzina
prijenosa: 110, 150, 300, 600, 1200, 2400, 4800 i 9600 bauda. Svakoj toj brzini pridruit
emo po jedan broj u nizu od 0 do 7:
enum {Baud110 = 0, Baud150, Baud300, Baud600,
Baud1200, Baud2400, Baud4800, Baud9600};
65
Broj stop-bitova pohranit emo u B4 i B5. Ako se trai jedan stop-bit, tada e B4 biti 1,
a za dva stop-bita B5 je jednak 1:
enum {StopB1 = 0x10, StopB2 = 0x20};
Konano, podatke o paritetu emo pohraniti u dva najvia bita (B6 - B7):
enum {ParitetNjet = 0x00, ParitetNeparni = 0x40,
ParitetParni = 0x80};
0
B7
0
B7
0
B7
B6
B5
0
B6
0
B5
0
B6
0
B5
0
B6
1
B5
bez64748
pariteta
B7
B6
B4
0
B4
0
B4
0
B4
B3
0
B3
1
B3
0
B3
B2
1
B2
0
B2
0
B2
B1
B0
0
B1
0
B0
0
B1
Bitova8 |
B0
0
B1
Baud1200 |
StopB2 |
B0
ParitetNjet
8 bitova
podataka
678
B5
B4
B3
B2
B1
B0
14243
1442443
2 stop-bita
1200 bauda
66
B7
0
B7
0
B7
B6
0
B6
0
B6
B5
0
B5
1
B5
B4
B3
0
B4
B3
0
B4
B2
B2
1
B3
B1
B1
1
B2
B0
1
B0
0
B1
00000111 &
SerCom
B0
00000100
// dobiva se 4
Prenosi li se osam bitova podataka provjerit emo sljedeom bitovnom ili operacijom:
int JeLiOsamBita = SerCom & 0x08;
// dobiva se 8
budui da 816 ima u binarnom prikazu 1 samo na mjestu B3. Ako je bit B3 postavljen,
tj. ako ima vrijednost 1, varijabla JeLiOsamBita e poprimiti vrijednost dekadsko (i
heksadekadsko!) 8; u protivnom e biti 0.
Analogno, za izluivanje broja stop-bitova posluit emo se maskom 3016 koja na
mjestima B4 i B5 ima 1:
int BrojStopBita = SerCom & 0x30;
// dobiva se 32
U naem primjeru kao rezultat emo dobiti 32. Ako rezultantu bitovnu strukturu
pomaknemo udesno za 4 mjesta tako da B4 i B5 dou na B0 odnosno B1 (slika 4.11):
BrojStopBita = BrojStopBita >> 4;
1
B7
B6
B5
144424443
B4
B3
B2
B1
B0
00100000 >> 4
644474448
B7
B6
B5
B4
B3
B2
B1
B0
00000010
67
ekvivalentna je naredbi
a = a + 5;
+=
-=
*=
/=
%=
>>=
<<=
^=
&=
|=
5;
<< n
20;
<< n
-2;
<< n
3;
<< n
<< endl;
<< endl;
<< endl;
<< endl;
//
//
//
//
//
//
//
//
isto kao: n =
ispisuje: 15
isto kao: n =
ispisuje: -5
isto kao: n =
ispisuje: 10
isto kao: n =
ispisuje 1
n + 5
n - 20
n * (-2)
n % 3
68
a -= b - c;
// to je zapravo a = a - (b - c)
ve kao
a -= b + c;
// a = a - (b + c)
osnovna
alternativa
osnovna
alternativa
osnovna
{
}
[
]
#
##
<%
%>
<:
:>
%:
%:%:
&&
||
!
&
|
^
and
or
not
bitand
bitor
xor
~
!=
&=
|=
^=
alternativa
compl
not_eq
and_eq
or_eq
xor_eq
69
trigraf
zamjena za
trigraf
zamjena za
trigraf
zamjena za
??=
??/
??'
#
\
^
??(
??)
??!
[
]
|
??<
??>
??-
{
}
~
namjenu je u jeziku C++ dozvoljena i upotreba nizova od tri znaka (engl. trigraph),
naslijeenih iz programskog jezika C (tablica 4.14). Sreom, operacijski sustavi koji za
predstavljanje znakova koriste neki 8-bitni standard (npr. kodna stranica 852 pod DOSom ili kodna stranica 1250 pod Windows-ima) omoguavaju istovremeno koritenje
nacionalnih znakova i specijalnih znakova neophodnih za pisanje programa u jeziku
C++, tako da veini korisnika tablice 4.13 i 4.14 nee nikada zatrebati. Ipak za
ilustraciju pogledajmo kako bi izgledao neki program napisan pomou alternativnih
oznaka (itatelju preputamo da deifrira kd):
%:include <iostream.h>
int main() <%
bool prva = true;
bool druga = false;
bool treca = prva and druga;
cout << treca << endl;
cout << not treca << endl;
cout << prva or druga << endl;
return 1;
%>
70
Budui da deklaracija typedef ne uvodi novi tip podataka, niti mijenja standardna
pravila pretvorbe podataka, sljedea pridruivanja su dozvoljena i nee prouzroiti
nikakve promjene u tonosti:
float a = pi;
typedef float pliva;
pliva = pi;
promjene:
b;
h;
y;
// nakon promjene:
double a, b;
float k, h;
// ...
double x, y;
Kada je broj objekata mali to i nije teko, ali za veliki broj deklaracija te zamjene mogu
biti naporne i podlone pogrekama. Posao emo si bitno olakati, ako u gornjem
primjeru dodamo deklaraciju typedef za podatke iji tip emo povremeno mijenjati:
typedef float brojevi;
brojevi a, b;
float k, h;
// ...
brojevi x, y;
Sada e svi podaci tipa brojevi biti prevedeni kao double podaci, bez potrebe
daljnjih izmjena u izvornom kdu.
71
Valja naglasiti da standard jezika C++ ne definira veliinu bajta, osim u smislu rezultata
to ga daje sizeof operator; tako je sizeof(char) jednak 1. Naime, duljina bajta
(broj bitova koji ine bajt) ovisi o arhitekturi raunala. Mi emo u knjizi uglavnom
podrazumijevati da bajt sadri 8 bitova, to je najei sluaj u praksi.
Operand sizeof operatora moe biti identifikator tipa (npr. int, float, char) ili
konkretni objekt koji je ve deklariran:
float f;
int i;
cout << sizeof(f) << endl;
cout << sizeof(i) << endl;
// duljina float
// duljina int
// duljina float
// duljina int
72
Rezultat operatora sizeof je tipa size_t, cjelobrojni tip bez predznaka koji ovisi
o implementaciji prevoditelja, definiran u zaglavlju stddef.h (o zaglavljima e biti
rijei u kasnijim poglavljima).
Operator sizeof se uglavnom koristi kod dinamikog alociranja memorijskog
prostora kada treba izraunati koliko memorije treba osigurati za neki objekt, o emu e
biti govora u kasnije u knjizi.
73
operator
znaenje
pridruivanje
::
::
globalno podruje
podruje klase
s desna na lijevo
s lijeva na desno
-> .
[]
()
++
--
izbor lana
indeksiranje
poziv funkcije
uveaj nakon
umanji nakon
s lijeva na desno
s lijeva na desno
s lijeva na desno
s lijeva na desno
s lijeva na desno
sizeof
++
-* & + - ! ~
new
delete
typeid
()
veliina objekta
uveaj prije
umanji prije
unarni operatori
stvori objekt
izbrii objekt
tip objekta
dodjela tipa
pokazivai na lan
mnoenja
zbrajanja
bitovni pomaci
poredbeni operatori
operatori jednakosti
bitovni i
bitovno iskljuivo ili
bitovni ili
logiki i
logiki ili
uvjetni izraz
pridruivanja
s desna na lijevo
s desna na lijevo
s desna na lijevo
s desna na lijevo
s desna na lijevo
s desna na lijevo
s desna na lijevo
s desna na lijevo
s lijeva na desno
s lijeva na desno
s lijeva na desno
s lijeva na desno
s lijeva na desno
s lijeva na desno
s lijeva na desno
s lijeva na desno
s lijeva na desno
s lijeva na desno
s lijeva na desno
s desna na lijevo
s desna na lijevo
razdvajanje
s lijeva na desno
->* .*
* / %
+ << >>
< > <= >=
== !=
&
^
|
&&
||
?:
= *= /= += -= &=
^= |= %= >>= <<=
,
prioritet
najvii
najnii
74
za to napie naredbu
c = a---b;
to e stvarno ta naredba uraditi pouzdano emo saznati ako ispiemo vrijednosti svih
triju varijabli nakon naredbe:
int main() {
int a = 2;
int b = 5;
int c;
c = a---b;
cout << "a = " << a << ", b = " << b
<< ", c = " << c << endl;
return 1;
}
tj. uzeo je vrijednost varijable a, od nje oduzeo broj b te rezultat pridruio varijabli c, a
varijablu a je umanjio (ali tek nakon to je upotrijebio njenu vrijednost).
Kao i u matematici, okruglim zagradama se moe zaobii ugraena hijerarhija
operatora, budui da one imaju vii prioritet od svih operatora. Tako e se u kdu
d = a * (b + c);
75
77
U veini realnih problema tok programa nije pravocrtan i jedinstven pri svakom
izvoenju. Redovito postoji potreba da se pojedini odsjeci programa ponavljaju
programski odsjeci koji se ponavljaju nazivaju se petljama (engl. loops). Ponavljanje
moe biti unaprijed odreeni broj puta, primjerice elimo li izraunati umnoak svih
cijelih brojeva od 1 do 100. Meutim, broj ponavljanja moe ovisiti i o rezultatu neke
operacije. Kao primjer za to uzmimo uitavanje podataka iz neke datoteke datoteka se
uitava podatak po podataka, sve dok se ne uita znak koji oznaava kraj datoteke (endof-file). Duljina datoteke pritom moe varirati za pojedina izvoenja programa.
Gotovo uvijek se javlja potreba za grananjem toka, tako da se ovisno o
postavljenom uvjetu u jednom sluaju izvodi jedan dio programa, a u drugom sluaju
drugi dio. Na primjer, elimo izraunati realne korijene kvadratne jednadbe. Prvo emo
izraunati diskriminantu ako je diskriminanta vea od nule, izraunat emo oba
korijena, ako je jednaka nuli izraunat emo jedini korijen, a ako je negativna ispisat
emo poruku da jednadba nema realnih korijena. Grananja toka i ponavljanja dijelova
kda omoguavaju posebne naredbe za kontrolu toka.
78
int a = 1;
cout << a << endl;
}
return 0;
proi uredno, ali ako naredbu za ispis lokalne varijable a prebacimo izvan bloka
#include <iostream.h>
int main() {
{
int a = 1;
}
cout << a << endl;
return 0;
}
// ispisuje 1
// ispisuje 5
Prva naredba za ispis dohvatit e lokalnu varijablu a = 1, budui da ona ima prednost
pred istoimenom varijablom a = 5 koja je deklarirana ispred bloka. Po izlasku iz bloka,
lokalna varijabla a se gubi te je opet dostupna samo varijabla a = 5. Naravno da bi
ponovna deklaracija istoimene varijable bilo u vanjskom, bilo u unutarnjem bloku
rezultirala pogrekom tijekom prevoenja. Podrujem dosega varijable pozabavit emo
se detaljnije u kasnijim poglavljima.
Ako se blok u naredbama za kontrolu toka sastoji samo od jedne naredbe, tada se
vitiaste zagrade mogu i izostaviti.
79
Ako je vrijednost izraza iza rijei if logika istina (tj. bilo koji broj razliit od nule),
izvodi se blok naredbi koje slijede iza izraza. U protivnom se taj blok preskae i
izvoenje nastavlja od prve naredbe iza bloka. Na primjer:
if (a < 0) {
cout << "Broj a je negativan!" << endl;
}
U sluaju da blok sadri samo jednu naredbu, vitiaste zagrade koje omeuju blok mogu
se i izostaviti, pa smo gornji primjer mogli pisati i kao:
if (a < 0)
cout << "Broj a je negativan!" << endl;
ili
if (a < 0) cout << "Broj a je negativan!" << endl;
Druga naredba ispod if uvjeta izvest e se i za pozitivne brojeve, jer vie nije u bloku,
pa e se za pozitivne brojeve ispisati pogrena apsolutna vrijednost! Inae, u primjerima
emo izbjegavati pisanje vitiastih zagrada gdje god je to mogue radi utede na
prostoru.
80
Kod ovog oblika, ako izraz u if uvjetu daje rezultat razliit od nule, izvest e se prvi
blok naredbi. Po zavretku bloka izvoenje programa nastavlja se od prve naredbe iza
drugog bloka. Ako izraz daje kao rezultat nulu, preskae se prvi blok, a izvodi samo
drugi blok naredbi, nakon ega se nastavlja izvoenje naredbi koje slijede. Evo
jednostavnog primjera u kojem se raunaju presjecita pravca s koordinatnim osima.
Pravac je zadan jednadbom a x + b y + c = 0.
#include <iostream.h>
int main() {
float a, b, c;
// koeficijenti pravca
cin >> a >> b >> c;
// uitaj koeficijente
cout << "Koeficijenti: " << a << ","
<< b << "," << c << endl; // ispii ih
cout << "Presjecite s apscisom: ";
if (a != 0)
cout << -c / a << ", ";
else
cout << "nema, ";
// pravac je horizontalan
cout << "presjecite s ordinatom: ";
if (b != 0)
cout << -c / b << endl;
else
cout << "nema" << endl; // pravac je vertikalan
return 0;
}
81
else
// zadnji_blok_naredbi
// diskriminanta
82
Za logiki izraz u prvom if uvjetu postavili smo samo vrijednost varijable a ako e
ona biti razliita od nule, uvjet e biti zadovoljen i izvest e se naredbe u prvom if
bloku. Taj blok sastoji se od niza if-else blokova identinih onima iz prethodnog
primjera. Ako poetni uvjet nije zadovoljen, tj. ako varijabla a ima vrijednost 0,
preskae se cijeli prvi blok i ispisuje poruka da je jednadba linearna. Uoimo u
gornjem primjeru dodatno uvlaenje ugnijeenih blokova.
Pri definiranju logikog izraza u naredbama za kontrolu toka poetnik treba biti
oprezan. Na primjer, elimo li da se dio programa izvodi samo za odreeni opseg
vrijednosti varijable b, naredba
if (-10 < b < 0) //...
nee raditi onako kako bi se prema ustaljenim matematikim pravilima oekivalo. Ovaj
logiki izraz u biti se sastoji od dvije usporedbe: prvo se ispituje je li 10 manji od b, a
potom se rezultat te usporedbe usporeuje s 0, tj.
if ((-10 < b) < 0) //...
Kako rezultat prve usporedbe moe biti samo true ili false, odnosno 1 ili 0, druga
usporedba dat e uvijek logiku neistinu. Da bi program poprimio eljeni tok,
usporedbe moramo razdvojiti i logiki izraz formulirati ovako: ako je 10 manji od b i
ako je b manji od 0:
if (-10 < b && b < 0) //...
83
Druga nezgoda koja se moe dogoditi jest da se umjesto operatora za usporedbu ==, u
logikom izrazu napie operator pridruivanja =. Na primjer:
if (k = 0)
k++;
else
k = 0;
// pridruivanje, a ne usporedba!!!
Ako izraz uvjet daje logiku istinu, izraunava se izraz1, a u protivnom izraz2. U
primjeru
x = (x < 0) ? -x : x;
// x = abs(x)
84
85
#include <iostream.h>
int main() {
cout << "Upii datum u formatu DD MM GGGG:";
int dan, mjesec;
long int godina;
cin >> dan >> mjesec >> godina;
long datum;
if (mjesec < 3) {
datum = 365 * godina + dan + 31 * (mjesec - 1)
+ (godina - 1) / 4
- 3 * ((godina - 1) / 100 + 1) / 4;
}
else {
// uoimo operator dodjele tipa (int):
datum = 365 * godina + dan + 31 * (mjesec - 1)
- (int)(0.4 * mjesec + 2.3) + godina / 4
- 3 * (godina / 100 + 1) / 4;
}
cout << dan << "." << mjesec << "." << godina
<< ". pada u ";
switch (datum % 7) {
case 0:
cout << "subotu." << endl;
break;
case 1:
cout << "nedjelju." << endl;
break;
case 2:
cout << "ponedjeljak." << endl;
break;
case 3:
cout << "utorak." << endl;
break;
case 4:
cout << "srijedu." << endl;
break;
case 5:
cout << "etvrtak." << endl;
break;
default:
cout << "petak." << endl;
}
return 0;
}
86
mnoi s 365. U protivnom bi vrlo vjerojatno dolo do brojanog preljeva, osim ako
bismo se ograniili na datume iz ivota Kristovih suvremenika. Uoimo u gornjem kdu
operator dodjele tipa (int)
/*...*/
/*...*/
Pogledajmo kd:
87
#include <iostream.h>
int main() {
int n;
cout << "Upii prirodni broj: ";
cin >> n;
// manji od 13!
Prije ulaska u petlju trebamo definirati poetnu vrijednost varijable fjel u koju emo
gomilati umnoke. Na ulasku u petlju deklariramo broja petlje, varijablu i tipa int te
joj pridruujemo poetnu vrijednost 2 (poetni_izraz: int i = 2). Unutar same
petlje mnoimo fjel s brojaem petlje, a na kraju tijela petlje uveavamo broja
(izraz_prirasta: i++). Petlju ponavljamo sve dok je broja manji ili jednak
unesenom broju (uvjet_izvoenja: i <= n). Pri testiranju programa pazite da
uneseni broj ne smije biti vei od 12, jer e inae doi do brojanog preljeva varijable
fjel.
Zanimljivo je uoiti to e se dogoditi ako za n unesemo brojeve manje od 2. Ve
pri prvom ulasku u petlju nee biti zadovoljen uvjet ponavljanja petlje te e odmah biti
preskoeno tijelo petlje i ona se nee izvesti niti jednom! Ovo nam odgovara, jer je 1! =
1 i (po definiciji) 0! = 1. Naravno da smo petlju mogli napisati i na sljedei nain:
for (int i = n; i > 1; i--)
fjel *= i;
Rezultat bi bio isti. Iskusni programer bi gornji program mogao napisati jo saetije
(vidi poglavlje 5.11), no u ovom trenutku to nam nije bitno.
Moe se dogoditi da je uvjet izvoenja uvijek zadovoljen, pa e se petlja izvesti
neogranieni broj puta. Program e uletjeti u slijepu ulicu iz koje nema izlaska, osim
pomou tipki Power ili Reset na kuitu vaeg raunala. Na primjer:
// ovaj primjer pokreete na vlastitu odgovornost!
cout << "Beskonana petlja";
for (int i = 5; i > 1; )
cout << "a";
Varijabla i je uvijek vea od 1, tako da ako se i usudite pokrenuti ovaj program, ekran
e vam vrlo brzo biti preplavljen slovom a. Iako naizgled banalne, ovakve pogreke
mogu poetniku zadati velike glavobolje.
88
Naredba unutar petlje potpuno potire izraz prirasta, te varijabla i alternira izmeu 1 i
0.
Poetni izraz i izraz prirasta mogu se sastojati i od vie izraza odvojenih operatorom
nabrajanja , (zarez) . To nam omoguava da program za raunanje faktorijela napiemo
i (neto) krae:
#include <iostream.h>
int main() {
int n;
cout << "Upii prirodni broj: ";
cin >> n;
long fjel;
int i;
for (i = 2, fjel = 1; i <= n; fjel *= i, i++) ;
cout << n << "! = " << fjel;
return 0;
}
89
je od bloka ostala prazna naredba, tj. sam znak ;. Njega ne smijemo izostaviti, jer bi
inae prevoditelj prvu sljedeu naredbu (a to je naredba za ispis rezultata) obuhvatio u
petlju. Sada je i deklaracija brojaa prebaena ispred for naredbe, jer poetni izraz ne
trpi viestruke deklaracije. Da smo for naredbu napisali kao:
for (int i = 2, long fjel = 1; i <= n; fjel *= i, i++) ;
2
4
6
8
10
3
6
9
12
15
4
8
12
16
20
5
10
15
20
25
6
12
18
24
30
7
14
21
28
35
8
16
24
32
40
9
18
27
36
45
10
20
30
40
50
Tijekom razvoja standarda to pravilo se mijenjalo. Tako e neki stariji prevoditelji ostaviti
varijablu i ivom i iza for petlje.
90
6
7
8
9
10
12
14
16
18
20
18
21
24
27
30
24
28
32
36
40
30
35
40
45
50
36
42
48
54
60
42
49
56
63
70
48
56
64
72
80
54
63
72
81
90
60
70
80
90
100
Kako se gornji program izvodi? Pri ulasku u vanjsku petlju, inicijalizira se broja redaka
na vrijednost 1 te se s njom ulazi u unutarnju petlju. U unutarnjoj petlji se broja stupaca
mijenja od 1 do 10 i za svaku pojedinu vrijednost izraunava se njegov umnoak s
brojaem redaka (potonji je cijelo to vrijeme redak = 1). Po zavrenoj unutarnjoj petlji
ispisuje se znak za novi redak, ime zavrava blok naredbi vanjske petlje. Slijedi prirast
brojaa redaka (redak = 2) i povrat na poetak vanjske petlje. Unutarnja petlja se
ponavlja od stupac = 1 do stupac = 10 itd. Kao to vidimo, za svaku vrijednost
brojaa vanjske petlje izvodi se cjelokupna unutarnja petlja.
Da bismo dobili ispis brojeva u pravilnim stupcima, u gornjem primjeru smo rabili
operator za rukovanje (manipulator) setw(). Argument tog manipulatora (tj. cijeli broj
u zagradi) odreuje koliki e se najmanji prostor predvidjeti za ispis podatka koji slijedi
u izlaznom toku. Ako je podatak krai od predvienog prostora, preostala mjesta bit e
popunjena prazninama. Manipulator setw() definiran je u datoteci zaglavlja
iomanip.h.
sljedei:
1. Izraunava se logiki izraz uvjet_izvoenja.
2. Ako je rezultat jednak logikoj neistini, preskae se blok_naredbi i program se
nastavlja od prve naredbe iz bloka.
3. Ako je uvjet_izvoenja jednak logikoj istini izvodi se blok_naredbi. Potom
se program vraa na while naredbu i izvodi od toke 1.
Konkretnu primjenu while bloka dat emo programom kojim se ispisuje sadraj
datoteke s brojevima. U donjem kdu je to datoteka brojevi.dat, ali uz promjene
odgovarajueg imena, to moe biti i neka druga datoteka.
#include <iostream.h>
#include <fstream.h>
91
int main() {
ifstream ulazniTok("brojevi.dat");
cout << "Sadraj datoteke:" << endl << endl;
float broj;
while ((ulazniTok >> broj) != 0)
cout << broj << endl;
return 0;
}
uitava podatak. Ako je uitavanje bilo uspjeno, ulTok e biti razliit od nule
(neovisno o vrijednosti uitanog broja), te se izvodi blok naredbi u while-petlji
ispisuje se broj na izlaznom toku cout. Nakon to se izvede naredba iz bloka, izvoenje
se vraa na ispitivanje uvjeta petlje. Ako ulTok poprimi vrijednost 0, to znai da nema
vie brojeva u datoteci, petlja se prekida. Blok petlje se preskae, te se izvoenje
nastavlja prvom naredbom iza bloka.
Primijetimo zgodno svojstvo petlje while: ako je datoteka prazna, ulazni tok e
odmah poprimiti vrijednost 0 te e uvjet odmah prilikom prvog testiranja biti
neispunjen. Blok petlje se tada nee niti jednom izvesti, to u naem sluaju i trebamo.
Zadatak. Dopunite gornji program tako da nakon sadraja datoteke ispie i broj
iitanih brojeva i njihovu srednju vrijednost.
92
Trebalo je samo poetni izraz (int i = 2) izluiti ispred while naredbe, a izraz prirasta
(i++) prebaciti iza bloka naredbi.
Zadatak. Program za ispis datoteke napiite tako da umjesto while-bloka upotrijebite
for-blok naredbi.
Koji e se pristup koristiti (for-blok ili while-blok) prvenstveno ovisi o
sklonostima programera. Ipak, zbog preglednosti i razumljivosti kda for-blok je
preporuljivo koristiti kada se broj ponavljanja petlje kontrolira cjelobrojnim brojaem.
U protivnom, kada je uvjet ponavljanja odreen nekim logikim uvjetom, praktinije je
koristiti while naredbu.
93
x 2i +1
x3
x5
x7
x9
Petlju za raunanje lanova reda treba ponavljati sve dok je apsolutna vrijednost
zadnjeg izraunatog lana reda vea od nekog zadanog broja (npr. 107).
94
izvoenje naeg program za ispis brojeva iz datoteke brojevi.dat (na str. 90) prekine
kada se uita 0. Tada emo while-petlju modificirati na sljedei nain:
// ...
while ((ulazniTok >> broj) != 0) {
if (broj == 0)
break;
// prekida petlju uitavanja
cout << broj << endl;
}
// ...
Nailaskom na znak iji je kd manji od kda za prazninu i nije jednak znaku za novi
redak, izvodi se naredba continue preskau se sve naredbe do kraja petlje (u ovom
sluaju je to naredba za ispis znaka), ali se ponavljanje petlje dalje nastavlja kao da se
nita nije dogodilo.
Ako je vie petlji ugnijeeno jedna unutar druge, naredba break ili naredba
continue prouzroit e prekid ponavljanja, odnosno nastavak okolne petlje u kojoj se
naredba nalazi.
Valja izbjegavati esto koritenje break i continue naredbi u petljama, jer
one naruavaju strukturiranost programa.
Koristite ih samo za izvanredna stanja, kada ne postoji drugi prikladan nain da se
izvoenje naredbi u petlji prekine. Naredba continue redovito se moe izbjei ifblokom.
95
goto ime_oznake ;
ime_oznake je simboliki naziv koji se mora nalaziti ispred naredbe na koju se eli
prenijeti kontrola, odvojen znakom : (dvotoka). Na primjer
if (a < 0)
goto negativniBroj;
//...
negativniBroj: //naredba
Naredba na koju se eli skoiti moe se nalaziti bilo gdje (ispred ili iza naredbe goto)
unutar iste funkcije. ime_oznake mora biti jedinstveno unutar funkcije, ali moe biti
jednako imenu nekog objekta ili funkcije. Ime oznake jest identifikator, pa vrijede
pravila navedena u poglavlju 1.
U pravilno strukturiranom programu naredba goto uope nije potrebna, te ju
velika veina programera uope ne koristi.
Zadnju naredbu za kontrolu toka koju emo ovdje spomenuti upoznali smo na samom
poetku knjige. To je naredba return kojom se prekida izvoenje funkcija te emo se
njome pozabaviti u poglavlju posveenom funkcijama.
96
97
ili
Koji ete pristup preuzeti ovisi iskljuivo o vaoj odluci, ali ga onda svakako
koristite dosljedno.
a=b*c-d/(2.31+e)+e/8.21e-12*2.43;
s potpuno istim efektom. Ovakvo saimanje kda ne samo da zauzima manje prostora u
editoru i izvornom kdu, ve esto olakava prevoditelju generiranje kraeg i breg
98
Iako e oba kda raditi potpuno jednako, sutinski gledano je prvi pristup ispravniji (i
itljiviji) izraz u if ispitivanju po definiciji mora davati logiki rezultat (tipa bool). U
drugom (saetom) primjeru se, sukladno ugraenim pravilima konverzije, aritmetiki tip
pretvara u tip bool (tako da se brojevi razliiti od nule pretvaraju u true, a nula se
pretvara u false), pa je konani ishod isti.
Slina situacija je prilikom ispitivanja da li je neka varijabla jednaka nuli. U
dosljednom pisanju ispitivanje bismo pisali kao:
if (a == 0) {
// ...
}
99
I u ovom sluaju e oba ispitivanja poluiti isti izvedbeni kd, ali e neiskusnom
programeru iitavanje drugog kda biti zasigurno tee. tovie, neki prevoditelji e
prilikom prevoenja ovako saetih ispitivanja ispisati upozorenja.
Mogunost saimanja izvornog kda (s eventualnim popratnim zamkama) ilustrirat
emo programom u kojem traimo zbroj svih cijelih brojeva od 1 do 100. Budui da se
radi o trivijalnom raunu, izvedbeni program e i na najsporijim suvremenim strojevima
rezultat izbaciti za manje od sekunde. Stoga neemo koristiti Gaussov algoritam za
rjeenje problema, ve emo (zdravo-seljaki) napraviti petlju koja e zbrojiti sve
brojeve unutar zadanog intervala. Krenimo s prvom verzijom:
100
// ver. 1.0
#include <iostream.h>
int main() {
int zbroj = 0;
for(int i = 1; i <= 100; i++)
zbroj = zbroj + i;
cout << zbroj << endl;
return 0;
}
Blok naredbi u petlji je sada ostao prazan, ali ne smijemo izbaciti znak ;. U
dosadanjim realizacijama mogli smo broja petlje i poveavati i prefiks-operatorom:
// ver. 3.1
//...
for (int i = 1; i <= 100; zbroj += i, ++i) ;
//...
konani zbroj bi bio pogrean prvo bi se uveao broja, a zatim tako uvean dodao
zbroju kao rezultat bismo dobili zbroj brojeva od 2 do 101. Meutim, kada stopimo
oba izraza za prirast
// ver. 4.0
//...
101
Usporedimo li zadnju inaicu (4.0) s prvom, skraenje kda je oevidno. Ali je isto
tako oito da je kd postao nerazumljiviji: prebacivanjem naredbe za pribrajanje brojaa
u for-naredbu, zakamuflirali smo osnovnu naredbu zbog koje je uope petlja napisana.
Budui da veina dananjih prevoditelja ima ugraene postupke optimizacije
izvedbenog kda, pitanje je koliko e i hoe li uope ovakva saimanja rezultirati
brim i efikasnijim programom. Stoga vam preporuujemo:
Ne troite previe energije na ovakva saimanja, jer e najvjerojatnije
rezultat biti neproporcionalan uloenom trudu, a izvorni kd postati
neitljiv(iji).
102
float[5]
x[0]:
x[1]:
x[2]:
x[3]:
x[4]:
float
float
float
float
float
x
14243
4 bajta
144444444424444444443
20 bajtova
103
float[5]
x[0]:
x[1]:
x[2]:
x[3]:
x[4]:
float
float
float
float
float
1342.5
4.7
23.4
12.
-7.2e12
Navede li se ipak kod inicijalizacije duljina polja, treba paziti da ona bude vea ili
jednaka broju inicijalizirajuih lanova:
float x[5] = {1342.5, 4.7, 23.4, 12., -7.2e12};
// pogreka: previe
// inicijalizatora!
Tada lanovi brojanog polja kojima nedostaju inicijalizatori postaju jednaki nuli. Nije
dozvoljeno pridruiti praznu inicijalizacijsku listu polju koje nema definiranu duljinu:
float z[] = {};
Pri odabiru imena za polje treba voditi rauna da se ime polja ne smije
podudarati s imenom neke varijable u podruju dosega polja, tj. u podruju u
kojemu je polje vidljivo.
Ako se deklarira polje s imenom koje je ve iskoriteno za neku drugu varijablu,
prevoditelj e dojaviti pogreku da je varijabla s dotinim imenom deklarirana
viekratno. Na primjer:
int a;
int a[] = {1, 9, 9, 6};
// pogreka: ve postoji a!
104
Pojedini lanovi polja se dalje u kdu dohvaaju pomou cjelobrojnog indeksa koji se
navodi u uglatoj zagradi [] iza imena polja.
Prvi lan u polju ima indeks 0, a zadnji lan ima indeks za 1 manji od duljine
polja.
U primjeru sa slike 6.2 to znai da su lanovi polja: x[0], x[1], x[2], x[3] i x[4].
Prema tome:
float a = x[0];
//pridruuje vrijednost prvog lana
x[2] = x[0] + x[1]; //trei lan postaje jednak zbroju
// prvog i drugog lana
x[3]++;
//uvea etvrti lan za 1
Kao to je iz ovih primjera oito, svaki pojedini lan polja moe se dohvaati i mijenjati
neovisno o ostalim lanovima.
Primjenu polja ilustrirat emo programom koji iz datoteke podaci.dat uitava
(x, y) parove toaka neke funkcije, a zatim za proizvoljni x unesen preko tipkovnice
izraunava interpoliranu vrijednost funkcije f(x) u toj toki. Koristit emo Lagrangeovu
formulu za interpolaciju:
n
f ( x ) Li ( x ) f i = L0 ( x ) f 0 + L1 ( x ) f 1 + Ln ( x ) f n ,
i =0
Li ( x ) =
j =0
j i
x xj
xi x j
( x x0 )( x x1 ) ( x xi 1 )( x xi +1 ) ( x xn )
( xi x0 )( xi x1 ) ( xi xi 1 )( xi xi +1 ) ( xi xn )
Tako dobivena funkcija prolazi tono kroz zadane toke (xi, fi) bez obzira kako su one
rasporeene (slika 6.3). Prvo emo uitati parove podataka u polja x[], odnosno y[]:
xn , fn
x3 , f3
x1 , f1
x0 , f0
x 2 , f2
x
105
#include <iostream.h>
#include <fstream.h>
int main() {
const int nmax = 100;
float x[nmax], y[nmax];
fstream ulazniTok("podaci.dat", ios::in);
if (!ulazniTok) {
cerr << "Ne mogu otvoriti traenu datoteku"
<< endl;
return 1;
}
int i = -1;
while (ulazniTok) {
// prekida se na kraju datoteke
if (++i >= nmax) {
cerr << "Previe podataka!" << endl;
return 2;
}
ulazniTok >> x[i] >> y[i];
}
if (i < 0) {
cerr << "Nema podataka!" << endl;
return 3;
}
// nastavak slijedi...
106
0.0
0.7
1.0
1.2
1.5
1.7
2.0
2.5
3.0
107
vana mjera zatite, jer za razliku od mnogih programskih jezika, u jeziku C++ nema
provjere granica polja prilikom pristupa elementima.
Navede li se preveliki ili negativni indeks, prevoditelj nee javiti pogreku i
pristupit e se memorijskoj adresi koja je izvan podruja rezerviranog za
polje!
Kod dohvaanja lana s nedozvoljenim indeksom, uitana vrijednost bit e openito
neki sluajni broj, to i ne mora biti pogibeljno. Meutim, kod pridruivanja lanu s
indeksom izvan dozvoljenog opsega, vrijednost e se pohraniti negdje u memoriju u
podruje predvieno za neku drugu varijablu ili ak izvrni kd. U prvom sluaju
dodjela e uzrokovati promjenu vrijednosti varijable pohranjene na tom mjestu, to e
vjerojatno na kraju dati pogreku u konanom rezultatu. U drugom sluaju, ako
vrijednost odbjeglog lana dospije u dio memorije gdje se nalazi izvrni kd,
posljedice su nesagledive i vjerojatno e rezultirati blokiranjem rada programa ili
cijelog raunala. Da bismo se osvjedoili u gornje tvrdnje, pogledajmo sljedei
(bezopasan) primjer:
int main() {
float a[] = {10, 20, 30};
float b[] = {40, 50, 60};
float c[] = {70, 80};
cout << b[-1] << endl;
cout << b[3] << endl;
return 0;
}
b[3]
80.
14243
int c[2]
40.
50.
1442443
int b[3]
60.
10.
20.
1442443
30.
int a[3]
Zbog toga su naredbama za ispis lanova b[-1] i b[3] dohvaeni lanovi c[1],
odnosno a[0].
Kod dohvaanja lanova polja indeks openito smije biti cjelobrojna konstanta,
cjelobrojna varijabla ili cjelobrojni izraz. Stoga su sljedee naredbe dozvoljene:
108
float a[10];
int i = 2;
int j = 5;
float b = a[i + j];
b = a[i % j];
b = a[2 * i - j];
// a[7]
// a[2]
// a[-1]
Meutim, budui da indeks ne smije biti nita osim cjelobrojnog tipa, naredbe poput:
b = a[2.3];
float c = 1.23;
b = a[c / 2];
// pogreka
// pogreka
2. stupac
3. stupac
4. stupac
5. stupac
1. redak
214
...
...
...
...
2. redak
...
...
...
...
...
3. redak
...
101
...
...
...
int Tablica[3][5];
lanove tog dvodimenzionalnog polja dohvaamo preko dva indeksa: prvi je odreen
retkom, a drugi stupcem u kojem se podatak u tablici nalazi. Ne zaboravimo pritom da
je poetni indeks 0:
Tablica[0][0] = 214;
Tablica[2][1] = 101;
// 1.redak, 1.stupac
// 3.redak, 2.stupac
tri jednaka
109
// pogreno
C++ prevoditelj na ovakvu naredbu nee javiti sintaksnu pogreku! Zarez koji odvaja
bojeve 2 i 1 je operator razdvajanja koji e prouzroiti odbacivanje prvog broja; stoga
e gornja naredba biti ekvivalentna naredbi
Tablica[1];
Zapravo se dohvaa adresa prvog lana u drugom retku (o adresama e biti rijei
kasnije u ovom poglavlju).
Pravila koja vrijede za jednodimenzionalna polja vrijede i za viedimenzionalna
polja. lanovi viedimenzionalnog polja mogu se takoer inicijalizirati prilikom
deklaracije:
int Tablica[3][5] = { {11, 12, 13, 14, 15},
{21, 22, 23, 24, 25} };
110
Tablica Tablica Tablica Tablica Tablica
[0][0]: [0][1]: [0][2]: [0][3]: [0][4]:
int[3][5]
Tablica
int
int
int
int
int
11
12
13
14
15
int
int
int
int
21
22
23
24
25
int
int
int
int
111
(1)
Podijelimo li sada drugu jednadbu s a22
, ona prelazi u
112
return 2;
}
int r, s;
for (r = 0; r < n; r++)
for (int s = 0; s <= n; s++)
ulazniTok >> a[r][s];
// nastavak slijedi...
// po recima
// po stupcima
2 x1 7 x2 + 4 x3 = 9
x1 + 9 x2 6 x3 = 1
3x1 + 8 x2 + 5x3 = 6
datoteka s ulaznim podacima jest:
3
2
1
-3
-7
9
8
4
-6
5
9
1
6
a[0][1]=-7.
a[1][1]=9.
a[2][1]=8.
a[0][2]=4.
a[1][2]=-6.
a[2][2]=5.
a[0][3]=9.
a[1][3]=1.
a[2][3]=6.
Ostali lanovi polja ostaju nedefinirani, ali kako ih neemo dohvaati, to nema
nikakvog utjecaja na raun. Slijedi ranije opisani Gaussov postupak:
// nastavak: svoenje na trokutastu matricu...
for (r = 0; r < n; r++) {
for (s = r + 1; s <= n; s++)
a[r][s] /= a[r][r];
for (int rr = r + 1; rr < n; rr++)
for (int ss = r + 1; ss <= n; ss++)
a[rr][ss] -= a[rr][r] * a[r][ss];
}
// ...te povratna supstitucija
for (r = n - 1; r >= 0; r--)
for (s = n - 1; s > r; s--)
a[r][n] -= a[r][s] * a[s][n];
// ispis rezultata
for (r = 0; r < n; r++)
113
Uoimo takoer kako su u gornjem kdu izostavljene vitiaste zagrade kod fornaredbi koje su ugnijeene jedna unutar druge:
for (r = 0; r < n; r++)
for (int s = 0; s <= n; s++)
ulazniTok >> a[r][s];
Unato injenici da se unutar vanjske for-petlje nalaze dvije naredbe (unutarnja fornaredba te naredba za uitavanje), prevoditelj blok za prvu for-naredbu uzima do kraja
prve izvrne naredbe, tj. do prvog znaka ; (toka-zarez).
Ponekad se moe ukazati potreba i za trodimenzionalnim poljima, pa e primjerice
u nekom programu za analizu prostorne raspodjele temperature trebati deklarirati polje
float temperatura[x][y][z];
kod kojeg e pojedini indeksi biti odreeni (x, y, z) koordinatama toaka za koje se
rauna temperatura. Iako broj dimenzija polja nije ogranien, polja s vie od dvije
dimenzije se koriste vrlo rijetko. Pritom valja uoiti da zauzee memorije raste s
potencijom broja dimenzija: jednodimenzionalno polje a[10] broji deset lanova,
dvodimenzionalno polje b[10][10] broji 10 10 = 100 lanova, a trodimenzionalno
polje c[10][10][10] sadri 10 10 10 = 1000 lanova.
Poneki itatelj e se zapitati kako se dvodimenzionalna ili trodimenzionalna polja
pohranjuju u memoriju koja se adresira linearno. Prevoditelj pojedine dimenzije polja
114
2. redak
644474448
a[0][1]
a[1][0]
a[1][1]
3. redak
644474448
a[2][0]
a[2][1]
Zbog toga se moe dogoditi (slino kao u primjeru na stranici 107 za tri
jednodimenzionalna polja) da indeks izvan dozvoljenog opsega dohvati lan iz drugog
retka.
6.2. Pokazivai
Kao to samo ime kae, pokazivai (engl. pointers) su objekti koji pokazuju na drugi
objekt. Sam pokaziva sadri memorijsku adresu objekta na kojeg pokazuje. Iako na
prvi pogled svrsishodnost pokazivaa nije oita, u jeziku C++ njihova primjena je
veoma rasprostranjena, jer pruaju praktiki neogranienu fleksibilnost u pisanju
programa. Pokazivae smo posredno upoznali kod polja, budui da se lanovi polja
dohvaaju upravo preko pokazivaa.
Pokaziva se moe deklarirati tako da pokazuje na bilo koji tip podataka. U
deklaraciji se navodi tip podatka na koji pokaziva pokazuje, a ispred imena se stavlja *
(zvjezdica). Naredba
int *cajger;
deklarira pokaziva cajger na objekt tipa int. Smisao pokazivaa ilustrirat emo
sljedeim kdom:
int *kazalo;
int n = 5;
kazalo = &n;
// pokaziva na int
// usmjeri pokaziva na n
Prvom naredbom se deklarira varijabla kazalo kao pokaziva na (neki) broj tipa int
(slika 6.8a). Zatim se deklarira varijabla n te joj se pridruuje vrijednost 5 (slika 6.8b).
Na kraju se varijabli kazalo, pomou operatora za dohvaanje adrese & pridruuje
vrijednost memorijske adrese na kojoj je pohranjena varijabla n (slika 6.8c). Sada
vrijednost varijable n moemo dohvaati izravno ili posredno, preko pokazivaa:
115
int*
int*
kazalo
kazalo :
int*
int*
??
a)
int
int
nn
int
int
55
b)
int*
int*
kazalo
kazalo :
int*
int*
int
int
nn
int
int
55
c)
Slika 6.8. Deklaracija pokazivaa i pridruivanje vrijednosti
// ispisuje 5...
// ...i opet 5
Uoimo kako smo pri dohvaanju sadraja varijable n preko pokazivaa, morali ispred
imena pokazivaa umetnuti znak za pokaziva; nas naime zanima sadraj memorije na
koju pokazuje pokaziva, a ne sadraj memorije u kojoj se nalazi pokaziva.
Operator * naziva se operatorom dereferenciranja ili indirekcije
(dereferencing, indirection operator) dok se operator & naziva operatorom
adrese (address-of operator).
Izostavimo li znak * ispred imena pokazivaa, dohvatit emo sadraj pokazivaa, tj.
memorijsku adresu na kojoj je pohranjena varijabla n:
cout << "kazalo = " << kazalo << endl;
// ispisuje memorijsku adresu varijable n
116
*kazalo += 5;
n = *kazalo - 2;
// isto kao: n += 5
// sada je n = 10
// isto kao: n = n - 2;
// sada je n = 8
Kod operacija preko pokazivaa naroito treba paziti da se ne izostavi operator *, jer u
protivnom moe doi do neugodnih efekata. Izostavimo li u prvoj gornjoj naredbi
operator *
kazalo += 5;
// preusmjerava pokaziva!
cout << "kazalo = " << kazalo << endl;
cout << "*kazalo = " << *kazalo << endl;
117
int*
int*
int*
int*
kazalo
kazalo :
kazalo
kazalo :
int*
int*
int*
int*
int
int
nn
int
int
88
int
int
mm
int
int
nn
int
int
mm
a)
int
int
33
int
int
88
int
int
33
b)
// n = 10
// pogreka
// pogreka
118
// pokaziva na void
nesvrstan = &n;
nesvrstan = pokx;
// preusmjeri na n
// preusmjeri na x
119
*nesvrstan = x;
Prije ispisa, pokazivau na void treba dodijeliti tip pokazivaa na int, da bi prevoditelj
znao pravilno proitati sadraj memorije na koju nesvrstan pokazuje. Stoga na njega
primjenjujemo operator dodjele tipa (int*).
Zanimljivo je uoiti da pokazivai, unato tome to pokazuju na razliite tipove
podataka, na istom raunalu uvijek zauzimaju jednake memorijske prostore. U to se
moemo osvjedoiti pomou operatora sizeof: kako svi pokazivai zauzimaju jednaki
memorijski prostor, naredbe za ispis
// duljine pokazivaa na:
cout << sizeof(int*) << endl;
//
int,
cout << sizeof(float*) << endl; //
float,
double *xyz;
cout << sizeof(xyz) << endl;
//
double
Posebna vrijednost koji pokaziva moe imati jest nul-pokaziva (engl. null-pointer).
Takav pokaziva ne pokazuje nikuda pa pokuaj pristupa sadraju na koji takav
pokaziva pokazuje moe zavriti vrlo pogubno. Nema provjere tijekom izvoenja
programa je li vrijednost pokazivaa nul-pokaziva ili ne; na programeru je sva
odgovornost da do takve situacije ne doe. Pokaziva se moe inicijalizirati kao nulpokaziva tako da mu se jednostavno dodijeli nula:
int *x = 0;
Izuzetak od ovog pravila postoji ako se koriste razliiti modovi adresiranja memorije, kao to
je sluaj s DOS aplikacijama kod kojih postoje bliski (engl. near) i daleki (engl. far)
pokazivai koji su razliitih duljina.
120
Postavlja se pitanje emu uope slue nul-pokazivai, kada njihova primjena moe
imati poguban uinak po izvoenje programa. Ako se pokaziva ne inicijalizira odmah
nakon deklaracije, moe mu se dodijeliti vrijednost nul-pokazivaa. Prije upotrebe,
provjerom vrijednosti pokazivaa mogue je ustanoviti je li on inicijaliziran ili ne.
6.2.2. Kamo sa zvijezdom (petokrakom)
jer je znak za pokaziva * (zvjezdica) pridruen tipu, ime se tip podatka jae istie kao
pokaziva na float. Meutim, ovaj nain je nespretan pri viestrukim deklaracijama,
na primjer:
int* kazaljka, nekazaljka;
121
Osim toga, takav nain pisanja dosljednije odraava prikaz pokazivaa u izrazima. To je
naroito izraeno u izrazima s mnoenjem. Na primjer, u sljedeem primjeru elimo
pomnoiti dvije varijable na koje pokazuju pokazivai poka i pokb. Preglednije je
pisati operatore dereferenciranja neposredno uz imena pokazivaa:
int
int
int
int
a = 5;
b = 10;
*poka = &a;
*pokb = &b;
ili, jo gore:
int c = *poka **pokb;
122
float[5]
float[5]
xx
x[0]:
x[1]:
x[2]:
x[3]:
x[4]:
float
float
float
float
float
float
float
float
float
float
float
float **
Ova zadnja naredba moe se interpretirati na sljedei nain: uzmi adresu prvog lana
polja, poveaj ju za dva, pogledaj to se nalazi na toj adresi, te pridrui vrijednost na toj
lokaciji varijabli a. Valja uoiti da se pritom adresa ne poveava za dva bajta, ve za
dva segmenta u koje stanu podaci tipa float. Na primjer, ako se poetak polja x nalazi
na adresi 0x2000, a varijable tipa float zauzimaju 4 bajta, tada e pokaziva x + 2
pokazivati na adresu 0x2008. U ovom primjeru upoznali smo se s aritmetikim
operacijama s pokazivaima, o emu e ipak opirnije biti govora u sljedeem
potpoglavlju.
Zbog navedene veze izmeu pokazivaa i polja, ako se navede naziv polja bez
indeksa, on ima znaenje pokazivaa na prvi lan, te e sljedei kd ispisati iste
vrijednosti:
int b[] = {10, 20, 30};
cout << b << endl;
cout << *b << endl;
ispisati memorijsku adresu u kojoj je pohranjen lan polja b[1]. Oito je da su pristupi
lanovima polja preko indeksa ili preko pokazivaa potpuno ekvivalentni koji pristup
e se koristiti ovisi iskljuivo o ukusu programera.
Dovitljivi itatelj e na osnovi ove povezanosti pokazivaa i polja sada sam
zakljuiti zato je poetni indeks polja u jeziku C++ upravo nula, a ne 1 kao u nekim
drugim programskim jezicima.
No vano je ipak razumjeti osnovnu razliku izmeu pokazivaa i polja.
Deklaracijom
int x[5];
123
// b[0] = a[0]
rezultirat e preslikavanjem samo prvog lana a[0] u prvi lan b[0]. Ovakvo
djelovanje operatora pridruivanja na polja je logino, ako se razumije kako se polja
prikazuju na razini strojnog kda.
Promotrimo jo vezu izmeu pokazivaa i viedimenzionalnih polja.
Viedimenzionalna polja se pohranjuju u memoriju linearno (vidi sliku 6.7): polje
deklarirano kao
float a[3][4];
moe se stoga shvatiti kao niz od tri jednodimenzionalna polja, od kojih svako sadri
etiri elementa, sloenih jedno za drugim. Pritom su a[0], a[1] i a[2] pokazivai na
poetne lanove svakog od tih jednodimenzionalnih polja. U to se moemo osvjedoiti
sljedeim primjerom:
float a[3][4] = {{1.1, 1.2, 1.3, 1.4},
{2.1, 2.2, 2.3, 2.4}};
cout << *a[0] << "\t" << *a[1] << endl;
124
Naredbom za ispis dobit emo brojeve 1.1 i 2.1, tj. poetne lanove prva dva
jednodimenzionalna broja. Slijedi da se dvodimenzionalno polje moe interpretirati kao
polje pokazivaa, od kojih svaki pokazuje na poetak jednodimenzionalnog polja (slika
6.11).
float[3][4]
float[3][4] aa
float
float (*a)[0]
(*a)[0]
float
float (*a)[1]
(*a)[1]
float
float (*a)[2]
(*a)[2]
float
float
1.2
1.2
float
float
1.3
1.3
float
float
1.4
1.4
float
float
2.2
2.2
float
float
2.3
2.3
float
float
2.4
2.4
float
float
float
float
float
float
Ako na raunalu na kojem radimo tip int zauzima dva bajta, gornji programski
odsjeak e ispisati dvije memorijske adrese od kojih je druga za 2 * 3 = 6 manja od
prve. Openito, svaki izraz tipa
125
T *pok1, *pok2;
int i;
// ...
pok2 = pok1 + i;
Pri tome je T neki proizvoljan tip, a i neki cijeli broj. Gornja naredba se treba itati
ovako: Pretvori pok1 u pokaziva na char, zato jer je char tip duljine jednog bajta.
Dobivenu adresu uveaj za broj koji je jednak i-strukoj veliini tipa T. Tako dobivenu
adresu pretvori natrag u pokaziva na tip T.
Za pokazivae su dozvoljeni i skraeni aritmetiki operatori, kao inkrement,
dekrement te operatori obnavljajueg pridruivanja.
esto se aritmetika s pokazivaima koristi u vezi s poljima. Naime, kako su lanovi
polja sloeni linearno u memoriji, a ime polja predstavlja pokaziva na njegov prvi lan,
pomou pokazivake aritmetike je jednostavno mogue pristupati susjednim lanovima:
float
float
float
float
x[10];
*px = &x[3];
x2 = *(px - 1);
x5 = *(px + 1);
// = 9
// = -9
126
#include <stdio.h>
int main() {
FILE *pok;
pok = fopen("datoteka.dat", "r");
if (pok == 0)
cout << "Nema traene datoteke.";
}
Gornji primjer poziva funkciju fopen() iz standardne biblioteke stdio.h koja otvara
neku datoteku. U sluaju da ta datoteka postoji, vratit e se pokaziva na objekt tipa
FILE (definiranog takoer u datoteci stdio.h), pomou kojeg se kasnije moe
pristupati sadraju datoteke. Ako operacija ne uspije, vratit e se nul-pokaziva. Gornju
provjeru moemo skraeno napisati i ovako:
if (!pok)
// ...
Sr gornjeg programa ini while petlja koja brojeve koji su ve smjeteni u polju
usporeuje s noviBroj kojeg elimo umetnuti. Pretraivanje kree od zadnjeg
(najveeg) broja u nizu, a istovremeno s pretraivanjem pomiu se lanovi niza vei od
novopridolog. Moe se vidjeti kako je za pomicanje po elementima polja koriten
pokaziva odrediste koji se dekrementira u svakom prolasku kroz petlju. Tako se na
jednostavan nain omoguava prolazak kroz sve lanove polja.
127
Zadatak. U gornji kd ubacite provjeru broja unesenih lanova niza tako njegova
duljina ne nadmai duljinu polja. Takoer ubacite provjeru da li broj ve postoji u nizu,
te onemoguite viekratno pohranjivanje jednakih brojeva.
6.2.5. Dinamiko alociranje memorije operatorom
new
128
Operatorom new rezervira se potreban memorijski prostor za objekt tipa float. Iza
kljune rijei new navodi se tip podatka za koji se osigurava memorijski prostor, a zatim
(neobavezno) par okruglih zagrada. Ako je operacija alociranja uspjeno izvedena,
operator vraa adresu alociranog prostora koju pridruujemo naem pokazivau
pokaZivac. Ako postupak nije bio uspjean (na primjer, nema dovoljno memorije za
zahtijevani objekt), operator vraa nulu, odnosno nul-pokaziva. Zato bismo, radi
vlastite sigurnosti, trebali provjeriti rezultat operatora new te eventualno javiti pogreku
ako alokacija memorije ne uspije:
float *pokaZivac = new float();
if (pokaZivac == 0)
cout << "Pun sam kao ipak!" << endl;
//...
Unutar okruglih zagrada iza oznake tipa moe se definirati poetna vrijednost objekta,
to znai da smo prethodni primjer mogli pisati jo krae:
float *pokaZivac = new float(10.5);
if (!pokaZivac)
cout << "Pun sam kao ipak!" << endl;
//...
129
Iza kljune rijei delete navodi se ime pokazivaa na lokaciju koju treba osloboditi.
Naravno da pritom taj pokaziva mora biti dohvatljiv iako je prostor na koji pokaziva
pokazuje alociran dinamiki, sam pokaZivac je deklariran kao automatski objekt, te
mu se po izlasku iz bloka gubi svaki trag.
Valja uoiti da primjena operatora delete ne podrazumijeva i brisanje sadraja
objekta njime samo prostor koji je zauzimao objekt postaje slobodnim za odstrel, tj. za
druge dinamike objekte. Stoga je gotovo sigurno da e izvoenje naredbi:
int *varijablaKojaNestaje = new int(123);
delete varijablaKojaNestaje;
cout << *varijablaKojaNestaje << endl;
To nije tono za sve prevoditelje jer mnogi prevoditelji automatski generiraju kd koji unitava
sve dinamike objekte prilikom okonanja programa. No na to svojstvo se ne treba oslanjati,
ve valja uredno osloboditi svu zauzetu memoriju runo.
130
Vrlo je lako zaboraviti umetnuti par uglatih zagrada prilikom oslobaanja polja.
Prevoditelj nee javiti nikakvu pogreku, pa ak niti upozorenje, no takav kd moe
prouzroiti probleme prilikom izvoenja. Operator delete oslobaa memoriju samo
jednog lana, dok operator delete [] prvo provjerava duljinu polja, te unitava svaki
element polja.
Operatori delete i delete [] nisu isti operatori. delete slui za
oslobaanje jednog objekta, dok delete [] oslobaa polja.
Kao primjer za alokaciju i dealokaciju polja posluit e nam program kojemu je svrha
za zadane toke izraunati koeficijente pravca koji se moe najbolje provui izmeu
njih. Zamislimo trkaa koji tri stalnom brzinom. Mjerenjem meuvremena na svakih
100 metara elimo odrediti prosjenu brzinu kojom on tri, te koliko e mu vremena
trebati da pretri stazu dugaku 800 metara. Rezultati mjerenja dani su u tablici 6.1.
s
t
0
0
100 m
13 s
200 m
31 s
300 m
46 s
400 m
63 s
500 m
76 s
Prikaemo li te podatke grafiki (slika 6.12), uoit emo da su mjereni podaci grupirani
oko pravca. Nagib tog pravca odgovara prosjenoj brzini trkaa, a ekstrapolacijom
pravca na 800 m moi emo procijeniti vrijeme potrebno za cijelu stazu.
Jednadbu pravca odredit emo postupkom najmanjih kvadrata. Koeficijenti pravca
y = ax + b
odreeni su izrazima
n1
1
a =n-1
( x i -x ) yi ,
( xi -x )2 i=0
i =0
b =y -a x ,
gdje su
131
800
600
s
m
400
200
0
50
t /s
100
1 n-1
x = x i ,
n i =0
1 n-1
y = yi
n i =0
srednje vrijednosti koordinata toaka. Pokuajmo napisati program koji koristi gore
opisanu metodu najmanjih kvadrata. Prvo treba uitati broj mjernih toaka i pripadajue
koordinate:
#include <iostream.h>
int main() {
cout << "Koliko toaka mogu oekivati? ";
int n;
// broj toaka
cin >> n;
float *x = new float[n];
float *y = new float[n];
// alocira se prostor za
// polja s koordinatama
132
srednjiY /= n;
// nastavak slijedi...
Iza oznake tipa podatka, u uglatim zagradama naveden je broj lanova polja. Kao i kod
alokacije pojedinanih varijabli, operator new vraa kao rezultat pokaziva na poetak
polja ili nul-pokaziva, ovisno o uspjehu alokacije (Sa titom ili na titu!). Ovo je jedini
nain za alokaciju polja ija je duljina poznata prilikom izvoenja ako bismo koristili
automatska polja, bilo bi neophodno predvidjeti najvei mogui broj elemenata koji e
se smjetati u polje, te deklarirati takvo polje. U veini sluajeva bi to znailo uludo
troenje memorije, a s druge strane nemamo jamstva da e predviena duljina polja
uvijek biti dostatna. Ovako se alocira upravo potreban broj lanova, ime se
racionalizira potronja memorije.
Pri uitavanju koordinata u petlji, u varijable srednjiX i srednjiY se
akumuliraju njihove sume, a po izlasku iz petlje se iz njih raunaju srednje vrijednosti.
Slijedi petlja u kojoj se raunaju sume u brojniku i nazivniku izraza za koeficijent
smjera a:
// ... nastavak
double a = 0.0;
// koeficijent smjera
double nazivnik = 0.0;
// nazivnik izraza za k.s.
for (i = 0; i < n; i++) {
double deltaX = x[i] - srednjiX;
a += deltaX * y[i];
nazivnik += deltaX * deltaX;
}
a /= nazivnik;
double b = srednjiY - a * srednjiX;
// ima jo...
Pozdrav kojim su spartanski vojnici otpravljani u ratove. Znai: Vratite se kao pobjednici ili
nam se ne vraajte ivi pred oi!
133
ime se naznaava da se oslobaa polje. Unutar zagrada ne smije biti upisano nita
prevoditelj zna kolika je duljina polja, te e na osnovi toga osloboditi odgovarajui
prostor. U starijim inaicama C++ jezika, unutar uglatih zagrada trebalo je navesti
duljinu polja, meutim standard sada to ne zahtijeva i ne dozvoljava.
Spomenimo jo nekoliko vanih injenica vezanih uz alokaciju polja: za razliku od
alokacije prostora za pojedinane varijable, prilikom alokacije polja lanovi se ne mogu
inicijalizirati. Takoer, unato injenici da je polje alocirano tijekom izvoenja
programa, duljina tog polja se ne moe naknadno dinamiki mijenjati. Na primjer, to
napraviti ako broj lanova polja postane vei od duljine alociranog polja? Poneki
dovitljivi haker bi mogao doi na ideju za sljedei kd:
float *prvoPolje = new float[n];
// ...
delete [] prvoPolje;
float *novoPolje = new float[m];
134
Operator new vraa pokazivae na poetke tih jednodimenzionalnih polja, tj. adresu
polja od 3 pokazivaa, od kojih svaki pokazuje na poetak jednodimenzionalnog polja s
4 lana (vidi sliku 6.11 na strani 124). Okrugla zagrada oko pokazivaa *a je
neophodna jer uglata zagrada ima vii prioritet od znaka za pokaziva. Izostavljanjem
zagrada:
float *a[4] = new float[3][4];
// pogreka
// pogreka
// OK
dozvoljena. Razlog tome lei u nainu na koji generirani strojni kd pristupa poljima.
Dvodimenizonalno polje se slae u memoriju tako da se reci upisuju uzastopno jedan iza
drugoga. Da bi mogao odrediti poetak svakog pojedinog retka, prevoditelju je potreban
podatak o duljini redaka, a to je broj stupaca.
6.2.8. Pokazivai na pokazivae
135
#include <iostream.h>
int main() {
float **dvaDpolje;
int i, redaka, stupaca;
cout << "Broj redaka: ";
cin >> redaka;
dvaDpolje = new float*[redaka];
for (i = 0; i < redaka; i++) {
cout << "Broj lanova u " << (i+1) << ". retku:";
cin >> stupaca;
dvaDpolje[i] = new float[stupaca + 1];
dvaDpolje[i][0] = stupaca;
for (int j = 1; j <= stupaca; j++)
dvaDpolje[i][j] = i + 1 + j / 100.;
}
cout << "Ispis lanova polja:" << endl;
for (i = 0; i < redaka; i++)
for (int j = 1; j <= dvaDpolje[i][0]; j++)
cout << "[" << i << "][" << j << "] = "
<< dvaDpolje[i][j] << endl;
// nastavak slijedi nakon raslanbe...
Deklaracijom
float **dvaDpolje;
136
Prvo se u petlji oslobaaju prostori za polja pojedinih redaka, a tek potom se uklanja
pokaziva (na pokaziva) dvaDpolje. Redoslijed oslobaanje je vrlo vaan mora se
obavljati obrnutim redoslijedom od onoga pri pridruivanju. Naime, ako bi se prvo
uklonio poetni pokaziva na pokaziva dvaDpolje, izgubila bi se veza prema
pokazivaima na poetke pojedinih redaka, pa ih ne bi bilo mogue vie dohvatiti. Time
bi bila onemoguena dealokacija prostora za pojedine retke.
dvaDpolje
dvaDpolje
float*[0]
float**
float**
:
float*[1]
float[0][0] float[0][1]
float*[redaka]
float*[redaka]
float[0][stupaca]
float[0][stupaca]
137
// pogreka
// pogreka
// OK
// OK
Promjena varijable u tom sluaju mogua je samo izravno, ali ne i preko pokazivaa na
konstantu:
r = 45.2;
*pipok = 9.23
// OK
// pogreka
138
// nepromjenjivi
// pokaziva
Objekt na koji pokaziva pokazuje moe se po volji mijenjati, izravno ili preko
pokazivaa:
nekiBroj = 1993.1009;
*neMrdaj = 1992.3001;
// OK
// OK
// pogreka
139
Gornja dodjela je dozvoljena jer time integritet objekta na koji pokPromj pokazuje
nee biti naruen: jedino se vrijednost objekta nee moi promijeniti preko pokazivaa
pokKonst. No obrnuta dodjela nee biti dozvoljena:
pokPromj = pokKonst;
// pogreka
// OK
Gornja naredba sada prevoditelju daje na znanje da programer na sebe preuzima svu
odgovornost za eventualnu promjenu konstantnog objekta.
Sve to je reeno za nepromjenjive pokazivae i pokazivae na nepromjenjive
objekte vrijedi i za volatile pokazivae i pokazivae na volatile objekte. Tako je
mogue deklarirati razliite pokazivae:
volatile int *pok1;
int volatile *pok2;
char[]
char[]
: 'F'
'F' 'a'
'a' 'u'
'u' 's'
's' 't'
't' '' '' 'V'
'V' 'r'
'r' 'a'
'a' 'n'
'n' ''
'' 'i'
'i' ''
'' '\0'
'\0'
padobran
padobran
140
J
u
l
i
j
e
70
97
117
115
116
32
86
114
97
110
232
105
230
0
74
117
108
105
106
101
Paljiv itatelj je zasigurno primijetio da iako smo ispisivali znakovni niz padobran,
dobili smo i dio niza minijature. Naime, prevoditelj kojeg smo koristili za izvoenje
gornjeg primjera poredao je u memoriju znakovne nizove u obrnutom redoslijedu od
redoslijeda deklaracije. Znakovni niz minijature smjeten je neposredno iza niza
padobran. Kako smo u for-petlji ispisivali dvadeset znakova, to je vie nego to ih
ima padobran, zagazili smo i u niz minijature. Ovakvo ponaanje je vrlo ovisno o
141
prevoditelju: neki drugi prevoditelj e moda smjestiti podatke na drukiji nain te ete
moda dobiti drukiji ispis.
Nul-znak izmeu dva niza umetnuo je prevoditelj da bi funkcijama koje e
dohvaati i obraivati znakovni niz dao do znanja gdje on zavrava. Tako e izlazni tok
za ispis:
cout << padobran << endl;
tono znati da je slovo '' ispred nul-znaka zadnje u nizu, te e pravilno ispisati sadraj
niza padobran samo do tog slova:
Faust Vrani
tj. ispisuju se svi znakovi do prvog nul-znaka. Ako bismo prepisali i nul-znak koji
zakljuuje drugi niz, rezultat ispisa bi bio nepredvidiv.
Na osnovi ovog razmatranja slijedi da smo znakovne nizove mogli deklarirati i
inicijalizirati identino kako smo to radili i kod jednodimenzionalnih polja, ali uz
obaveznu eksplicitnu inicijalizaciju zakljunog nul-znaka:
char padobran[] = { 'F', 'a', 'u', 's', 't', ' ',
'V', 'r', 'a', 'n', '', 'i', '',
'\0'
};
// nezakljuen niz!
142
Naizgled, ova inicijalizacija nee proi, jer kada smo slinu stvar pokuali s brojevima:
int *n = 10;
// pogreka
// pogreka
143
esto se u programu javljaju srodni znakovni nizovi koje je praktino pohraniti u jedno
polje. Budui da je znakovni niz sam po sebi jednodimenzionalno polje znakova, polje
znakovnih nizova u stvari e biti dvodimenzionalno polje. Evo primjera:
#include <iostream.h>
int main() {
char dani[7][12] = {"nedjelja",
"ponedjeljak",
"utorak",
"srijeda",
"etvrtak",
"petak",
"subota"
};
cout << "Kad e taj " << dani[5] << endl;
return 0;
}
144
6.5. Reference
Reference su poseban tip podataka. One djeluju slino pokazivaima, te su u sutini
drugo ime za objekt odreenog tipa. Razmotrimo sljedei primjer:
#include <iostream.h>
int main() {
int i = 5;
// varijabla
int &iref = i;
// referenca na varijablu i
cout << "i = " << i << " iref = " << iref << endl;
// promjenom reference mijenja se izvorna varijabla
iref = 75;
cout << "i = " << i << " iref = " << iref << endl;
return 0;
}
Time se zapravo uvodi novo ime za ve postojeu varijablu i (vidi sliku 6.15).
Promjenom reference mijenja se u stvari izvorna varijabla, u to nas uvjerava ponovni
ispis varijabli u gornjem programu.
145
55
int&
pokazivai. Meutim, pokaziva se moe
int& iref
iref :
// pogreka
Pokuaj promjene konstantne reference xref prevoditelj e prijaviti kao pogreku, ali
se varijabla x moe mijenjati bez ikakvih ogranienja.
Reference najveu primjenu imaju kao argumenti i povratne vrijednosti funkcija, pa
emo ih u poglavlju o funkcijama pomnije upoznati.
6.5.1. Reference za hakere
146
Pri tome haker-hlebinac moe pomisliti da e &ref dati adresu na kojoj se zapravo
nalazi pokaziva te *(&ref) omoguava upisivanje adrese varijable j na to mjesto.
Gornji kd se nee uope niti prevesti, jer e prevoditelj dojaviti da ne moe pretvoriti
int * u int. O emu se zapravo radi?
Iako su reference u biti pokazivai, jezik C++ ne dozvoljava izravan pristup adresi
reference; to se njega tie, referenca je samo sinonim za neki drugi objekt. Zbog toga
e &ref vratiti adresu objekta na koji referenca pokazuje, a ne adresu reference. U to se
moemo osvjedoiti sljedeim primjerom:
int main() {
char a;
char &ref = a;
cout << "Adresa od a: " << &a << endl;
cout << "Adresa reference: " << &ref << endl;
return 0;
}
Nakon izvoenja, dobit emo ispis u kojemu su obje adrese identine. Takoer, sizeof
operator nee vratiti veliinu reference (koja je jednaka veliini pokazivaa), nego e
vratiti veliinu referiranog objekta:
int main() {
char a;
char &ref = a;
cout << "Veliina od a: " << sizeof(a) << endl;
cout << "Veliina reference: " << sizeof(ref) << endl;
return 0;
}
Nakon izvoena, dobit emo da je veliina i varijable a i reference ref jednaka jedan.
No referencu se ipak moe preusmjeriti prljavim trikovima. Donji program uspjeno je
preveden i izveden pomou Borland C++ 4.5 prevoditelja na Pentium raunalu te ne
garantiramo uspjeno izvoenje na nekoj drugoj kombinaciji prevoditelja i/ili raunala.
int main() {
char zn1 = 'a',zn2 = 'b', &ref = zn1;
// Join us on the following page as Dirty Harry strikes...
char **pok = (char **)(&zn2 - sizeof(char*));
*pok = &zn2;
cout << ref << endl;
return 0;
}
Nakon izvoenja ispisao se znak 'b', ime smo se osvjedoili da ref vie ne referira
varijablu zn1, nego zn2. Ideja je sljedea: prevoditelj sve lokalne varijable smjeta u
kontinuirano podruje memorije. Usporedbom adresa varijabli zn1 i zn2 ustanovili smo
da e na prevoditelj prvo smjestiti varijablu ref, zatim zn2, pa onda zn1, (dakle, u
147
obrnutom redoslijedu od slijeda deklaracije). Zbog toga smo s &zn2 dobili adresu koju
smo umanjili za veliinu pokazivaa. Rezultat je adresa reference koju smo dodijelili
pokazivau pok. On je nakon dodjele tipa pokazivao na referencu (vidimo da je to
pokaziva na pokaziva, to je uinjeno i u deklaraciji). Zatim je sadraj tog pokazivaa
promijenjen, to je rezultiralo i promjenom sadraja reference. Na kraju jo slijedi
slavodobitan ispis. (Imamo referencu!)
Gornji program se koristi prljavim trikovima koji izlaze izvan definicija jezika C++
te ovise o implementaciji prevoditelja. Zbog toga pisanje i izvoenje takvih programa
provodite na vlastitu odgovornost. (Mi smo u ovom sluaju Poncije Pilati.) Ako takav
program i radi na jednoj vrsti raunala, vjerojatno nee raditi ako se promijeni
prevoditelj ili se program prenese na drugi tip raunala.
// pokaziva
{
int j = 100;
pok = &j;
}
// ...
// varijabla j vie ne postoji!
cout << *pok << endl;
// upitan ispis
return 0;
}
Izlaskom iz bloka varijabla j prestaje postojati, ali i dalje postoji pokaziva pok koji
pokazuje na mjesto gdje je varijabla j bila pohranjena. Prevoditelj e to osloboeno
podruje vrlo vjerojatno upotrijebiti za pohranu neke druge varijable, pa e ispis
sadraja preko pokazivaa davati nepredvidivi rezultat. Jo je gora mogunost da preko
pokazivaa pok pohranimo neku vrijednost i prepiemo sadraj novostvorene varijable.
Ovakve mrtve due mogu zadavati velike glavobolje programerima budui da je
takvim pogrekama dosta teko naknadno ui u trag. Pokazivai koji pokazuju na
148
Deklaracija typedef se obavlja tako da se iza kljune rijei typedef navede obina
deklaracija. Naziv sinonima se umee na mjesto gdje bi se inae nalazio naziv objekta
koji se deklarira: u gornjem primjeru, ako bismo kljunu rije typedef izostavili, dobili
bismo deklaraciju varijable pokZnak. Kako je ispred varijable stavljena kljuna rije
typedef, deklarira se sinonim pokZnak koji oznaava tip jednak tipu objekta koji bi se
deklarirao u sluaju bez kljune rijei typedef. Deklaracija typedef se moe nalaziti
samo u globalnom podruju imena i, za razliku od deklaracija klasa, ne smije se nalaziti
unutar definicije funkcije.
149
Sada ne moramo razbijati glavu time ima li zvjezdica vii ili nii prioritet od uglatih
zagrada, te jesmo li moda zapravo deklarirali pokaziva na polje od sto znakova.
150
7. Funkcije
Test prvoklasne inteligencije sastoji se u
sposobnosti istovremenog dranja dviju
suprotnih ideja u glavi, a da se pritom zadri
sposobnost funkcioniranja.
Scott Fitzgerald (1896-1940)
151
int main(){
// ...
funkcijaPrva();
// ...
// ...
funkcijaDruga();
// ...
// ...
funkcijaDruga();
// ...
funkcijaCetvrta();
// ...
return 1;
}
funkcijaPrva(){
// ...
return;
}
funkcijaDruga(){
// ...
funkcijaTreca();
// ...
return;
}
funkcijaTreca(){
// ...
return;
}
funkcijaCetvrta(){
// ...
return;
}
p!
p
.
=
r r !( p r )!
Bez koritenja funkcija program bi izgledao ovako:
#include <iostream.h>
int main() {
int i, p, r;
cout << "p = ";
cin >> p;
cout << "r = ";
cin >> r;
long brojnik = 1;
for (i = p; i > 1; i--)
brojnik *= i;
long nazivnik = 1;
for (i = r; i > 1; i--)
nazivnik *= i;
for (i = p - r; i > 1; i--)
nazivnik *= i;
// rauna p!
// rauna r!
// rauna (p-r)!
152
Odmah su uoljive tri vrlo sline petlje u kojima se raunaju faktorijele u brojniku,
odnosno nazivniku binomnog koeficijenta.
Pretpostavimo da imamo na raspolaganju gotovu funkciju faktorijel() koja
rauna faktorijelu broja. U tom sluaju gornji kd bismo mogli napisati ovako:
#include <iostream.h>
int main() {
int p, r;
cout << "p = ";
cin >> p;
cout << "r = ";
cin >> r;
cout << faktorijel(p) / faktorijel(r) /
faktorijel(p - r) << endl;
return 0;
}
Tip ispred imena funkcije odreuje kakvog e tipa biti podatak kojeg e funkcija
vraati pozivatelju kao rezultat svoga izvoenja. Argumenti unutar zagrada iza imena
Istina, kd bi se, kraenjem dijela umnoka u brojniku i nazivniku, mogao napisati efikasnije,
sa samo dvije for petlje, ali bi to umanjilo efekt primjera!
153
funkcije su podaci koji se predaju funkciji prilikom njena poziva. Broj i tip tih
argumenata moe biti proizvoljan, s time da funkcija moe biti i bez argumenata, a
mogu se deklarirati i funkcije s neodreenim brojem argumenata. Broj argumenata,
njihov redoslijed i tip nazivaju se potpisom funkcije (engl. function signature).
Kao jednostavan primjer uzmimo funkciju koja rauna kvadrat broja. Nazovimo tu
funkciju kvadrat. Argument te funkcije bit e broj x koji treba kvadrirati; uzmimo da
je taj argument tipa float. Funkcija e kao rezultat vraati kvadrat broja;
pretpostavimo da je to broj tipa double. Deklaracija funkcije kvadrat() izgledat e u
tom sluaju ovako:
double kvadrat(float x);
Ovo je samo prototip funkcije. Za sada nismo definirali funkciju nismo napisali kd
same funkcije, odnosno nismo odredili to i kako funkcija radi. Definicija funkcije (engl.
function definition) kvadrat() izgledala bi:
double kvadrat(float x) {
return x * x;
}
Naredbe koje se izvode prilikom poziva funkcije ine tijelo funkcije (engl. function
body). Tijelo funkcije uvijek poinje prvom naredbom iza lijeve vitiaste zagrade {, a
zavrava pripadajuom desnom zagradom }. U gornjem primjeru tijelo funkcije sastoji
se samo od jedne jedine naredbe koja kvadrira x. Kljuna rije return upuuje na to
da se umnoak x * x prenosi kao povratna vrijednost u dio programa koji je pozvao
funkciju.
Deklaracija i definicija funkcije mogu biti razdvojene i smjetene u potpuno
razliitim dijelovima izvornog kda. Deklaracija se mora navesti u svakom
programskom odsjeku gdje se funkcija poziva, prije prvog poziva. To je potrebno kako
bi prevoditelj znao generirati strojni kd za poziv funkcije (taj kd ovisi o potpisu
funkcije). Definicija funkcije, se naprotiv, smjeta u samo jedan dio kda. Svi pozivi te
funkcije e se prilikom povezivanja programa usmjeriti na tu definiciju.
Funkcija mora biti deklarirana u izvornom kdu prije nego to se prvi puta
pozove. Definicija funkcije oblikom mora u potpunosti odgovarati
deklaraciji.
Ako se definicija i deklaracija razlikuju, prevoditelj e javiti pogreku. Moraju se
poklapati tip funkcije te broj, redoslijed i tip argumenata.
Imena argumenata u deklaraciji i definiciji funkcije mogu se razlikovati. tovie, u
deklaraciji funkcije imena argumenata mogu se izostaviti, to znai da smo gornju
deklaraciju mogli napisati i kao:
double kvadrat(float);
154
// deklaracija funkcije
int main() {
for(int i = 1; i <= 10; i++)
cout << setw(5) << i
<< setw(10) << kvadrat(i) << endl;
return 0;
}
double kvadrat(float x) {
return x * x;
}
// definicija funkcije
155
Zanimljivo je spomenuti da je ovaj princip prvo uveden u jezik C++, da bi tek potom bio
prihvaen u jeziku C i ukljuen u ANSI C standard [Stroustrup94].
156
// deklaracije funkcija
int main() {
// ...
funkcijaPrva();
// ...
funkcijaDruga();
}
funkcijaPrva(){
// ...
}
// definicija funkcije
funkcijaDruga(){
// ...
funkcijaPrva();
// ...
}
// definicija funkcije
Sada emo taj kd razmjestiti u tri odvojena modula, koje emo nazvati
poglavni.cpp, funk1.cpp i funk2.cpp; u prvi modul emo smjestiti glavnu
(main()) funkciju, a u potonje module definicije funkcija. Pogledajmo sadraje
pojedinih modula:
poglavni.cpp
#include "funk1.h"
#include "funk2.h"
int main() {
// ...
funkcijaPrva();
// ...
funkcijaDruga();
}
157
funk1.cpp
void funkcijaPrva(){
// ...
}
// definicija funkcije
funk2.cpp
#include "funk1.h"
void funkcijaDruga(){
// ...
funkcijaPrva();
// ...
}
// definicija funkcije
// deklaracija funkcije
funk2.h
extern void funkcijaDruga();
// deklaracija funkcije
158
Uvjetni operator radi na sljedei nain: ako je argument x vei ili jednak nuli, rezultat
operatora je jednak vrijednosti argumenta, a u protivnom sluaju rezultat je argument s
promijenjenim predznakom (odnosno apsolutna vrijednost). Naredbom return se
rezultat operatora proglaava za povratnu vrijednost, izvoenje funkcije se prekida te se
vrijednost vraa pozivajuem programu. Parametar naredbe return openito moe
biti bilo kakav broj, varijabla ili izraz koji se izraunava prije zavretka funkcije. Pri
tome tip rezultata izraza mora odgovarati tipu funkcije.
Ako je rezultat izraza naredbe return razliitog tipa od tipa funkcije, rezultat se
(ugraenim ili korisniki definiranim) pravilima konverzije svodi na tip funkcije. Stoga
e funkcija apsolutno() deklarirana kao:
double apsolutno(float x) {
return (x >= 0) ? x : -x;
}
vraati rezultat tipa double, unato tome to je argument, odnosno rezultat return
naredbe tipa float Naravno da nema previe smisla ovako definirati funkciju tipa
159
koja e (ako argument nije prevelik) vraati cjelobrojni dio argumenta. Rezultat
uvjetnog pridruivanja je opet float, ali kako je funkcija deklarirana kao long,
pozivajuem kdu e biti vraen samo cjelobrojni dio argumenta.
Funkcija kao rezultat moe vraati podatke bilo kojeg tipa, izuzev polja i
funkcija (iako moe vraati pokazivae i reference na takve tipove).
U pozivajuem kdu rezultat funkcije moe biti dio proizvoljnog izraza. Konkretno,
prije definiranu funkciju kvadrat() moemo koristiti u aritmetikim izrazima s desne
strane operatora pridruivanja, tretirajui ju kao svaki drugi double podatak. Na
primjer:
float xNaPetu = kvadrat(x) * kvadrat(x) * x;
double cNaKvadrat = kvadrat(a) + kvadrat(b);
Rezultat funkcije se moe i ignorirati. Funkciju kvadrat() smo mogli pozvati i ovako:
kvadrat(5);
Iako je smislenost gornje naredbe upitna, poziv je sasvim dozvoljen. Ako funkcija ne
treba vratiti vrijednost, to se moe eksplicitno naznaiti tako da se deklarira tipom void.
Obino su to funkcije za ispis poruka ili rezultata ovisnih o vrijednostima argumenata.
Na primjer, zatreba li nam funkcija koja e samo ispisivati kvadrat broja, a sam kvadrat
nee vraati pozivnom kdu, deklarirat emo i definirati funkciju:
void ispisiKvadrat(float x) {
cout << (x * x) << endl;
return;
}
Zadatak. Napiite program za odreivanja dana u tjednu koritenjem dviju funkcija ije
su deklaracije:
160
Prva funkcija neka pretvara zadani datum u cijeli broj (izmeu 0 i 6), a druga funkcija
shodno tom broju neka ispisuje tekst naziva dana u tjednu.
Budui da funkcija tipa void nita ne vraa pozivnom kdu, naredba return ne smije
sadravati nikakav podatak. tovie, u ovakvim sluajevima se naredba return moe i
izostaviti.
Naredba return je obavezna za izlazak iz funkcija koje vraaju neki
rezultat. Za funkcije tipa void naredba return se moe izostaviti u tom
sluaju prevoditelj e shvatiti da je izlaz iz funkcije na kraju njene definicije.
Funkcija tipa void ne moe se koristiti u aritmetikim operacijama, pa emo gore
definiranu funkciju ispisiKvadrat() stoga uvijek pozivati na sljedei nain:
ispisiKvadrat(10);
// pogreka
Povratak iz funkcije je mogu s bilo kojeg mjesta unutar tijela funkcije. Stoga se
naredba return moe pojavljivati i na vie mjesta. Takav primjer imamo u sljedeoj
funkciji:
void lakonski(int odgovor) {
switch (odgovor) {
case 0:
cout << "Ne";
return;
// 1. return
case 1:
cout << "Da";
return;
// 2. return
}
cout << "Nekada sam bio tako neodluan,"
" a moda i nisam?!";
}
// 3. return
161
Neke funkcije za svoj rad ne iziskuju argumente takve funkcije imaju praznu listu
argumenata, tj. unutar zagrada se u deklaraciji ne navode argumenti. Tako smo u
dosadanjim primjerima funkciju main() deklarirali bez argumenata:
int main() {
// ...
return 0;
}
Uobiajeni nain prijenosa argumenta u funkcije jest prijenos vrijednosti podatka, kao
to smo to ve radili s funkcijama faktorijel(), odnosno kvadrat() u ovom
162
poglavlju. Meutim, vrlo je vano uoiti da prilikom takvog poziva funkcije formalni
argument i vrijednost koja se prenosi nisu meusobno povezani. Formalni argument
ivi samo unutar funkcije te je njegova vrijednost po izlasku iz funkcije izgubljena.
Ako u funkciji mijenjamo vrijednost argumenta, promjena se nee odraziti na objekt
koji smo naveli u listi prilikom poziva. Pogledajmo na jednom banalnom primjeru
kakve to ima praktine posljedice:
#include <iostream.h>
int DodajSto(float i) {
i += 100;
return i;
}
int main() {
int n = 1;
DodajSto(n);
cout << "Radio " << n << endl;
n = DodajSto(n);
cout << "Radio " << n << endl;
return 0;
}
// ispisuje 1
// ispisuje 101
Iako funkcija DodajSto() u svojoj definiciji uveava vrijednost argumenta, ona barata
samo s lokalnom varijablom koja se prilikom poziva inicijalizira na vrijednost stvarnog
argumenta. Pojednostavljeno reeno, funkcija je napravila kopiju argumenta te cijelo
vrijeme radi s njom. Prilikom izlaska se kopija unitava, jer je ona definirana samo
unutar funkcijskog bloka. Zbog toga e nakon prvog poziva funkcije varijabla n i dalje
imati istu vrijednost kao i prije poziva. Ovakav prijenos vrijednosti funkciji naziva se
prijenos po vrijednosti (engl. pass by value).
Kako se uveana vrijednost vraa kao rezultat funkcije, tek nakon drugog poziva
funkcije, tj. pridruivanja povratne vrijednosti varijabli n, varijabla n e doista biti
uveana.
itatelju naviknutom na osobine nekih drugih programskih jezika (npr. BASIC,
FORTRAN) init e se ovakvo ponaanje argumenata funkcije vrlo nespretnim i
neshvatljivim. Meutim, ovakav pristup ima jednu veliku odliku: zatitu podataka u
pozivajuem kdu. Radi ilustracije, pretpostavimo da se prilikom izvoenja funkcije
zaista mijenja i vrijednost stvarnog argumenta (nazovimo to BASIC-pristup). Uz takvu
pretpostavku, gornji program bi ispisao brojeve 101 i 201. Razmotrimo sada kakve bi
posljedice u takvom sluaju imala naizgled banalna promjena definicije funkcije
DodajSto(). Na primjer, definiciju funkcije e netko napisati krae kao:
int DodajSto(int i) {
return i + 100;
}
163
Ovime se smisao funkcije nije promijenio ona i nadalje kao rezultat vraa broj jednak
argumentu uveanom za 100. Meutim, u tijelu funkcije se vrijednost formalnog
argumenta vie ne mijenja, tako da bi nakon ovakve preinake uz BASIC-pristup konani
ishod programa bio drukiji. Naprotiv, ispis uz ne-BASIC-pristup e biti uvijek isti,
togod mi radili s argumentom unutar funkcije, uz pretpostavku da je povratna
vrijednost u naredbi return pravilno definirana.
Ovakvim pristupom u jeziku C++ u velikoj su mjeri izbjegnute popratne pojave
(engl. side-effects) koje mogu dovesti do neeljenih rezultata, budui da je koliina
podataka koji se izmjenjuju izmeu pozivnog kda i funkcije svedena na najmanju
moguu mjeru: na argumente i povratnu vrijednost.
Kada se funkciji argumenti prenose po vrijednosti, tada se njihova vrijednost
u pozivajuem kdu ne mijenja.
Ako su stvarni argument i formalni argument u deklaraciji razliitih tipova, tada se
prilikom inicijalizacije formalnog argumenta na stvarni argument primjenjuju
uobiajena pravila konverzije, navedena u poglavlju 2.
Kao stvarni argument funkcije moe se upotrijebiti i neki izraz. U takvim
sluajevima se izraz izraunava prije poziva same funkcije. Tako bismo tablicu kvadrata
brojeva mogli ispisati i pomou sljedeeg kda:
int i = 0;
while (i < 10) cout << kvadrat(++i) << endl;
// promjenjivi rezultat!
164
// ispisuje 27
Prilikom poziva funkcije zamijeni(a, b), unutar funkcije vrijednosti varijabli prvi i
drugi e biti zamijenjene. Dakle, algoritam je sutinski korektan. Meutim, po izlasku
iz funkcije ta zamjena nema nikakvog efekta, jer je funkcija baratala s preslikama
vrijednosti varijabli a i b, a ne s izvornicima.
Jedno mogue rjeenje je upotreba pokazivaa prilikom poziva funkcije. Umjesto
vrijednosti, funkciji emo proslijediti pokazivae na objekte. Tada emo funkciju
definirati ovako:
void zamijeniPok(int *prvi, int *drugi) {
int segrt = *prvi;
*prvi = *drugi;
*drugi = segrt;
}
Prilikom poziva, umjesto stvarnih vrijednosti a i b proslijedit emo adrese tih objekata:
zamijeniPok(&a, &b);
165
Poziv funkcije sada nije potrebno komplicirati operatorom &, ve je dovoljno napisati:
zamijeniRef(a, b);
Sutinski gledano, nema razlike izmeu pristupa preko pokazivaa ili referenci: rezultat
je isti. Reference su u biti pokazivai koje nije potrebno dereferencirati prilikom
koritenja pa se mehanizam prenoenja se u jednom i u drugom sluaju na razini
generiranog strojnog kda odvija preko pokazivaa. Meutim, ve je na prvi pogled
uoljiva vea jednostavnost kda ako koristimo reference. To se odnosi na definiciju
funkcije gdje nije potrebno koristiti operator dereferenciranja *, a naroito na poziv
funkcije, jer ne treba navoditi operator adrese &. Zato itatelju najtoplije preporuujemo
koritenje referenci. Posebno se to odnosi na C-gurue naviknute iskljuivo na
pokazivae, budui da u programskom jeziku C reference ne postoje kao tip podataka.
Podsjetimo se da se pokazivai i reference na razliite tipove podataka ne mogu
pridruivati. Pokuamo li funkciju zamijeniPok() pozvati tako da joj prenesemo kao
argument pokaziva na neto to nije int, prevoditelj e javiti pogreku prilikom
prevoenja:
float a = 10.;
int b = 3;
zamijeniPok(*a, *b);
// pogreka: a je float
166
float a = 10.;
int b = 3;
zamijeniRef(a, b);
// oprez!
Poetnici su esto vrlo zbunjeni kad naiu na potrebu da unutar funkcije promijene
vrijednost nekog pokazivaa proslijeenog kao parametar. Na primjer, zamislimo da
elimo napisati funkciju UnesiIme() koja treba rezervirati memorijski prostor te
uitati ime korisnika. Pri tome je sasvim logino ime proslijediti kao parametar. No
postavlja se pitanje kojeg tipa mora biti taj parametar. Prosljeivanje pokazivaa na
znak nee biti dovoljno:
167
// lo poziv
Na veliko razoaranje C++ utokljunaca, gornji primjer nee ispisati uneseno ime, ve
e po svoj prilici izbaciti neki nesuvisli niz znakova. U emu je problem?
Do kljua za razumijevanja gornjeg problema doi emo ako se podsjetimo da se
parametri u sve funkcije prenose po vrijednosti. To znai da se parametar naveden u
pozivu funkcije kopira u privremenu varijablu koja ivi iskljuivo za vrijeme izvoenja
funkcije. Parametar moemo slobodno mijenjati unutar funkcije, a promjene se nee
odraziti na stvarni parametar naveden u pozivajuem kdu. Upravo se to dogaa s naim
parametrom korisnik prilikom poziva se vrijednost pokazivaa korisnik kopira u
privremenu varijablu ime. Funkcija UnesiIme() barata s tom privremenom vrijednosti,
a ne sa sadrajem varijable korisnik. Adresa memorijskog bloka alociranog
operatorom new se pridruuje lokalnoj varijabli, te se na to mjesto uitava znakovni niz.
Nakon to funkcija zavri, vrijednost varijable ime se gubi, a varijabla korisnik ostaje
nepromijenjena.
Rjeenje ovog problema nije tako sloeno: umjesto prosljeivanja vrijednosti
varijable korisnik, potrebno je funkciji proslijediti njenu adresu. Parametar funkcije
e sada postati pokaziva na pokaziva na znak: sama varijabla korisnik je tipa
pokaziva na znak, a njena adresa je pokaziva na pokaziva. Unutar funkcije
UnesiIme() takoer treba voditi o tome rauna, tako da se vrijednosti pokazivaa
pristupi pomou *ime. Evo ispravnog programa:
void UnesiIme(char **ime) {
*ime = new char[100];
cin >> *ime;
// parametar je pokaziva
// na pokaziva
// pristup preko pokazivaa
}
int main() {
char *korisnik;
UnesiIme(&korisnik);
cout << korisnik << endl;
delete [] korisnik;
}
// prosljeuje se adresa
// uvijek poistite za sobom!
168
// parametar je referenca
// na pokaziva
// pristup preko reference
}
int main() {
char *korisnik;
UnesiIme(korisnik);
// prosljeuje se adresa,
// ali to nije potrebno
// eksplicitno navesti
169
lan. Stoga funkcija preko tog pokazivaa rukuje s izvornikom, a ne preslikom tog polja.
Shodno tome, gornju funkciju smo mogli potpuno ravnopravno deklarirati i na sljedei
nain:
void Pocisti(int *polje, int duljina);
Iako ovakvo ponaanje polja kao argumenta unosi odreenu nedosljednost u jezik, ono
je posve praktine naravi. Zamislimo da treba funkciji kao argument prenijeti polje od
nekoliko tisua lanova. Kada bi pri pozivu funkcije lanovi polja inicijalizirali lanove
novog, lokalnog polja u funkciji, proces pozivanja funkcije bi trajao vrlo dugo, ne samo
zbog operacija pridruivanja pojedinih lanova, ve i zbog vremena potrebnog za
dodjeljivanje memorijskog prostora za novo polje. Osim toga, lokalne varijable unutar
funkcije se smjetaju na stog, posebni dio memorije ija je duljina ograniena. Smjetaj
dugakih polja na stog vrlo bi ga brzo popunio i onemoguio daljnji rad programa.
Prilikom deklaracije je mogue, ali nije potrebno navesti duljinu polja (ograniimo
se za sada na jednodimenzionalna polja) prenosi se samo pokaziva na prvi lan. Zbog
toga, sljedee tri deklaracije gornje funkcije su iste:
void Pocisti(int polje[], int duljina);
void Pocisti(int polje[5], int duljina);
void Pocisti(int *polje, int duljina);
Iako postoji standardna funkcija strlen() koja rauna duljinu znakovnog niza,
napisali smo svoju inaicu takve funkcije. Uoimo u uvjetu while petlje kako je zbog
vieg prioriteta operatora * bilo neophodno staviti zagrade oko operacije pribrajanja
pokazivau. Da te zagrade nema, uvjet petlje bi dohvaao prvi lan polja, te ono to se
tamo nalazi (kd znaka) uveao za broja i.
170
Iz poziva funkcije je jasno da se matrica prenosi preko pokazivaa na prvi lan. Stoga je
navedeni zapis u deklaraciji funkcije ovakav samo radi bolje razumljivosti. Varijable
redaka i stupaca deklarirane su ispred tijela funkcija ispisiMatricu() i main(),
tako da su one dohvatljive iz obiju funkcija (o podruju dosega imena govorit emo
opirnije u zasebnom poglavlju).
Zadatak. Napiite funkciju ispisiMatricu() koristei pokazivae i aritmetiku s
pokazivaima. U funkciju prenesite samo pokaziva na prvi lan.
Gornja funkcija e raditi za proizvoljan broj redaka, pa smo podatak o broju redaka
prenijeli kao poseban argument. Potekoe iskrsavaju ako dimenzije polja nisu zadane u
171
Argument m[][] u deklaraciji funkcije nije dozvoljen, jer u trenutku prevoenja mora
biti poznata druga dimenzija (za viedimenzionalna polja: sve dimenzije osim prve).
U prethodnom poglavlju smo pokazali kako se lanovi dvodimenzionalnog polja
mogu dohvaati preko pokazivaa na pokazivae. Iskoristimo tu injenicu pri pozivu
funkcije za ispis lanova matrice:
#include <iostream.h>
#include <iomanip.h>
void ispisiMatricu(float **m, int redaka, int stupaca) {
for (int r = 0; r < redaka; r++) {
for (int s = 0; s < stupaca; s++)
cout << setw(10) << ((float*)m)[r*stupaca+s];
cout << endl;
}
}
int main() {
int redaka = 2;
const int stupaca = 3;
float (*matrica)[stupaca] = new float[redaka][stupaca];
matrica[0][0] = 1.1;
matrica[0][1] = 1.2;
matrica[0][2] = 1.3;
matrica[1][0] = 2.1;
matrica[1][1] = 2.2;
matrica[1][2] = 2.3;
ispisiMatricu((float**)matrica, redaka, stupaca);
return 0;
}
Oito je ovakva notacija teko itljiva. Daleko elegantnije rjeenje pruaju korisniki
definirani tipovi klase, koje se mogu tako definirati da sadre podatke o pojedinim
dimenzijama te da sadre funkcije za ispis lanova. Klase e biti opisane u narednim
poglavljima, a za sada uoimo jo samo da je u gornjem kdu stupaca bilo neophodno
172
deklarirati kao const, budui da druga dimenzija polja mora biti poznata u trenutku
prevoenja koda.
7.4.6. Konstantni argumenti
Svaki pokuaj promjene sadraja tog niza unutar funkcije prouzroit e pogreku
prilikom prevoenja:
int DuljinaNiza(const char *niz) {
// ...
*niz = 0;
// pogreka: pokuaj promjene
return i;
// nepromjenjivog objekta
}
173
Sada vie nije mogue prosljeivati kao parametar pokazivae na konstantne objekte,
jer postoji opasnost da e se vrijednost konstantnog objekta promijeniti u funkciji:
int main() {
const char* TkoPjeva = "Franjo afranek";
cout << DuljinaNiza(TkoPjeva);
// pogreka
return 0;
}
Postoje funkcije kojima se u veini poziva prenosi jedna te ista vrijednost argumenta ili
argumenata. Na primjer, u funkciji za raunanje odreenog integrala trapeznom
formulom tipina relativna pogreka rezultata moe biti vrijednost 104. Kako bi se
izbjeglo suvino navoenje eljene pogreke u sluaju da tipina pogreka zadovoljava,
prilikom poziva se pogreka moe izostaviti, a u deklaraciji funkcije navesti
podrazumijevana vrijednost pogreke.
Na poetku ovog poglavlja, u programu za raunanje binomnih koeficijenata (vidi
kd na str. 152) pretpostavili smo da imamo na raspolaganju funkciju faktorijel().
Bez ikakve optimizacije, binomne koeficijente smo raunali doslovno preko formule
p!
p
=
r
r !( p r )!
Svakom imalo bistrijem matematiaru jasno je da se dio umnoka u faktorijeli brojnika
moe pokratiti s (p r)! u nazivniku, tako da se ukupni broj mnoenja u raunu
smanjuje. Na primjer:
5 = 5! = 5 4 3 2 1 = 5 4
2 2! 3! (2 1)(3 2 1) 2 1
Kako iskoristiti tu injenicu u programu za raunanje binomnih koeficijenata? Mogli
bismo napisati dvije funkcije: jednu za raunanje faktorijele (u nazivniku), drugu za
174
raunanje umnoka svih cijelih brojeva izmeu dva zadana (u brojniku). Time e se
ukupni broj mnoenja u raunu doista smanjiti, ali e se poveati broj funkcija u
izvornom kdu.
Obje tako definirane funkcije u sutini bi provodile isti postupak: mnoenje
uzastopnih cijelih brojeva. Umjesto da definiramo dvije odvojene funkcije, moemo
poopiti funkciju faktorijel() tako da proirimo listu argumenata dodatnim
parametrom stojBroj koji e ograniavati broj mnoenja. Za uobiajeni faktorijel taj
argument bi imao vrijednost 1. Da bismo otklonili potrebu za eksplicitnim navoenjem
te vrijednosti kod svakog poziva funkcije, definirat emo podrazumijevanu vrijednost
(engl. default argument value) tom dodatnom argumentu:
#include <iostream.h>
long faktorijel(int n, int stojBroj = 1) {
long umnozak = stojBroj;
while (++stojBroj <= n)
umnozak *= stojBroj;
return umnozak;
}
int main() {
int p, r;
cout << "p = ";
cin >> p;
cout << "r = ";
cin >> r;
cout << faktorijel(p, p-r) / faktorijel(r) << endl;
return 0;
}
175
na poziv funkcije znati da ona moe imati podrazumijevane argumente. Zbog toga se
podrazumijevane vrijednosti argumenata moraju navesti u deklaraciji funkcije.
Ovo je naravno vano ako su deklaracija i definicija funkcije razdvojene. Treba li u
tom sluaju ponoviti podrazumijevanu vrijednost u definiciji funkcije? Ne, jer
prevoditelj ne usporeuje meusobno vrijednosti koje se pridruuju argumentu u
deklaraciji, odnosno definiciji, tako da e pokuaj pridruivanja u definiciji interpretirati
kao pokuaj promjene podrazumijevane vrijednosti (ak i ako su one jednake):
// ...
// deklaracija funkcije:
long faktorijel(int, int = 1);
// ...
// definicija funkcije:
long faktorijel(int n, int stojBroj = 1) { // pogreka
// redeklaracija podrazumijevane vrijednosti
// ...
izostaviti
pridruivanje
// deklaracija funkcije:
long faktorijel(int, int = 1);
// definicija funkcije:
long faktorijel(int n, int stojBroj) {
// podrazumjevana vrijednost je
// ve pridruena u deklaraciji
// ...
}
176
// pogreka
jer pridruivanje argumenata pri pozivu funkcije tee s lijeva na desno, pa e u pozivu
funkcije samo s jednim argumentom drugi argument ostati neinicijaliziran. Ako se u
deklaraciji funkcije podrazumijevane vrijednosti pridruuju nekolicini argumenata, tada
svi oni moraju biti grupirani na kraj liste parametara:
void GoodBadUgly(int, const char* = "Clint",
const char* = "Ellie",
const char* = "Lee");
// pogreka
Za neke funkcije je nemogue unaprijed tono znati broj i tip argumenata koji e se
proslijediti u pozivu funkcije. Takve funkcije se deklariraju tako da se lista argumenata
zakljuuje s tri toke (...), koje upuuju da prilikom poziva mogu uslijediti dodatni
podaci. itatelj koji je ikada vidio ili pisao programe u jeziku C zasigurno poznaje
standardiziranu funkciju printf() koja se koristi za ispis podataka (u jeziku C ne
postoje ulazni i izlazni tokovi cin, odnosno cout). Ona se moe koristi za ispis bilo
kojeg broja podataka razliitih tipova. Funkcija printf() u jeziku C++ mogla bi se
deklarirati kao:
int printf(const char* , ...);
Ovime je odreeno da funkciji printf() prilikom njena poziva treba prenijeti barem
jedan argument tipa char * (pokaziva na znakovni niz). Ostali argumenti nisu
neophodni i mogu biti proizvoljnog tipa. Zarez iza zadnjeg deklariranog argumenta nije
neophodan, tako da smo funkciju printf() mogli deklarirati i kao:
int printf(const char* ...);
177
// nepredvidiv rezultat
178
Prvo je deklariran objekt vl tipa va_list koji je definiran unutar datoteke zaglavlja
stdarg.h. Taj objekt na neki nain sadri sve argumente funkcije, te emo pomou
njega pristupati pojedinom argumentu (sama struktura objekta korisniku nije vana on
179
Za one koji ipak nee moi zaspati dok ne spoznaju to je va_list to je pokaziva na stog.
Makro funkcija va_start() inicijalizira taj pokaziva tako da pokazuje iza zadnjeg
prenesenog parametra, dok va_arg() preko njega pristupa pojedinom argumentu.
180
Potekoe, a esto i pogreke nastaju kada se iz funkcije eli prenijeti lokalni objekt
generiran pri pozivu funkcije. Uzmimo da smo poeljeli funkciju koja e sadraj jednog
znakovnog niza nalijepiti na drugi znakovni niz i novonastali niz vratiti kao rezultat.
Nazovimo tu funkciju dolijepi(); argumenti te funkcije bit e pokazivai na nizove
koje elimo spojiti, a povratna vrijednost e biti referenca na novostvoreni niz.
Deklaracija funkcije bi prema tome izgledala kao:
char &dolijepi(const char*, const char*);
181
Unutar funkcije se generira lokalno polje spojeni u koje se stapaju znakovni nizovi na
koje pokazuju argumenti funkcije. Pri izlasku iz funkcije prenosi se pokaziva na to
polje pozivnom kdu. Meutim, kako se lokalno polje spojeni izlaskom iz funkcije
gubi iz vidokruga, rezultat je nepredvidiv.
Kao rezultat funkcije se nikada ne smije vraati referenca ili pokaziva na
lokalni objekt generiran unutar funkcije.
elimo li ipak da gornja funkcija kao rezultat vrati znakovni niz, morat emo kao
parametar proslijediti pokaziva na memorijsko podruje u koje elimo smjestiti
rezultat:
char *dolijepi(const char *prvi, const char *drugi,
char *spojeni);
Time smo vrue kestenje prebacili na pozivajui kd, koji sada preuzima brigu o
alociranju i oslobaanju prostora za znakovni niz, a pozvana funkcija e samo mijenjati
sadraj alociranog prostora.
Zadatak: Napiite kd za ovako deklariranu funkciju dolijepi(). Obratite panju na
vraanje vrijednosti.
182
njihov ivot vezan za izvoenje funkcije. Takvi objekti se zovu lokalni objekti, jer
vrijede lokalno za samu funkciju. Objekt nije vezan striktno za funkciju, ve za blok u
kojemu je deklariran (vidi poglavlje 3.1), no funkcija u biti nije nita drugo nego blok.
Na primjer:
void sortiraj(int *polje, int duljina) {
int i, j;
for (i = duljina - 1; i > 0; i--)
for (j = 0; j < i; j++)
if (polje[j] > polje[j + 1]) {
int priv = polje[j + 1];
polje[j + 1] = polje[j];
polje[j] = priv;
}
}
U gornjem primjeru varijable i i j su brojai koji nemaju smisla izvan funkcije. Zbog
toga se oni deklariraju kao lokalni. Memorija se dodjeljuje na ulasku u funkciju i
oslobaa na izlasku iz nje. Varijabla priv je takoer lokalna, s time da je ona
deklarirana u ugnjeenom bloku. Ona ivi samo unutar tog bloka, stvara se na poetku
bloka i unitava na kraju.
Za lokalne varijable se kae da imaju automatsku smjetajnu klasu (engl. automatic
storage class) koja se ispred deklaracije moe eksplicitno navesti kljunom rijei auto:
auto int priv;
183
// deklaracija funkcije
184
// globalne varijable
int main() {
dan = 30;
mjesec = 1;
godina = 1997;
mjesec = noviMjesec();
return 1;
}
int noviMjesec() {
if (++mjesec > 12) {
mjesec = 1;
godina++;
}
return mjesec;
}
int main() {
int lokalna;// neinicijalizirana lokalna varijabla
185
prva naredba za ispis na zaslonu ispisati nulu, a druga neki neodreeni broj koji e
varirati ovisno o uvjetima pod kojima je program preveden i pokrenut.
Zadatak: Razmislite i provjerite to e biti ispisano u gornjem primjeru, ako se
globalna i lokalna deklariraju kao pokazivai na znakovne nizove.
186
int a = 10;
// globalna varijabla
int main() {
float a = 50;
cout << a << endl;
}
// lokalna varijabla
// ispisuje 50
// globalna varijabla
int main() {
float a = 50;
cout << a << endl;
cout << ::a << endl;
}
// lokalna varijabla
// lokalna varijabla: 50
// globalna varijabla: 10
Lokalne varijable unutar funkcije se inicijaliziraju svaki puta prilikom poziva funkcije.
Meutim, postoje situacije kada je poeljno da se vrijednost neke varijable inicijalizira
samo pri prvom pozivu funkcije, a izmeu poziva te funkcije da se vrijednost uva.
Istina, takva se varijabla moe deklarirati kao globalna, ali e tada biti dohvatljiva i iz
drugih funkcija i izloena opasnosti od nekontrolirane promjene. Rjeenje za ovakve
sluajeve pruaju statiki objekti (engl. static objects). Ove objekte treba razlikovati od
statikih globalnih objekata iz prethodnog odsjeka ovdje se radi o statikim
objektima unutar funkcija.
Dodavanjem kljune rijei static ispred oznake tipa, objekt postaje statiki on
se inicijalizira samo jednom, prilikom prevoenja, te se takav lan pohranjuje u datoteku
zajedno s izvedbenim kdom. Ilustrirajmo to sljedeim primjerom:
#include <iostream.h>
#include <stdlib.h>
void VoliMeNeVoli() {
static bool VoliMe = false;
VoliMe = !VoliMe;
if (VoliMe)
cout << "Voli me!" << endl;
else
cout << "Ne voli!" << endl;
// statiki objekt
187
}
int main() {
int i = rand() % 10 + 1;
while (i--)
VoliMeNeVoli();
}
// sluajni broj od 1 do 10
me!
me!
me!
me!
Statiki objekti u funkcijama ive za vrijeme cijelog izvoenja programa, ali su dostupni
samo unutar funkcije u kojoj su deklarirani. Te statike objekte moemo shvatiti kao da
su deklarirani izvan funkcije koristei smjetajnu klasu static, s time da im se moe
pristupiti samo iz funkcije u kojoj su deklarirani. Zato se i koristi ista kljuna rije
static: smjetaj ovakvih objekata i globalnih statikih objekata se provodi na isti
nain (u podatkovni segment programa), pa e i statiki objekti imati poetnu vrijednost
0 ako se ne inicijaliziraju drukije.
188
Izvoenje prevedenog kda bilo bi bre kada bi svaki poziv funkcije kvadrat() u
programu bio jednostavno zamjenjen naredbom za meusobno mnoenje dva ista broja.
Dodavanjem kljune rijei inline ispred definicije funkcije daje se naputak
prevoditelju da pokua svaki poziv funkcije nadomjestiti samim kdom funkcije. Takve
se funkcije nazivaju umetnute funkcije (engl. inline function). Evo primjera:
inline double kvadrat(float x) {
return x * x;
}
189
No to uiniti ako su nam u programu potrebna oba oblika funkcije kvadrat(), tj. kada
elimo kvadrirati i cijele i decimalne brojeve? float inaica funkcije e podravati oba
tipa podatka, ali e, kao to smo ve primijetili, iziskivati nepotrebne konverzije tipa.
Programski jezik C++ omoguava preoptereenje funkcija (engl. function
overloading) koritenje istog imena za razliite inaice funkcije. Pritom se funkcije
moraju meusobno razlikovati po potpisu, tj. po tipu argumenata u deklaraciji funkcije.
Prevoditelj e prema tipu argumenata sam prepoznati koju inaicu funkcije mora za
pojedini poziv upotrijebiti. Tako e u kdu:
float kvadrat(float);
int kvadrat(int);
int main() {
float x = 0.5;
int n = 4;
// deklaracije preoptereenih
// funkcija
190
// float kvadrat(float)
// int kvadrat(int)
// deklaracije preoptereenih
// funkcija
int main() {
double dupliX = 3456.54321e49;
cout << kvadrat(dupliX) << endl;
}
// pogreka!
// pogreka: nedoumica je li
// kvadrat(long) ili kvadrat(float)
// isti problem
191
// samo deklaracije
int main() {
float x = -23.76;
cout << apasalutno(x) << endl;
cout << apasalutno(-473) << endl;
}
Ako funkcija ima vie argumenata, tada se gornji postupak nalaenja odgovarajue
funkcije provodi za svaki od argumenata. Ako jedna od funkcija osigurava bolje
slaganje za jedan od argumenata, a barem jednako dobro slaganje za ostale argumente,
ona e biti odabrana.
192
Budui da bilo koji tip T (na primjer int) i referenca na taj tip T & (na primjer
int &) prihvaaju jednake inicijalizirajue vrijednosti, funkcije iji se argumenti
razlikuju samo po tome radi li se o vrijednosti ili referenci na nju, ne mogu se
preoptereivati. Zato e prevoditelj prilikom prevoenja sljedeeg kda prijaviti
pogreku:
void f(int i) {
//...
}
void f(int &refi) {
//...
}
193
long kvadrat(int);
long kvadrat(long);
dok emo za cjelobrojne potencije koristiti petlju u kojoj emo mantisu mnoiti
odreeni broj puta. Za raunanje prirodnog logaritma i eksponencijalne funkcije koristit
emo standardne funkcije log(), odnosno exp() deklarirane u math.h datoteci
zaglavlja.
#include <iostream.h>
#include <math.h>
double mocnica(double x, double y) {
if (x <= 0.) {
cerr << "Nemoan sam to izraunati!" << endl;
return 0.;
}
return exp(y * log(x));
}
double mocnica(double x, int y) {
int brojac = y > 0 ? y : - y;
double rezultat = 1.;
while (brojac-- > 0)
rezultat *= x;
return y > 0 ? rezultat : 1. / rezultat;
}
194
// ispisuje -27
// ponovno
// ispisuje poruku
195
7.9. Rekurzija
Pozivom funkcije formiraju se unutar nje lokalne varijable koje su nedohvatljive izvan
same funkcije. Neovisnost lokalnih varijabli od okolnog programa omoguava da
funkcija poziva samu sebe ponovnim pozivom se stvaraju nove lokalne varijable
potpuno neovisne o varijablama u pozivajuoj funkciji. Ovakav proces uzastopnog
pozivanja naziva se rekurzija (engl. recursion). Razmotrimo rekurziju na jednostavnom
primjeru: funkciji za raunanje faktorijela:
int faktorijel(int n) {
return (n > 1) ? (n * faktorijel(n - 1)) : 1;
}
196
faktorijel(3);
n = 3;
faktorijel(n-1);
n = 2;
faktorijel(n-1);
n = 1;
return 1;
return n*faktorijel(n-1);
// vraa 2
return n*faktorjel(n-1);
// vraa 6
nf = faktorjel(3);
takve formule implementirati jednostavnim petljama. Rekurzije mogu biti vrlo efikasno
sredstvo za rukovanje posebnim strukturama podataka.
Ako to nije mogue, bit emo prisiljeni za razliite podintegralne funkcije svaki puta
mijenjati izvorni kd funkcije Integral().
Rjeenje za probleme ovakvog tipa pruaju pokazivai na funkcije. Osim to
funkcija moe primati odreene argumente i vraati neki rezultat, ona u izvedbenom
kdu programa zauzima i odreeni prostor u memoriji raunala. Kada pozivamo neku
197
Prvi argument funkcije Integral() je pokaziva na funkciju f() tipa double koja
kao argument prihvaa samo jedan podatak tipa double. Ispred imena parametra u listi
argumenata nalazi se operator dereferenciranja * koji ukazuje na to da emo funkciji
Integral() prenijeti pokaziva, a ispred operatora dereferenciranja je identifikator
povratnog tipa (double) funkcije f(). Iza pokazivaa na funkciju f() unutar zagrada
se navode tipovi argumenata funkcije. Vjerojatno ne treba naglaavati da se ti argumenti
moraju slagati s argumentima funkcije koja e se pozivati po broju i tipu (ili se stvarni
argumenti moraju dati svesti na formalne ugraenim ili korisniki definiranim
pretvorbama).
Uoimo zagrade oko oznake za pokaziva f na funkciju. One su neophodne, jer bi u
protivnom:
float Integral(double *f(double), double x0, double xn);
// pogreno: deklaracija funkcije f
zbog vieg prioriteta zagrada nad operatorom *, prvi argument bio interpretiran kao
deklaracija funkcije f(double) koja kao rezultat vraa pokaziva na double.
Posvetimo se sada definiciji funkcije Integral(). Za poetak se podsjetimo
ukratko o emu se u trapeznom pravilu radi. Interval x0, xn unutar kojeg se funkcija
f (x) eli integrirati podijeli se na n jednakih intervala, izmeu kojih se funkcija
aproksimira pravcima (slika 7.3). Integral funkcije (tj. povrina ispod funkcije) priblino
je jednaka zbroju povrina tako dobivenih trapeza (rafirane plohe na slici):
xn
f ( xn )
f ( x0 )
+ f ( x1 ) + f ( x2 ) + + f ( xn 1 ) +
2
2
f ( x) dx =&h
x0
x n x0
n
198
f ( x0 ) f ( x1 )
I 0 = h0
+
,
2
2
h0 = xn x0
h0 f ( x0 )
f ( x2 )
+ f ( x1 ) +
2 2
2
I2 =
h0 f ( x0 )
f ( x4 )
+ f ( x1 ) + f ( x2 ) + f ( x3 ) +
4 2
2
...
Budui da se broj podintervala podvostruuje, u svakom koraku se raunaju vrijednosti
podintegralne funkcije i za sve toke obraene u prethodnom koraku. Da se izbjegne
nepotrebno ponavljanje rauna, podintegralnu funkciju je dovoljno raunati samo za
nove toke i tu novu sumu (nazvat emo ju sumaNova) pridodati sumi iz prethodnog
koraka (sumaStara). Postupak e se ponavljati sve dok su zadovoljena dva uvjeta:
1. relativna promjena sume je vea od unaprijed dozvoljene vrijednosti koju funkciji
predajemo kao parametar relGrijeh, te
2. broj podintervala je manji ili jednak od broja CircusMaximus, kojeg takoer
prenosimo funkciji kao parametar.
Kako korisnik ne bi morao svaki puta prenositi vrijednosti ova dva parametra, pridruit
emo im podrazumijevane vrijednosti. Pogledajmo cjelokupni izvorni kd:
#include <iostream.h>
#include <math.h>
float Integral(double(*f)(double), double x0, double xn,
double relGrijeh = 1.e-5,
long CircusMaximus = 65536L);
f ( x)
x0
x1
x2
xn1
xn
199
200
mogue zamke u uvjetu (3). Naime, ako se kojim sluajem dogodi da sumaNova bude
jednaka 0, dijeljenje u treem uvjetu uzrokovat e prekid programa (dijeljenje s
nulom). S novododanim uvjetom (2) to se nee dogoditi, jer u sluaju da on nije
zadovoljen, nee se niti poeti ispitivati trei uvjet. Izvoenje petlje e se prekinuti, a
itatelju ostavljamo na razmiljanje hoe li rezultat funkcije biti ispravan.
Radi bolje ilustracije, pogledajmo uvjet u sljedeem if-grananju (log() je
funkcija koja vraa prirodni logaritam ln argumenta):
if ((x > 0) && (log(x) < 10) {
//...
}
Iako funkcija logiki-i ima svojstvo komutacije, tj. logiki rezultat ne ovisi o tome koji
je operand s lijeve, a koji s desne strane, samo e gornji redoslijed operanada uvijek
osiguravati pravilno izvoenje gornjeg kda. Ako nije ispunjen prvi uvjet (tj. ako je x
negativan ili jednak 0), daljnje ispitivanje nema smisla i ono se ne provodi, tako da se uz
navedeni redoslijed ne moe funkciji log() prenijeti negativni argument.
Ekvivalentna situacija je i kada u uvjetu imamo logiki-ili. Izvrnimo uvjet u
gornjem if-grananju i napiimo kd:
if ((x <= 0) || (log(x) < 10) {
//...
}
U ovom sluaju, im je zadovoljen prvi uvjet (x <= 0), bit e zadovoljen cijeli uvjet,
tako da se daljnje ispitivanje ne provodi opet je izbjegnuta mogunost da se funkciji
log() prenese negativni argument.
Iz gornjeg primjera je vidljivo da je sintaksa za deklaraciju pokazivaa na funkcije
dosta zamrena. Ako se pokaziva na funkciju esto koristi, jednostavnije je definirati
novi sinonim za taj tip pomou kljune rijei typedef:
typedef double (*pokFunkcija)(double);
201
Funkcija se poziva preko pokazivaa tako da se navede naziv pokazivaa iza kojega se u
zagradama specificiraju parametri. Pri tome pokaziva se moe prije poziva
dereferencirati operatorom *, ali i ne mora (dereferenciranje se podrazumijeva):
mojaFunkcija(5.);
(*mojaFunkcija)(5.);
Potpun tip pokazivaa na funkciju odreuju povratna vrijednost funkcije te broj i tip
argumenata. Zbog toga nije mogue pokazivau dodijeliti adresu funkcije koja ima
drukiji potpis:
double xy(double x, double y);
mojaFunkcija = xy;
// pogreka: nekompatibilni tipovi
// dozvoljeno
Pri tome, ako sada funkciju elimo pozvati pomou pokazivaa pok podrazumijevane
parametre ne moemo koristiti:
pok(60);
pok();
// OK
// pogreka: pok nema podrazumijevanih
// parametara
202
7.11.
Funkcija main()
koja ima praznu listu argumenata (ovaj oblik smo do sada iskljuivo i koristili), ili
int main(int argc, char *argv[]) {
//...
}
U ovom drugom obliku, prvi argument argc jest broj parametara koji se prenose
programu iz radne okoline unutar koje je program pokrenut. Ako je argc razliit od
nule, tada se parametri koji se prenose mogu dohvatiti preko lanova polja argv, od
argv[0] do argv[argc - 1]. Ti lanovi su pokazivai na znakovne nizove koji sadre
pojedine parametre. Tako je argv[0] pokaziva na poetak niza koji sadri ime kojim
je program pozvan ili prazan niz (""). argv[argc] je uvijek nul-pokaziva.
Ilustrirajmo dohvaanje argumenata funkcije main(), jednostavnim programom
koji ispisuje rezultat aritmetike operacije na dva broja, zadane kao parametra iza
poziva programa:
#include <iostream.h>
#include <math.h>
int main(int argc, char* argv[]) {
if (argc < 4) {
cerr << "Nedovoljno parametara!"
return 1;
}
float operand1 = atof(argv[1]);
char operacija = *argv[2];
float operand2 = atof(argv[3]);
switch (operacija) {
case('+'):
cout << (operand1 + operand2) <<
break;
case('-'):
cout << (operand1 - operand2) <<
break;
case('*'):
cout << (operand1 * operand2) <<
break;
<< endl;
endl;
endl;
endl;
203
case('/'):
cout << (operand1 / operand2) << endl;
break;
default:
cerr << "Nepoznati operator " << operacija << endl;
return 1;
}
return 0;
}
=
=
=
=
=
=
4
"racunaj.exe"
"2"
"/"
"3"
0
Prvi argument argc je jednak broju parametara koji slijede iza naredbe kojom je
program pokrenut, uveanom za 1. argv[0] jest pokaziva na znakovni niz s imenom
kojim je program pokrenut. Ponekad taj znakovni niz sadri cijelu stazu do izvrnog
"C:\\knjiga.cpp\\primjeri\\racunaj.exe").
Slijede
programa
(npr.
pokazivai na parametre, a niz se zakljuuje nul-pokazivaem argv[4].
Funkcija main() u pravilu zavrava naredbom return (koja mora sadravati
cjelobrojni parametar) koja uzrokuje unitenje svih automatskih objekata. Operacijskom
sustavu se prenosi vrijednost navedena u return naredbi. Ako je izvoenje programa
zavrilo ispravno, podrazumijeva se da povratna vrijednost bude 0; u protivnom se vraa
neka vrijednost razliita od 0 (vrijednost ovisi o operacijskom sustavu). Ako izvoenje
funkcije main() nije zakljueno naredbom return (primjerice ako ju zaboravimo
napisati), tada e ona skonati kao da se na kraju nalazi naredba
return 0;
204
Budui da u svakom C++ programu smije biti samo jedna main() funkcija, njeno se
ime ne moe preoptereivati.
Ako ne elimo posebno obavjetavati operacijski sustav o uspjenosti izvoenja
naeg programa, funkciju main() moemo deklarirati tipa void:
void main() {
// ...
}
y
= arctan
x
Pri tome su x i y pravokutne koordinate toke, a r i radijus i kut radij-vektora toke. U
kdu emo koristiti dvije standardne funkcije: sqrt() koja vraa kvadratni korijen
argumenta, te atan2(double x, double y) koja vraa arkus tangensa za
koordinate toke na apscisi i ordinati. Obje funkcije su deklarirane u datoteci math.h
205
koju ukljuujemo na poetku kda. Budui da rezultat elimo ispisati ukljuit emo i
zaglavlje iostream.h u kojem su deklarirani ulazni i izlazni tok te preoptereeni
operator <<.
#include <iostream.h>
#include <math.h>
int main() {
double x = -1;
double y = -1;
double r = sqrt(x * x + y * y);
double fi = atan2(x, y);
cout << "r =" << r << " fi =" << fi << endl;
return 0;
}
Valja uoiti da postoji i funkcija atan(double) koja kao argument prihvaa tangens
kuta koji treba izraunati. Meutim, funkcija atan2() je prikladnija, jer daje toan kut
u sva etiri kvadranta (funkcija atan() daje rezultate samo u prvom i etvrtom
kvadrantu), te za kuteve vrlo blizu /2 i /2, tj. kada je x = 0.
Ako se neka biblioteka zaboravi ukljuiti, prevoditelj e javiti pogreku tijekom
prevoenja. Ukljuivanje suvinih biblioteka (tj. biblioteka iz koje nisu koritene
funkcije) nee imati nikakve kobne posljedice na izvrni kd, jedino e se sam program
dulje prevoditi. Mnogi dananji povezivai (linkeri) provjeravaju koje funkcije se
pozivaju u programu, te samo njih ukljuuju u izvrni kd.
U tablici 7.1 su navedene neke od standardiziranih biblioteka te opis funkcija koji
se u njima nalaze. Vjerujemo da su veini itatelja najzanimljivije matematike funkcije
i funkcije za obradu znakovnih nizova, deklarirane u math.h, odnosno string.h. U
prilogu B na kraju knjige zainteresirani itatelj moe nai popis najee koritenih
funkcija, te njihovo kratko objanjenje.
Tablica 7.1. Neke ee koritene standardne datoteke zaglavlja
Naziv
Opis
complex.h
fstream.h
float.h
iomanip.h
iostream.h
limits.h
locale.h
math.h
stdarg.h
stdlib.h
string.h
time.h
206
// preusmjerava pokaziva
Gornji odsjeak e samo preusmjeriti pokaziva zn2 na poetak znakovnog niza zn1.
Budui da se pritom nee stvoriti novi znakovni niz, svaka promjena na nizu zn2 e se
odraziti i na nizu zn1, to vjerojatno nije ono to bismo oekivali od operatora
pridruivanja. Takoer, jednostavnim operatorom zbrajanja ne moemo nadovezati dva
znakovna niza:
char *zn1 = "Dora ";
char *zn2 = "Krupieva";
char *zn3 = zn1 + zn2;
// pogreka: nepravilno
// zbrajanje pokazivaa
207
strcat(oba, praznina);
strcat(oba, drugi);
int usporedba = strcmp(oba, prvi);
if (usporedba) {
if (usporedba > 0)
cout << "\"" << oba << "\" je vei od \""
<< prvi << "\"" << endl;
else
cout << "\"" << prvi << "\" je vei od \""
<< oba << "\"" << endl;
}
else
cout << " \"" << prvi << "\" i \"" << oba
<< "\" su jednaki" << endl;
return 1;
}
a l i \0
strcpy(oba, prvi);
char *oba
a l i \0
208
char *oba
a l i \0
char *praznina
\0
strcat(oba, praznina);
char *oba
a l i
\0
// opasno!
e biti preveden, ali e prilikom izvoenja funkcija strcpy() preslikati znakovni niz
u dio memorije koji ne pripada (samo) nizu replika, to e zasigurno poluiti
neeljene efekte.
Zadatak. Razmislite to ne valja u sljedeem kdu:
char broj[3];
strcpy(broj, "pet");
// potencijalna opasnost!
Ako niste sigurni u odgovor, isprobajte ovaj kd dodajui naredbu za ispis niza broj
nakon poziva funkcije strcpy(). Sam primjer je ve sam za sebe potencijalno
opasan, ali ak i ako se preivi kopiranje, imat emo problema ako niz broj kasnije
pokuamo preslikati u neki drugi niz (razmislite zato). Ovakva pogreka je vrlo esta u
C++ poetnika.
Funkcija strcat() se koristi za nadovezivanje sadraja dva znakovna niza. Ona
ima deklaraciju sljedeeg oblika:
char *strcat(char *sprijeda, const char *straga);
Djeluje tako da nadovezuje znakovni niz straga na znakovni niz sprijeda. Prilikom
preslikavanja niza straga preslikava se i njegov zakljuni nul-znak (vidi sliku 7.5).
Kao i kod funkcije strcpy(), programer mora paziti je li za rezultantni niz
(zajedno sa zakljunim nul-znakom) osiguran dovoljan prostor, kako se preslikavanjem
ne bi zalo u memorijski prostor rezerviran za druge varijable ili izvedbeni kd. Stoga
nije na odmet jo jednom naglasiti:
209
// pogreka
Ona obavlja abecednu usporedbu sadraja dvaju niza, znak po znak sve dok su
odgovarajui znakovi u oba niza meusobno jednaki ili dok ne naleti na zakljuni nulznak u jednom od nizova. Ako je prvi niz vei (u abecednom slijedu dolazi iza
drugoga), funkcija kao rezultat vraa cijeli broj vei od nule; ako su nizovi potpuno
jednaki vraa se nula, a ako je drugi niz vei, vraa se negativni cijeli broj.
Funkcija strcmp() razlikuje velika i mala slova. Preciznije, usporedba se obavlja
prema ASCII nizu znakova u kojem sva velika slova prethode malim slovima. Zbog
toga e usporedba nizova u sljedeem primjeru:
char *prvi = "mama";
char *drugi = "Tata";
cout << strcmp(prvi, drugi) << endl;
javiti da po abecednom slijedu niz "Tata" prethodi nizu "mama", a to vjerojatno nije
ono to bi korisnik oekivao. Da bi se to izbjeglo, valja koristiti funkciju strlwr(),
koja sva slova u nizu pretvara u mala, omoguavajui korektnu abecednu usporedbu.
Kako bi se pojednostavnilo rukovanje znakovnim nizovima, pogodno je znakovni
niz opisati pomou objekta. Za taj objekt tada moemo definirati operatore koji bitno
pojednostavnjuju gore navedene operacije. Tako se usporedba moe obavljati
standardnim poredbenim operatorima (<, <=, ==, !=, >=, >), dodjela operatorom = i
slino. tovie, dio C++ standarda je i klasa String koja ve posjeduje svu navedenu
funkcionalnost. Vjerujemo da e itatelju to biti puno jasnije nakon to upozna osnove
objektnog programiranja u narednim poglavljima.
210
Sloeni programi se sastoje od mnotva funkcija koje se pozivaju iz funkcije main() ili
iz neke druge korisniki definirane funkcije. Struktura meusobnog pozivanja funkcija u
takvim programima nerijetko moe biti vrlo sloena. Tekoe nastupaju ako tijekom
izvoenja programa nastupi pogreka tako da je potrebno trenutano prekinuti izvoenje
programa. Regularni povratak iz funkcija u takvim situacijama moe biti vrlo
mukotrpan ili ak nemogu.
Ilustrirajmo to primjerom programa koji poziva funkciju za raunanje prirodnog
logaritma. Kao to znamo, logaritamska funkcija definirana je samo za pozitivne
brojeve vee od nule. to, meutim, napraviti ako se kojim sluajem u tijeku prorauna
toj funkciji proslijedi nula ili negativan broj? Kako e funkcija za raunanje logaritma
dati do znanja pozivajuoj funkciji da je rezultat nedefiniran, kad ona inae kao rezultat
moe vratiti bilo koji pozitivni ili negativni broj? Nije nam ostala na raspolaganju niti
jedna specijalna vrijednost koja bi signalizirala pozivajuoj funkciji neispravnost
rjeenja.
Jedno rjeenje je trenutano prekinuti izvoenje programa, za to moemo
iskoristiti funkciju exit() deklariranu u biblioteci stdlib.h:
void exit(int status);
211
Pozivom funkcije exit() se zatvaraju sve datoteke koje su bile otvorene tijekom
izvoenja programa i unitavaju svi statiki objekti, redoslijedom obrnutim od
redoslijeda njihova stvaranja.
212
return x * x;
}
//
//
//
//
float kvadrat(float)
int kvadrat(int)
double kvadrat(double)
double kvadrat(double)
Uoimo da se u posljednjem pozivu realna konstanta implicitno tretira kao podatak tipa
double, te se shodno tome poziva funkcija s takvim tipom argumenta.
Argumenti predloka se mogu koristiti viekratno kao argumenti u deklaraciji
funkcije. Primjerice, sasvim openitu funkciju zamijeni() kojoj je zadaa zamjena
sadraja dvaju objekata, moemo deklarirati i definirati kao
template <class Tip>
void zamijeni(Tip &prvi, Tip &drugi) {
Tip privremeni = prvi;
prvi = drugi;
drugi = privremeni;
}
213
214
kraticom LIFO, od engl. Last In, First Out posljednji unutra, prvi van. Pokaziva
stoga pokazuje na zadnji umetnuti podatak, tj. na vrh stoga. Kada procesor u strojnom
kdu naleti na poziv funkcije, on e pohraniti adresu iz pokazivaa instrukcija (to je
adresa naredbe na koju se izvoenje mora vratiti po povratku iz funkcije) na adresu na
koju pokazuje pokaziva stoga, te e sadraj pokazivaa stoga poveati. Ako se unutar
funkcije pozove neka druga funkcija, postupak e se ponoviti povratne adrese se
postepeno slau u stog, a pokaziva stoga se pritom uveava. Pri povratku iz zadnje
funkcije, preko pokazivaa stoga se oita posljednja povratna adresa, te se pokaziva
smanji. Postupak se ponavlja pri svakom povratku iz funkcija u nizu, te se stog prazni.
Budui da su podaci na stogu dohvatljivi iskljuivo preko pokazivaa stoga, treba
voditi rauna o redoslijedu punjenja i pranjenja stoga. Sreom, sve operacije vezane uz
stog sreuje prevoditelj, oslobaajui programera od prizemnog posla. Meutim, ono o
emu autor programa mora voditi rauna jest da se taj stog ne prepuni, jer e to izazvati
prekid rada programa. Na prvi pogled programer nema ni tu velikog utjecaja budui da
veliinu stoga odreuje prevoditelj. Istina, svaki prevoditelj ima mogunost da se
veliina stoga za neki program povea, ali to nije univerzalni lijek. Ipak, iz gornjih
razmatranja je jasno da mnogobrojni uzastopni pozivi funkcija optereuju stog. To se
posebno odnosi na glomazne rekurzije, gdje broj poziva funkcije moe postati vrlo
velik. Uz to valja znati da se osim povratnih vrijednosti, na stog pohranjuju i argumenti
funkcija (to je uostalom i razlogom zato se polja ne prenose po vrijednosti) i lokalni
objekti. Ako su objekti koji se prenose funkcijama veliki, stog e vrlo brzo biti ispunjen
do vrha.
215
8. Klase i objekti
Ja ne vjerujem u klasne razlike, ali se sreom
miljenje mog sobara po tom pitanju razlikuje.
Marc, strip u londonskom The Times
Jezik C++ s kakvim smo se do sada upoznali ne razlikuje se znatno od bilo kojeg
drugog proceduralnog jezika. Ono to ovaj jezik ini posebno istaknutim jest
mogunost definiranja novih korisnikih tipova klasa, ime se uvodi koncept
objektnog programiranja.
U nastavku e biti objanjen nain na koji se klase deklariraju. Bit e objanjeni
pojmovi kao to su: podatkovni i funkcijski lanovi, prava pristupa, konstruktor,
destruktor. Takoer, bit e razjanjeni kljuni koncepti objektnog programiranja, kao to
su javno suelje i implementacija klase.
216
Kada je klasa jednom definirana, moe se pomou nje konstruirati neogranien broj
objekata koji se zatim mogu koristiti. Kako sve stvari koje nas okruuju imaju svoj
poetak i kraj, i ivot jednog objekta ima svoj poetak i svretak. Kada se objekt stvori,
potrebno mu je dati poetni oblik. Postupak koji opisuje kako e se to napraviti dan je u
specifikaciji klase. Funkcija koja inicijalizira objekt naziva se konstruktor (engl.
constructor). Kada objekt vie nije potreban, posebna funkcija klase pod imenom
destruktor (engl. destructor) uredno e unititi objekt.
Kako bismo ilustrirali injenicu da objekti nisu samo tvorevine raunalskih
strunjaka koji smiljaju naine kojima e zagoravati programerima ivot, evo primjera
koji pokazuje da se itav ivot uistinu odvija kroz interakciju objekata.
Zamislite maestra koji na klaviru izvodi Beethovenovu Mjeseevu sonatu. Takva
naoko jednostavna i svakidanja situacija zapravo je primjer jedinstvene suradnje
nekoliko stotina objekata. Kao prvo, tu je muziar koji izvodi glazbu, zatim tu su klavir i
note po kojima se svira. Pokuajmo poblie identificirati klase na temelju kojih su
stvoreni ti objekti. Muziar je primjerak klase ljudi. Definiciju ovjeka emo prepustiti
filozofima i teolozima, koji se dodue jo danas spore o tome to zapravo ini ovjeka i
kakvo je njegovo javno suelje, no s klavirom je stvar puno jasnija. Uzmimo da se radi o
Steinwayu. On je samo predstavnik ope klase klavira te im identificiramo Steinway
kao klavir odmah znamo ugrubo kako on izgleda. Osnovna svrha klavira je muziciranje
(iako e se moda miljenja ponekih sretnih vlasnika ovdje razlikovati). Njegovo javno
suelje je klavijatura pomou koje korisnik ostvaruje svrhu klavira. Javno suelje
klavira sastoji se od daljnjeg skupa objekata: tipki. Openita klasa tipki definira da
svaka tipka moe biti pritisnuta te da pritisak na nju proizvodi tono odreeni ton.
Nizovi tonova koje klavir proizvodi takoer spadaju u javno suelje klavira.
No, kako klavir ostvaruje svoje javno suelje? Da bismo doznali odgovor na to
pitanje, moramo zaviriti pod poklopac u implementaciju. To je podruje koje ne spada
u javno suelje jer je teko zamisliti da bi imalo ozbiljan muziar pokuao odsvirati
Mjeseevu sonatu direktno koristei implementacijske detalje, runo udarajui
batiima po icama. No i unutar implementacije se i dalje nalazimo u svijetu sainjenom
od objekata. Unutranjost klavira sastoji se od ica, koje opet mogu pripadati odreenoj
klasi ica, te od batia. Funkcioniranje implementacije zasniva se na interakciji ica i
batia. Tako bismo mogli nastaviti s nabrajanjem rastavljajui pojedine objekte na sve
manje i manje detalje, do kvarkova i gluona. to je dalje u implementaciji prepustit
emo fiziarima i njihovim akceleratorima estica.
Valja uoiti da neki drugi objekti ipak mogu imati pristup implementaciji klavira.
Zamislimo da klavir nije dobro ugoen. Loe sviranje klavira u tom sluaju je posljedica
pogreke u implemetaciji. Muziar e, nakon to preko njemu dostupnog javnog suelja
ustanovi da klavir ne radi kako treba, vjerojatno vrlo iznerviran, dignuti ruke i pozvati
ugaaa klavira ili to bi to purgeri rekli, klavir-timera. Muziar nita ne zna o
ustrojstvu klavira, te bi svojim mijeanjem u implementaciju vjerojatno samo unio
dodatni nered. No klavir-timer je osoba koja ima pristup unutranjosti (slika 8.1), te se
u terminologiji C++ jezika ona naziva prijateljem klase klavira (engl. friend of a class).
217
218
time proizvesti neki ton. Mogunost promatranja objekta kroz razliite klase u stablu
nasljeivanja se zove polimorfizam (engl. polymorphism) i tree je vrlo vano svojstvo
svih objektno orijentiranih jezika.
Nakon to smo definirali osnovna svojstva javnog suelja klavira, uoavamo da
smo iz cijele prie izostavili vane elemente javnog suelja, a to su pedale za
produljenje tona. Objektno orijentirano programiranje je vrlo koristan alat koji moe
znaajno poveati produktivnost programera, ali prije nego to se prijee na samo
kdiranje klase, za njegovu uspjenu primjenu potrebno je vie pripremnog rada.
Apstraktni model klavira je potrebno detaljno prouiti prilikom definicije klase kako bi
se precizno opisalo njegovo javno suelje i izbjegle pogreke u projektiranju. Na
primjer, ako na poetku zamislimo klavir tako da mu tipke smjestimo pod poklopac,
teko emo kasnije uspostaviti zadovoljavajue javno suelje. Ispravljanje ovakvih
fundamentalnih konstrukcijskih pogreaka u C++ jeziku je znatno sloenije nego u
klasinim proceduralnim jezicima.
Promotrimo ukratko akcije koje je potrebno provesti prilikom konstrukcije
pojedinog objekta. I dok opet nailazimo na probleme definiranja konstruktora klase
muziara, odnosno ljudi, s klavirima je stvar jasnija. Kada se klavir napravi, potrebno je
dovesti njegove implementacijske detalje u poetno stanje, odnosno pozvati klavirtimera koji e ga odmah ugoditi. Ta akcija se definira kao konstruktor klase klavira.
Vjerujem da e matovit itatelj sam pronai mnogo ilustrativnih primjera za proceduru
koja unitava klavir kada on vie nije potreban, odnosno njegov destruktor. Postupak
koji e biti izabran kao najprikladniji vjerojatno ovisi o tome koliko je (ne)rado dotini
itatelj iao u muziku kolu.
Tri osnovna svojstva objektno orijentiranih jezika enkapsulacija, nasljeivanje i
polimorfizam vrlo su uspjeno ostvarena u C++ jeziku. Program se gradi od niza
objekata koji meusobno surauju, ba kao u naem primjeru s klavirom. Pri tome se
programer mora voditi odreenim formalizmom koji namee sam jezik, no osnovna bit
modeliranja pomou klasa je dosljedno provedena kroz sve elemente jezika.
219
class Klavir {
// ...
};
Naziv klase je identifikator koji mora biti jedinstven u vaeem podruju imena (engl.
name scope - bit e objanjeno kasnije). Objekti se mogu stvoriti tako da se navedu ime
klase iza kojeg se navedu imena objekata odvojena zarezima:
Klavir Steinway, Petroff;
U ovom primjeru definirana su dva objekta Steinway i Petroff klase Klavir. Tijelo
klase moe sadravati podatkovne lanove, funkcijske lanove, deklaracije ugnijeenih
klasa i specifikacije prava pristupa.
8.2.1. Podatkovni lanovi
Svaka klasa moe sadravati podatkovne lanove. Evo primjera klase koja moe biti dio
grafikog suelja i definira objekt Prozor:
class Prozor {
int koordX1, koordY1, koordX2, koordY2;
char *naziv;
Prozor *vlasnik;
};
Ova klasa se sastoji od etiri cjelobrojna lana koji pamte koordinate gornjeg lijevog i
donjeg desnog kuta prozora, zatim od pokazivaa na naziv prozora te od pokazivaa na
neki drugi objekt klase Prozor. Taj pokaziva pokazuje na objekt Prozor koji je
vlasnik promatranog prozora.
Svi su tipovi dozvoljeni prilikom deklaracije klase, s time da oni moraju biti
deklarirani do tog mjesta u programu. Nije mogue deklarirati kao lan klase objekt
klase koja se upravo definira, na primjer:
class Prozor {
int koordX1, koordY1, koordX2, koordY2;
char *naziv;
Prozor vlasnik; // neispravno
};
220
Ovakva rekurzivna deklaracija definira klasu Prozor koji sadri objekt klase Prozor
koji sadri objekt klase Prozor koji sadri... Oito se definicija protee u beskonanost
pa stoga takav objekt nije mogue prikazati u (jo uvijek) konanoj memoriji raunala.
Klasa ne moe sadravati samu sebe, ali moe sadravati pokaziva ili
referencu na objekt iste klase.
Klasa moe sadravati pokaziva ili referencu na element iste klase, kao u prvom
primjeru.
Ponekad je potrebno napraviti klasu koja e sadravati pokaziva na drugu klasu, dok
e ta druga klasa sadravati pokaziva na poetnu klasu. Proirimo primjerice klasu
Prozor pokazivaem na izbornik (engl. menu) koji je povezan s prozorom. Da bi klasa
izbornika funkcionirala, mora sadravati pokaziva na prozor koji posjeduje izbornik.
To se moe napraviti tako da se samo definira ime klase izbornika, zatim se definira
klasa Prozor, a potom se definira klasa Izbornik sa svim pripadajuim lanovima.
Takva vrsta deklaracije zove se deklaracija unaprijed (engl. forward declaration). Evo
primjera:
class Izbornik; // ovo je deklaracija imena unaprijed
class Prozor {
// ovo je deklaracija klase Prozor
Izbornik *pokIzbornik; // pozivanje na deklaraciju
// unaprijed
// ...
};
class Izbornik {
// ovo je potpuna definicija klase Izbornik gdje se
// unaprijed definiranom imenu dodaju detalji
Prozor *pokProzor;
// ...
};
Kada je neka klasa samo deklarirana unaprijed, mogu se definirati iskljuivo reference i
pokazivai na objekte klase, ali ne i objekti klase. Naravno, u programu koji slijedi iza
potpune definicije klase mogu se definirati i objekti te klase. To znai da bi pokuaj
deklariranja objekta Izbornik u sklopu klase Prozor izazvao pogreku prilikom
prevoenja.
8.2.2. Dohvaanje lanova objekta
Kako bi lanovi objekta bili od koristi, mora postojati nain kojim okolni svijet moe
pristupiti pojedinom lanu objekta. Za to se koriste operatori za pristup lanovima
(engl. member selection operators).
221
Prije nego to pristupimo lanu nekog objekta, moramo znati ijim lanovima
elimo pristupati. Neki objekt se referira pomou naziva objekta, reference ili
pokazivaa na objekt.
Kada objekt identificiramo pomou naziva objekta ili reference, za pristup
lanovima se koristi operator . (toka), tako da se s lijeve strane operatora navede naziv
objekta ili referenca, a s desne strane ime lana kojemu se pristupa. Deklarirat emo
klasu Racunalo te objekt i referencu tog tipa:
class Racunalo {
public:
int kBMemorije;
int brojDiskova;
int megahertza;
};
Racunalo mojKucniCray;
Racunalo &refRacunalo = mojKucniCray;
U gornjoj deklaraciji kljuna rije public oznaava da e lanovi klase biti javno
dostupni (o tome vie rijei u odsjeku 8.2.6). Sada bismo memoriju objekta
mojKucniCray proirili na sljedei nain:
mojKucniCray.kBMemorije = 64;
refRacunalo.brojDiskova = 5;
222
Iako je to sasvim ispravno rjeenje, ono ima niz nedostataka. Operacija mnoenja je
svojstvena samom vektoru. Naprotiv, gore navedena funkcija za mnoenje razdvaja
operaciju od objekta nad kojim se obavlja zato smo u funkciji za pristup pojedinim
elementima vektora morali koristiti v.ax ili v.ay. C++ prua elegantnije rjeenje ovog
problema:
class Vektor {
public:
float ax, ay;
void MnoziSkalarom(float skalar);
};
void Vektor::MnoziSkalarom(float skalar) {
ax *= skalar;
ay *= skalar;
}
Strukture e biti obraene u sljedeem poglavlju, pa se itatelj koji nije vian jeziku C ne
mora previe zamarati ovim primjerom.
223
Naziv funkcijskog lana mora biti jedinstven u podruju imena unutar klase.
To znai da moemo imati obinu funkciju MnoziSkalarom() izvan klase Vektor,
koja je potpuno nezavisna od funkcijskog lana klase Vektor. No unutar klase ne smije
postojati lan ili ugnijeena klasa istog imena. Nazivi pridruenih funkcija nisu vidljivi
izvan klase, tako da poziv
MnoziSkalarom(7.6);
sam za sebe bez operatora . nema smisla, jer nije jasno ijim ax i ay se pristupa u toku
izvoenja. Kako ime funkcije nije vidljivo izvan klase, ovakva naredba e rezultirati
pogrekom u prevoenju Nepoznata funkcija MnoziSkalarom(float).
U naem primjeru unutar definicije klase naveli smo samo prototip funkcije
MnoziSkalarom(), dok smo njenu definiciju naveli izvan klase. Kako imena funkcije
nisu vidljiva izvan klase, prilikom definiranja funkcije koristili smo :: (dvije dvotoke)
operator za razluivanje podruja imena (engl. scope resolution operator). Cijelo ime
funkcijskog lana je zapravo Vektor::MnoziSkalarom(), pa smo takvo ime morali
navesti prilikom definicije funkcije. Valja primijetiti da smo je mogli takoer pozivati
navodei puno ime
v.Vektor::MnoziSkalarom(5.0);
Puno ime lanova deklariranih unutar klase ukljuuje naziv klase koji se od
naziva lana razdvaja operatorom ::.
224
Iako ispravno, ovakvo pisanje je sloenije. Objekt v je klase Vektor, pa prevoditelj sam
zakljuuje da je funkcija MnoziSkalarom() iz klase Vektor.
8.2.4. Kljuna rije this
Velika prednost C++ jezika lei upravo u tome to prevoditelj skriva this od korisnika
i sam obavlja pristupanje preko pokazivaa. U veini primjena koritenje te kljune
rijei nee biti potrebno. Ipak, u nekim specifinim sluajevima bit e potrebno unutar
funkcijskog lana saznati adresu objekta za koji je lan pozvan. To se tada moe uiniti
pomou kljune rijei this. Vano je napomenuti da se this ne smije deklarirati kao
formalni parametar funkcijskog lana. On je automatski dostupan u svakom lanu te ga
nije potrebno posebno navoditi.
Kljuna rije this se esto koristi kada je potrebno vratiti pokaziva ili referencu
na objekt. Da bismo to demonstrirali, proirit emo klasu Vektor funkcijom
ZbrojiSa() koja zbraja vektor s nekim drugim vektorom. Promijenit emo povratni
tip funkcije na Vektor &, ime e funkcijski lan vraati referencu na objekt za koji je
pozvan.
class Vektor {
public:
float ax, ay;
Vektor &MnoziSkalarom(float skalar);
Vektor &ZbrojiSa(float zx, float zy);
};
Vektor &Vektor::MnoziSkalarom(float skalar) {
225
ax *= skalar;
ay *= skalar;
return *this;
}
Vektor &Vektor::ZbrojiSa(float zx, float zy) {
ax += zx;
ay += zy;
return *this;
}
Ako je funkcija kratka tako da njena definicija stane u nekoliko redaka, onda je njenu
definiciju mogue navesti unutar same deklaracije klase. Time dobivano umetnutu
definiciju (engl. inline definition) funkcijskog lana. Prepiimo nau klasu Vektor tako
da funkcija MnoziSkalarom() bude umetnuta.
226
class Vektor {
public:
float ax, ay;
void MnoziSkalarom(float skalar) {
ax *= skalar;
ay *= skalar;
}
};
Razlozi radi kojih je poeljno funkcijske lanove uiniti umetnutima su isti kao i za
Rezultat e prilikom ovakve deklaracije biti isti. Kljuna rije inline se mogla
ponoviti prije definicije funkcijskog lana. Umetnuti funkcijski lanovi su vrlo vani u
C++ jeziku, jer se esto primjenjuju za postavljanje i itanje podatkovnih lanova koji
nisu javno dostupni (vidi sljedei odsjeak). Prevoditelj koji ne podrava dovoljno
dobro umetnute funkcijske lanove moe vrlo lako generirati kd znaajno degradiranih
performansi.
8.2.6. Dodjela prava pristupa
227
definirano tom rijei. Zatim se moe navesti neka druga od gore navedenih kljunih
rijei, iza koje slijede elementi koji imaju drukije pravo pristupa. Evo primjera:
class Pristup {
public:
int a, b;
void Funkcija1(int brojac);
private:
int c;
protected:
int d;
int Funkcija2();
};
Prava pristupa odreuju koji e lanovi klase biti dostupni izvan klase, koji iz
naslijeenih klasa, a koji samo unutar klase. Time programer tono odreuje publiku
za pojedini dio objekta.
Javni pristup se dodjeljuje kljunom rijei public. lanovima koji imaju javni
pristup moe se pristupiti izvan klase. Cjelobrojni elementi a i b, te Funkcija1() se
mogu pozvati tako da se definira neki objekt klase Pristup te im se pomou
operatora . pristupi.
lan c ima privatni pristup dodijeljen kljunom rijei private. U vanjskom
programu te u klasama koje su naslijeene od klase Pristup on nije dostupan
njemu se moe pristupiti samo preko funkcijskih lanova klase Pristup.
Zatieni pristup se odreuje kljunom rijei protected te se time ograniava
mogunost pristupa lanu d i funkcijskom lanu Funkcija2(). Njima se ne moe
pristupiti iz vanjskog programa preko objekta. Dostupne su samo u funkcijskim
lanovima klase Pristup i klasama koje su naslijeene od klase Pristup.
Nasljeivanjem se bavi posebno poglavlje 9 ove knjige.
Redoslijed navoenja prava pristupa je proizvoljan, a pojedine vrste pristupa je mogue
ponavljati. Dozvoljeno je primjerice definirati prvo elemente s javnim, zatim elemente s
privatnim te ponovo elemente s javnim pristupom. lanovi navedeni u deklaraciji klase
odmah nakon otvorene vitiaste zagrade do prve kljune rijei za specifikaciju prava
pristupa (ili do kraja klase ako prava pristupa uope nisu navedena) imaju privatni
pristup. Ako se negdje u programu pokua nedozvoljeno pristupiti privatnom ili
zatienom elementu, dobit e se pogreka prilikom prevoenja Za element xx nema
prava pristupa.
Ako se pravo pristupa ne navede eksplicitno, za klase se podrazumijeva
privatni pristup.
Da bismo ilustrirali koritenja prava pristupa, stvorit emo objekt x klase Pristup te
emo pokazati kojim se podatkovnim lanovima iz kojeg dijela programa moe
pristupiti.
228
Pristup x;
void Pristup::Funkcija1(int brojac) {
// unutar funkcijskog lana objekta moe se
// pristupiti svim lanovima ravnopravno
a = brojac;
c = a + 5;
Funkcija2();
}
int main() {
// ovo je OK: a i b imaju javni pristup
x.a = x.b = 6;
// i ovo je OK: lan Funkcija1(int) takoer
// ima javni pristup
x.Funkcija1(1);
// ovo nije OK: c ima privatni pristup te mu se ne
// moe pristupiti izvana
cout << x.c << endl;
// niti ovo ne valja: Funkcija2() ima zatieni
// pristup
x.Funkcija2()
return 0;
}
(Odgovor lei u starom pravilu bon-tona, koje kae da se pripadnice ljepeg spola ne
pita za godine!)
Zadatak. Deklarirajte klasu Pravac s privatnim lanovima k i l koji e sadravati
koeficijent smjera i odsjeak pravca na osi y. Dodajte javne funkcijske lanove
UpisiK() i CitajK(), te UpisiL() i CitajL() za pristup podatkovnim lanovima.
Osobi koja se po prvi put susree sa C++ jezikom moe se uiniti da su komplikacije
oko prava pristupa nepotrebne, te da samo unose dodatnu entropiju u ve ionako
dovoljno kaotian nain programiranja, koji vie slii Brownovom gibanju. To je i
donekle tono u programima od nekoliko stotina redaka koji se napiu u jednom
popodnevu. No prilikom razvoja sloenih aplikacija sa stotinama tisua linija kda gdje
229
230
Ponekad moe biti nuno da neka funkcija, funkcijski lan ili pak cijela klasa ima pravo
pristupa privatnim i zatienim lanovima neke druge klase. Pojedina klasa u tom
sluaju moe dodijeliti eljenoj funkciji, funkcijskom lanu neke klase ili itavoj klasi
pravo pristupa vlastitim privatnim i zatienim lanovima. Dio programa kojemu je
dodijeljeno pravo pristupa zove se prijatelj klase (engl. a friend to a class) koja mu je to
pravo dala.
Definirajmo za primjer funkciju koja e zbrajati dva vektora. Ona bi to mogla
uiniti pomou javnog suelja vektora, no znatno je prirodnije pristupati direktno
lanovima klase Vektor. Klasa se tada moe redefinirati ovako:
class Vektor {
friend Vektor ZbrojiVektore(Vektor a, Vektor b);
private:
float ax, ay;
public:
// ...
};
Vektor ZbrojiVektore(Vektor a, Vektor b) {
Vektor c;
c.ax = a.ax + b.ax;
c.ay = a.ay + b.ay;
return c;
}
Deklaracija neke funkcije kao prijatelja poinje kljunom rijei friend iza koje se
navede puni prototip funkcije kojoj se dodjeljuju prava pristupa. Nije potrebno
deklarirati funkciju prije nego to se ona uini prijateljem. Tako je u naem primjeru
sama funkcija deklarirana i definirana nakon deklaracije klase. Ako je potrebno vie
funkcija uiniti prijateljem, tada se svaka funkcija navodi zasebno. Nije mogue nakon
jedne kljune rijei friend navesti vie deklaracija. Iako se deklaracije prijatelja mogu
pojaviti bilo gdje u tijelu klase, uvrijeeno je pravilo da ih se navodi odmah iza
deklaracije klase.
Prava pristupa je mogue dodijeliti i pojedinim funkcijskim lanovima drugih klasa.
Tako je mogue definirati klasu Matrica koja e sadravati funkcijski lan koji mnoi
matricu vektorom. Klasa Vektor e dodijeliti prava pristupa funkcijskom lanu
Matrica::MnoziVektorom() na sljedei nain:
class Vektor;
231
class Matrica {
private:
float a11, a12, a21, a22;
public:
// ...
Vektor MnoziVektorom(Vektor &v);
};
class Vektor {
friend Vektor Matrica::MnoziVektorom(Vektor &v);
// ...
};
Vektor Matrica::MnoziVektorom(Vektor &v) {
Vektor rez;
rez.ax = a11 * v.ax + a12 * v.ay;
rez.ay = a21 * v.ax + a22 * v.ay;
return rez;
}
Prilikom dodjele prava pristupa funkcijskom lanu klase postoji pravilo da se pravo ne
moe dodijeliti ako funkcijski lan nije ve prethodno deklariran. Zato je deklaracija
klase Matrica prethodila deklaraciji klase Vektor, ime je postignuto da u trenutku
deklariranja klase Vektor funkcijski lan kojem se ele dodijeliti prava ve bude
deklariran. Definicija funkcijskog lana Matrica::MnoziVektorom() je stavljena
nakon definicije klase Vektor iz dva razloga: struktura klase Vektor mora biti poznata
da bi se moglo pristupati njegovim lanovima te klasa Vektor mora dodijeliti pravo
pristupa funkcijskom lanu kako bi mogao pristupiti privatnim lanovima.
Ponekad je potrebno dodijeliti pravo pristupa svim funkcijskim lanovima jedne
klase. Tada se cijela klase moe deklarirati prijateljem druge klase. U tom sluaju ta
klasa ima pristup svim privatnim i zatienim tipovima, pobrojenjima i ugnijeenim
klasama. To se ini tako da se iza kljune rijei friend navede kljuna rije class za
kojom slijedi naziv klase kojoj se prava dodjeljuju. Klasa kojoj se dodjeljuju prava
pristupa mora u trenutku dodjele biti deklarirana, makar i nepotpuno (unaprijed), kao u
primjeru:
class Matrica;
class Vektor {
friend class Matrica;
// ...
};
Svi privatni i zatieni tipovi definirani unutar klase Vektor mogu se sada koristiti i u
definiciji funkcijskih lanova klase Matrica.
Unutar deklaracije friend se funkcija moe i definirati tada e ona biti umetnuta.
Time je postignuta slinost s funkcijskim lanovima koji se takoer mogu definirati u
232
233
Kada se stvori novi objekt neke klase, njegovi lanovi su neinicijalizirani. Vrijednosti
pojedinih lanova ovise o podacima koji su se sluajno nali na tom memorijskom
prostoru prije nego to je objekt stvoren. Kako je rad s podacima ija je vrijednost
odreena stohastiki potencijalno vrlo opasan, C++ nudi elegantno rjeenje. Svaka klasa
moe imati konstruktorski funkcijski lan koji se automatski poziva prilikom stvaranja
objekata. Konstruktor (engl. constructor) se deklarira kao funkcijski lan koji nema
specificiran povratni tip te je imena identinog nazivu klase.
Konstruktor ima isto ime kao i klasa kojoj pripada, te nema povratni tip.
Dodajmo klasi Vektor konstruktor koji e svaki novostvoreni vektor inicijalizirati na
nul-vektor:
class Vektor {
private:
float ax, ay;
public:
// definirana su dva konstruktora:
Vektor();
234
Vektor(float x, float y) { ax = x; ay = y; }
void PostaviXY(float x, float y) { ax = x; ay = y; }
float DajX() { return ax; }
float DajY() { return ay; }
void MnoziSkalarom(float skalar);
};
Vektor::Vektor() {
ax = 0;
ay = 0;
}
// deklaracija vektora
// deklaracija funkcije bez parametara
// koja vraa Vektor
235
236
237
Ovakav nain inicijalizacije nije obavezan, ali ima prednosti prilikom izvoenja
programa. Postavljanje vrijednosti se moe obaviti i unutar tijela konstruktora. No ako
se izostavi inicijalizacija nekog lana, C++ prevoditelj e sam umetnuti kd za
inicijalizaciju. U tom sluaju se inicijalizacija obavlja dva puta: jednom zato to nije
238
class Par {
Vektor prvi, drugi;
public:
Par(float x1, float y1, float x2, float y2) :
drugi(x2, y2), prvi(x1, y1) {}
// ...
};
239
jednoznano odrediti koji se konstruktor poziva. Operator new poziva konstruktor nakon
to alocira potrebnu koliinu memorije za smjetaj objekta. Ako alokacija memorije ne
uspije, konstruktor se ne izvodi, a new operator vraa nul-pokaziva.
Zadatak. Klasu Pravac iz zadatka na stranici 228 proirite konstruktorom koji e
pravac inicijalizirati pomou dva objekta klase Tocka (vidi zadatak na stranici 232).
Konstruktor mora imati sljedeu deklaraciju:
Pravac::Pravac(Tocka &t1, Tocka &t2);
240
konstruktor klase je ujedno i podrazumijevani konstruktor. Kako sva tri parametra imaju
podrazumijevanu vrijednost te ih se moe izostaviti, prevoditelj ne moe ustanoviti
razliku izmeu tog konstruktora i konstruktora bez parametara. Pokuaj posebne
definicije podrazumijevanog konstruktora zavrio bi pogrekom prilikom prevoenja jer
te dvije funkcije nije mogue razlikovati.
8.4.3. Poziv konstruktora prilikom definiranja objekata
241
242
Tablica A
int*
Tablica B
int*
int*
int*
Elementi
Elementi
ELEMENTI
pokazuju na isti komad memorije. Zbog toga se svaka promjena u tablici A odraava i na
tablicu B. Najopasnija popratna pojava ovakvog kopiranja objekata je u tome to objekt
B moe biti uniten (na primjer zavri li funkcija u kojoj je on bio alociran kao
automatski objekt) ime e se osloboditi memorija na koju pokazuje pokaziva
Elementi. No pokaziva u objektu A e i dalje pokazivati na isto mjesto koje je sada
nevaee. Pokuaj izmjene tablice A e promijeniti sadraj memorije na koju A
pokazuje, no ta memorija je u meuvremenu moda dodijeljena nekom drugom objektu.
Vidimo da ovakav pristup vodi u sveopu zbrku, a programi pisani na ovakav nain ne
mogu funkcionirati ispravno. Ovakvo kopija se esto naziva plitka kopija (engl. shallow
copy), a situacija u kojoj pokazivai dvaju objekata pokazuju na isto memorijsko
podruje na engleskom se naziva pointer aliasing.
Zato emo dodati konstruktor kopije klasi Tablica koji e alocirati zasebno
memorijsko podruje za svaki novi objekt te prepisati sadraj tablice. Time emo dobiti
tzv. duboku kopiju (engl. deep copy) objekta:
class Tablica {
// ...
public:
Tablica(const Tablica &refTabl);
};
Tablica::Tablica(const Tablica &refTabl) :
Elementi(new int[refTabl.Duljina]),
Duljina(refTabl.Duljina),
BrojElem(refTabl.BrojElem) {
assert(Elementi != 0);
for (int i = 0; i < BrojElem; i++)
Uz najbolju volju, ovaj termin nismo nikako mogli suvislo prevesti. Na engleskom alias znai
lano ime, no termin predstavljanje pokazivaa lanim imenom e prije izazvati provale
smijeha, no opisati problem.
243
Elementi[i] = refTabl.Elementi[i];
}
Klase mogu sadravati konstantne lanove i reference na objekte. Takvi lanovi imaju
poseban tretman prilikom inicijalizacije i pristupa njihovoj vrijednosti. Promotrimo
poblie o emu se radi.
Iz prethodnih poglavlja znamo da se referenca mora inicijalizirati prilikom
deklaracije. Ne postoji mogunost ostaviti referencu neinicijaliziranu ili ju naknadno
preusmjeriti. U klasama se referenca moe deklarirati, ali za razliite objekte iste klase
ona moe referirati razliite podatke. Na primjer, moemo definirati klasu Par koja e
opisivati ureeni par vektora, s time da vektori nee biti dio klase, ve e svaki Par
sadravati po dvije reference na vektore. Evo deklaracije klase:
class Par {
public:
Vektor &refPrvi, &refDrugi;
// nastavak slijedi ...
};
244
emo pogreku prilikom prevoenja, jer reference ne smiju ostati neinicijalizirane. Evo
kako to izgleda na primjeru klase Par:
class Par {
public:
Vektor &refPrvi, &refDrugi;
Par(Vektor &v1, Vektor &v2) : refPrvi(v1), refDrugi(v2) {}
};
Konstruktor klase Par kao parametar ima reference na objekte klase Vektor te se
reference refPrvi i refDrugi inicijaliziraju tako da pokazuju na parametre.
Obavezno je bilo staviti v1 i v2 kao reference: u suprotnom bi se prilikom poziva
konstruktora stvarale privremene kopije stvarnih parametara, a reference bi se
inicijalizirale adresom tih privremenih kopija. Nakon zavretka konstruktora kopije bi se
unitile, a reference bi pokazivale na memoriju u kojoj vie nema objekta. U nastavku je
dan primjer alociranja objekta klase Par i njegove ispravne inicijalizacije:
Vektor a(10.0, 2.0), b(-2.0, -5.0);
Par p(a, b);
// ispravna inicijalizacija
245
Tablica::Tablica() : maxDuljina(50),
Elementi(new int[10]),
BrojElem(0), Duljina(10) {
}
Tablica::Tablica(int BrElem, int duljina) :
maxDuljina(duljina), Elementi(new int[10]),
BrojElem(0), Duljina(BrElem) {
}
Prvi konstruktor inicijalizira lan maxDuljina na vrijednost 50, dok ga drugi postavlja
na proslijeenu vrijednost.
Konstantni podatkovni lanovi objekta se obavezno moraju inicijalizirati, te
se to mora uiniti u inicijalizacijskoj listi konstruktora.
246
Kada objekt vie nije potreban, on se uklanja iz memorije. Pri tome se poziva destruktor
(engl. destructor) koji je zaduen za oslobaanje svih resursa dodijeljenih objektu. To je
takoer funkcija koja ima naziv identian nazivu klase ispred kojeg stoji znak ~ (tilda).
Tako destruktor za klasu Tablica ima naziv ~Tablica. Destruktor, kao i konstruktor,
nema povratni tip. Destruktor se automatski poziva u sljedeim situacijama:
247
Tablica::~Tablica() {
delete [] Elementi;
}
248
Kao to je mogue stvoriti globalne i statike cjelobrojne ili realne varijable, mogue je
stvoriti globalne i statike objekte korisniki definiranih klasa. Na primjer:
class Kompleksni {
private:
float r, i;
public:
Kompleksni(float rr, float ii) : r(rr), i(ii) {}
float DajRe() { return r; }
float DajIm() { return i; }
void Postavi(float rr, float ii) { r = rr; i = ii; }
};
Kompleksni a(10, 20);
Kompleksni sumiraj(Kompleksni
static Kompleksni suma(0,
suma.Postavi(suma.DajRe()
suma.DajIm()
return suma;
}
int main() {
&ref) {
0);
+ ref.DajRe(),
+ ref.DajIm());
249
U gornjem programu funkcija sumiraj() zbraja sve argumente koji su joj proslijeeni
prilikom poziva. Suma se pamti u statikom objektu suma, koji je deklariran unutar
funkcije kako bi se onemoguilo vanjskom programu da mijenja sumu direktno.
Takoer, potrebno je objekt deklarirati statikim kako bi se suma pamtila prilikom
svakog poziva funkcije. Takoer, definiran je i globalni objekt a.
Objekti a i suma su definirani klasom, te je prilikom njihovog nastajanja potrebno
provesti postupak konstrukcije. Uostalom, vidi se da oba objekta u svojim deklaracijama
imaju parametre koji slue za poetno postavljanje objekta. To postavljanje se moe
provesti jedino u konstruktoru. Takoer, prilikom zavretka programa, oba objekta e
biti potrebno unititi potrebno je pozvati destruktor.
Dakle, za sve statike i globalne objekte konstruktor e se izvesti prije upotrebe bilo
kojeg objekta ili funkcije u datoteci izvornog kda. U praksi, to znai da e 99,99%
prevoditelja konstruktore pozvati prije ulaska u funkciju main(). Destruktori e se
Konstruktori za globalne i statike objekte e se pozvati prije ulaska u
funkciju main(), a destruktori nakon zavretka funkcije main() ili nakon
poziva funkcije exit().
pozvati nakon to funkcija main() zavri ili ako se program naprasno zavri pozivom
funkcije exit().
Konstruktori e se izvesti u redoslijedu deklaracija unutar datoteke izvornog kda, a
destruktori e se izvesti obrnutim redoslijedom. Standard ne definira redoslijed
pozivanja konstruktora u programima koji imaju vie datoteka izvornog kda, pa je
dobra navika ne oslanjati se u programima na redoslijed inicijalizacije objekata iz
razliitih datoteka.
250
251
delete polje;
Memorija na koju taj pokaziva pokazuje mora biti alocirana na neki drugi nain. Sam
operator new ne obavlja alokaciju memorije nego samo poziva konstruktor za element i
vraa pokaziva na njega. Da bismo mogli stvarati objekte na odreenom mjestu u
memoriji, potrebno je ukljuiti datoteku new.h, jer ona sadri deklaraciju
preoptereene verzije operatora new koji omoguava alokaciju objekta na eljenom
mjestu u memoriji. Evo kompletnog primjera:
#include <new.h>
const int velicina = 5;
Vektor *AlocirajPolje() {
int polje[velicina][2] = {{12, 3}, {0, 0}, {1, 1},
{6, -7}, {-2, -9}};
// alocirat emo memoriju za cijelo polje
char *pok = new char[sizeof(Vektor) * velicina];
for (int i = 0; i < velicina; i++)
252
Alokacija polja se obavlja tako da se najprije zauzme ukupna memorija potrebna za sve
vektore, a zatim se u nju umeu pojedini vektori koji se inicijaliziraju pomou
konstruktora s dva argumenta. Ovako alocirano polje nije mogue dealocirati
naredbom
delete [] pok;
prevoditelj nee moi pozvati destruktore za lanove polja. Dealokacija polja se mora
obaviti eksplicitnim pozivanjem destruktora za svaki lan polja:
void DealocirajPolje(Vektor *polje, int vel) {
for (int i = 0; i < vel; i++)
polje[i].~Vektor();
delete [] (char *)polje;
}
253
254
// OK
// OK
// pogreka
255
ogradom da klasa mora imati konstruktor. Evo kako to izgleda u funkcijskim lanovima
DajX() i DajY():
class Vektor {
private:
float ax, ay;
int broji;
public:
Vektor() : ax(0), ay(0), broji(0) {}
Vektor(float x, float y) : ax(x), ay(y), broji (0) {}
void PostaviXY(float x, float y) { ax = x; ay = y; }
float DajX() const;
float DajY() const;
};
float Vektor::DajX() const {
((int&)broji)++;// dodjelom se ukida konstantnost
return ax;
}
float Vektor::DajY() const {
((int&)broji)++;
// dodjelom se ukida konstantnost
return ay;
}
Nedavno je u standard C++ jezika ubaena kljuna rije mutable koja omoguava jo
elegantnije rjeenje gornjeg problema. Naime, pojedine nekonstantne nestatike
podatkovne lanove moemo deklarirati tako da ispred deklaracije lana umetnemo tu
kljunu rije. Ti lanovi e tada i u konstantnim objektima biti nekonstantni, te e ih se
moi mijenjati u konstantnim funkcijskim lanovima. Gornji primjer brojanja pristupa
se sada moe ovako rijeiti:
class Vektor {
private:
float ax, ay;
mutable int broji;
public:
Vektor() : ax(0), ay(0), broji(0) {}
Vektor(float x, float y) : ax(x), ay(y), broji (0) {}
void PostaviXY(float x, float y) { ax = x; ay = y; }
float DajX() const;
float DajY() const;
};
float Vektor::DajX() const {
broji++;// dozvoljeno jer je broji deklariran kao
// mutable
return ax;
256
}
float Vektor::DajY() const {
broji++;
// dozvoljeno jer je broji deklariran kao
// mutable
return ay;
}
257
// obina funkcija
// volatile funkcija
int main() {
volatile PrimjerZaVolatile a;
a.Funkcija1(); // pogreka: za volatile objekte se moe
// pozvati samo volatile funkcijski lan
a.Funkcija2(); // OK: objekt je volatile i funkcijski
// lan je volatile
return 0;
}
Ponekad je potrebno definirati podatkovni lan koji e biti zajedniki svim lanovima
klase. To moe, na primjer, biti broja koji broji koliko je stvoreno objekata te klase ili
moe biti statusni lan koji odreuje ponaanje klase. Taj problem bi se klasinim
pristupom mogao rijeiti tako da se deklarira globalna varijabla kojoj pristupaju
funkcijski lanovi klase. No to rjeenje nije elegantno jer je globalna varijabla dostupna
i izvan klase te joj pristup ne moe biti privatan.
258
Rjeenje problema daju statiki elementi klase. U tom sluaju varijabla se ne nalazi
u globalnom podruju imena nego u podruju imena unutar klase te joj je pristup
kontroliran, na primjer:
#include <iostream.h>
class Brojeni {
private:
static int Brojac;
int MojBroj;
public:
Brojeni();
~Brojeni();
};
Brojeni::Brojeni() : MojBroj(Brojac++) {
cout << "Stvoren element br.: " << MojBroj << endl;
}
Brojeni::~Brojeni() {
cout << "Unisten element br.: " << MojBroj << endl;
}
int Brojeni::Brojac = 0;
Podatkovni lan Brojac nije sadran u svakom objektu klase nego je globalan za cijelu
klasu. Takav lan se deklarira tako da se prije specifikacije tipa ubaci kljuna rije
static. Za statike lanove vrijede standardna pravila pristupa. U gornjem sluaju je
podatkovni lan Brojac deklariran privatnim, te mu se moe pristupiti samo unutar
klase.
Statiki lanovi su zajedniki za sve objekte neke klase. Oni su slini
globalnim varijablama s tom razlikom to su njihova imena vidljiva
iskljuivo iz podruja klase, te se podvrgavaju pravilima pristupa.
Deklaracija statikog lana unutar klase slui samo kao najava ostatku programa da e
negdje u memoriji postojati jedan lan koji e biti zajedniki svim objektima klase. Sam
lan time nije smjeten u memoriju. Inicijalizacija lana se mora eksplicitno obaviti
izvan klase tada se odvaja memorijski prostor i lan se inicijalizira. U gornjem je
primjeru to je uinjeno ovom naredbom:
int Brojeni::Brojac = 0;
Ona odvaja potreban memorijski prostor i inicijalizira lan na nulu. U C++ jeziku se
deklaracija klase esto izdvaja u zasebnu datoteku zaglavlja koja se ukljuuje u sve
segmente programa koji koriste klasu. Tako e deklaracija klase svim dijelovima
programa objaviti da e u memoriji postojati jedan zajedniki lan. U jednoj datoteci se
mora nai i inicijalizacija tog lana ili e se u protivnom dobiti pogreka prilikom
259
povezivanja kako lan nije pronaen u memoriji. Deklaraciju klase Brojeni moemo
smjestiti u datoteku broj.h, a definiciju funkcijskih i statikih lanova u datoteku
broj.cpp. Ako bi se inicijalizacija statikih lanova takoer smjestila u datoteku
zaglavlja, prilikom svakog ukljuenja te datoteke pretprocesorskom direktivom
#include stvorio bi se po jedan lan i na kraju bismo prilikom povezivanja dobili
poruku o pogreki jer je lan Brojeni::Brojac viestruko definiran.
Iz funkcijskih lanova klase statikom se lanu pristupa na isti nain kao i obinom
lanu. Ukoliko statiki lan ima javno pravo pristupa, moe mu se pristupiti i iz ostalih
dijelova programa, i to na dva naina. Prvi je nain pomou objekta: navede se objekt,
zatim operator . pa onda naziv lana. Tada sam objekt slui iskljuivo tome da se
identificira klasa u kojoj je lan definiran. Drugi nain je pristup bez objekta, samo
navodei klasu. Ispred samog lana se mora navesti naziv klase i operator :: da bi se
naznailo u kojoj se klasi nalazi lan kojemu se pristupa. Opi oblik sintakse za pristup
lanovima je naziv_klase :: ime_lana, na primjer:
class Brojeni {
public:
static int Brojac; // javni statiki lan
int MojBroj;
// ...
void IspisiBrojace();
};
void Brojeni::IspisiBrojace() {
cout << "Brojac: " << Brojac << endl;
// ovo je pristup iznutra
cout << "Moj broj: " << MojBroj << endl;
}
int main() {
Brojeni bb;
Brojeni::Brojac = 5;
bb.Brojac = 5;
bb.IspisiBrojace();
return 0;
}
Javnim statikim lanovima se moe pristupati direktno (navodei cijelo ime lana), jer
za cijelu klasu postoji samo jedan statiki lan. Naprotiv, obinim podatkovnim
lanovima se ne moe direktno pristupati, tako da pridruivanje
Brojeni::MojBroj = 5;
nije ispravno. Ova naredba izaziva pogreku pri prevoenju jer MojBroj postoji samo u
kontekstu odreenog objekta pa mu se moe pristupiti jedino pomou objekta:
bb.MojBroj = 5;
260
No dozvoljeno je napisati
bb.Brojac = 5;
Objekt bb sada prevoditelju samo kazuje naziv klase u kojoj je statiki lan smjeten.
Gornja naredba nita ne pridruuju objektu bb, ve pristupa statikom lanu.
Statiki podatkovni lanovi se dodatno razlikuju od obinih lanova po tome to
klasa moe sadravati statiki objekt te klase. U sluaju obinih lanova, klasa moe
sadravati samo pokazivae i reference na objekte te klase, na primjer:
class Obj {
// ovo je OK jer je lan statiki:
static Obj statickiClan;
// i ovo je OK jer su lanovi referenca i pokaziva:
Obj &ref, *pok;
// ovo nije OK:
Obj nestatickiClan;
};
Postoje funkcijski lanovi koji pristupaju samo statikim lanovima klase. Istina, oni se
mogu realizirati kao obini funkcijski lanovi. Meutim, ovakvo rjeenje ima manu to
se za poziv lana mora stvoriti objekt samo da bi se poziv obavio. To je nepraktino jer
objekt u biti nije potreban; funkcija koju pozivamo pristupa iskljuivo statikim
lanovima i ne mijenja niti jedan nestatiki lan koji ovisi o objektu. Takva se funkcija
moe deklarirati kao statika funkcija klase, tako da se ispred povratnog tipa umetne
kljuna rije static. Za poziv takve funkcije nije potrebno imati objekt, nego se
jednostavno navede naziv funkcije, kao da se radi o obinoj funkciji.
261
Iz primjera se vidi da prilikom poziva statike funkcije nije potrebno navoditi objekt
pomou kojeg se funkcija poziva, nego se jednostavno navede cijelo ime funkcije (u
gornjem primjeru niti jedan objekt klase nije bio deklariran). Vano je uoiti da statike
funkcije, slino statikim podatkovnim lanovima, imaju puno ime oblika
naziv_klase :: ime_funkcije. Ime funkcije pripada podruju imena klase, a ne
globalnom podruju imena. Zato je potrebno prilikom referenciranja na funkciju navesti
ime klase. Za statike lanove klasa vrijede pravila pristupa, pa ako funkciju elimo
pozivati izvan objekata klase, ona mora imati javni pristup.
Statiki funkcijski lanovi se mogu pozvati bez objekta, navodei puno ime
funkcije. Kompletno ime obuhvaa naziv klase, odvojen operatorom :: od
naziva lana.
Statiki funkcijski lanovi se, kao i statiki podatkovni lanovi, mogu pozvati i pomou
objekta klase. Sljedei poziv je stoga potpuno ispravan:
int main() {
Brojeni bb;
Brojeni::Brojac = 5;
cout << bb.VrijednostBrojaca() << endl;
return 0;
}
Kako se statike funkcije ne pozivaju pomou objekta, one niti ne sadravaju implicitan
this pokaziva. Svaka upotreba te kljune rijei u statikoj funkciji e rezultirati
pogrekom prilikom prevoenja. Kako nema this pokazivaa, jasno je da i pokuaj
pristupa bilo kojem nestatikom podatkovnom ili funkcijskom lanu klase rezultira
262
pogrekom prilikom prevoenja. Statiki funkcijski lanovi ne mogu biti deklarirani kao
const ili volatile.
Dozvoljeno je koristiti pokazivae na statike podatkovne i funkcijske lanove, kao
u primjeru
// ...
int *pok = &Brojeni::Brojac;
int (*pokNaFunkciju)() = Brojeni::VrijednostBrojaca;
Statikim lanovima klase moe se pristupati i iz drugih modula, odnosno imaju vanjsko
povezivanje. O tome e biti govora u poglavlju o organizaciji kda.
Zadatak. U zadatku na stranici 248 deklarirali smo klasu Muha, te globalnu varijablu
koja prati broj muha u raunalu. Promijenite mehanizam brojenja muha tako da broja
bude privatan te se ita pomou statikog lana. Osim toga, esto je potrebno imati
vezanu listu svih muha u programu (na primjer, za ubijanje svih muha jednim udarcem).
Dodajte statiki lan Muha *zadnja koji e pamtiti adresu zadnje stvorene muhe.
Takoer, kako bi imali vezanu listu muha, dodajte nestatike lanove prethodna i
sljedeca koji e se postavljati u konstruktoru i destruktoru te e pokazivati na
prethodnu i na sljedeu muhu u nizu. Dakle, prilikom stvaranja objekta on e se
automatski ubaciti u listu, a prilikom unitavanja on e se automatski izbrisati iz liste.
263
oznaava ime iz podruja klase. Sve definicije umetnutih funkcijskih lanova unutar
deklaracije klase se mogu promatrati kao da su navedene izvan deklaracije klase bez
promjene u znaenju, to znai da mogu pristupati lanovima klase deklariranim nakon
umetnute definicije. Ako se pak upotrebi operator za razluivanje podruja bez
navoenja podruja s lijeve strane, identifikator oznaava ime iz globalnog podruja.
Identifikator ::duljina oznaava globalnu cjelobrojnu varijablu. Operator :: se moe
koristiti i za pristup globalnoj varijabli u funkciji u kojoj je deklarirana istoimena
lokalna varijabla.
Varijabla ne postoji u podruju do mjesta do kojeg je navedena njena deklaracija. U
primjeru
int duljina = 12;
void funkcija() {
int dulj = duljina;
int duljina = 24;
dulj = duljina;
int d = ::duljina;
//
//
//
//
//
//
referencira se ::duljina
globalna duljina postaje
skrivena
pridruuje se 24
pristupa se globalnoj varijabli
i pridruuje se 12
imamo deklaraciju iji je pravi smisao teko odgonetnuti na prvi pogled. U ovom
sluaju ponaanje prevoditelja ovisi o dosta kompleksnim pravilima na koja se nije
dobro oslanjati. Ako i dobro razumijete ta pravila, ne znai da e to razumjeti i ljudi
koji e eventualno biti prisiljeni itati va kd. U gornjem sluaju pravilo kae da se
mjestom deklaracije smatra mjesto na kojem je naveden identifikator, pa se stoga
duljina s desne strane znaka jednakosti odnosi na netom definiranu lokalnu varijablu.
Varijabla duljina u biti ostaje nedefinirane vrijednosti umjesto da joj se dodijeli
vrijednost istoimene globalne varijable.
Svaki funkcijski lan takoer ima svoje podruje u koje smjeta identifikatore
lokalnih varijabli i objekata definiranih u funkciji. Ako se u funkciji deklarira lokalna
264
varijabla istog imena kao podatkovni lan klase tada je lan klase sakriven. No moe mu
se pristupiti pomou operatora za razluivanje podruja:
class Polje {
public:
Polje() { duljina = 10; pokPolje = new int[duljina]; }
int Zbroji();
private:
int duljina, *pokPolje;
};
int Polje::Zbroji() {
int duljina = Polje::duljina;
// dohvaa se statiki
// lan klase
int s = 0;
while (duljina) s += pokPolje[--duljina];
return s;
}
265
266
memorije pozvat emo funkcijski lan objekta i registrirati alokaciju. Kada memorija
vie ne bude potrebna, pozvat emo funkcijski lan koji e izbrisati alokaciju iz liste
alokacija. Koristi od takvog sustava mogu biti viestruke prilikom traenja pogreaka u
programu budui da je jedna od najeih pogreaka neoslobaanje zauzetih segmenata
memorije. Program normalno funkcionira sve do trenutka kada se koliina slobodne
memorije ne smanji tako da program ne moe alocirati dodatnu memoriju. Pomou
sistema praenja alokacije memorije mogli bismo na kraju programa provjeriti jesu li svi
zauzeti segmenti vraeni sistemu.
Bilo bi vrlo korisno takoer imati nekakav pokazatelj koji bi ukazivao na mjesto na
kojem je alokacija bila uinjena. ANSI/ISO C++ specifikacija jezika nudi predefinirane
globalne simbole _ _LINE_ _ i _ _FILE_ _ (dvostruki znak podcrtavanja se nalazi ispred
i iza rijei). Simbol _ _LINE_ _ ima numeriku vrijednost i pokazuje broj linije u kojoj
je simbol naveden, dok simbol _ _FILE_ _ ima vrijednost znakovnog niza i daje naziv
datoteke u kojoj je linija navedena. Na kraju programa bismo mogli ispisati brojeve
linija i nazive datoteka u kojima je obavljena alokacija.
Takav napredan sistem manipulacije memorijom realizirat emo klasom koja e
objedinjavati mehanizme za dodavanje i brisanje alokacije te za ispisivanje liste
alokacija. Svaka alokacija e biti predstavljena jednim objektom koji e se smjetati u
listu prilikom svakog alociranja. Klasa tog objekta ne mora biti dostupna ostatku
programa jedino klasa memorijskog alokatora moe raditi s njom. Zbog toga je
najbolje takvu klasu deklarirati u podruju klase alokatora te se time njeno ime uklanja
iz globalnog podruja. Deklaracija se smjeta u javni, privatni ili zatieni dio
deklaracije klase. Evo primjera kda za memorijski alokator:
#include <alloc.h>
#include <string.h>
#include <iostream.h>
class MemorijskiAlokator {
private:
// ugnijeena klasa
class Alokacija {
public:
Alokacija *slijed, *pret;
size_t velicina;
void *pocetak;
int linija;
char *datoteka;
Alokacija(size_t vel, void *pok, int lin,
char *dat);
~Alokacija();
void Ispisi();
};
Alokacija *prva, *zadnja;
267
public:
MemorijskiAlokator() : prva(NULL), zadnja(NULL) {}
~MemorijskiAlokator();
void Dodaj(size_t vel, void *pok, int lin,
char *dat = NULL);
int Brisi(void *pok);
void Ispisi();
};
MemorijskiAlokator::Alokacija::Alokacija(size_t vel,
void *pok, int lin, char *dat) :
velicina(vel), pocetak(pok), linija(lin),
datoteka(new char [strlen(dat) + 1]),
slijed(NULL), pret(NULL) {
if (dat)
strcpy(datoteka, dat);
else
datoteka[0] = 0;
}
MemorijskiAlokator::Alokacija::~Alokacija() {
delete [] datoteka;
}
void MemorijskiAlokator::Alokacija::Ispisi() {
cout << datoteka << ":" << linija << " " << velicina;
cout << " " << pocetak << endl;
}
Klasa Alokacija sadri podatke o svakoj alokaciji: broj linije i naziv datoteke, adresu
poetka bloka i veliinu. Posjeduje takoer pokazivae na prethodni i na sljedei
element. Vrlo je vano primijetiti da je naziv klase sada dio podruja klase
MemorijskiAlokator. Prilikom definicije lanova klase izvan deklaracije potrebno je
navesti punu stazu do imena klase, a to je MemorijskiAlokator::Alokacija. Klasa
na uobiajeni nain pristupa svojim podatkovnim lanovima. Moe se rei da je klasa
Alokacija ugnijeena, dok je klasa MemorijskiAlokator okolna klasa (engl.
enclosing class). Okolna klasa nema nikakvih posebnih privilegija prilikom pristupa
lanovima ugnijeene klase i obrnuto. Slijedi definicija klase MemorijskiAlokator:
MemorijskiAlokator::~MemorijskiAlokator() {
Alokacija *priv = prva, *priv1;
while (priv) {
priv1 = priv;
priv = priv->slijed;
delete priv1;
}
}
void MemorijskiAlokator::Dodaj(size_t vel, void *pok,
int lin, char *dat) {
268
269
int prva;
class MemorijskiAlokator {
class Alokacija {
// ...
void Funkcija();
};
Alokacija *prva;
// ...
};
void MemorijskiAlokator::Alokacija::Funkcija() {
prva = 0;
// pogreka prilikom prevoenja
}
Upravo zbog takvog algoritma prilikom razluivanja podruja nije potrebno navoditi
podruje prilikom referenciranja statikog lana. Statiki lan ima smisla i bez objekta
klase. Ugnijeena klasa takoer moe referencirati pobrojenja, tipove i druge klase
definirane u okolnoj klasi pod pretpostavkom da ugnijeena klasa ima pravo pristupa.
Ukoliko se ugnijeena klasa nalazi unutar javnog dijela tijela okolne klase, mogu
se objekti te klase koristiti i u glavnom programu. Prilikom definiranja takvih objekata
potrebno je navesti potpuno ime klase pomou operatora za razluivanje podruja.
Pretpostavimo da je deklaracija klase MemorijskiAlokator ovako napisana:
class MemorijskiAlokator {
private:
// ... privatni clanovi
public:
class Alokacija {
public:
Alokacija *slijed, *pret;
// ...
};
};
270
Sada je mogue u glavnom programu deklarirati objekt klase Alokacija, pri emu je
potrebno navesti puno ime ugnijeene klase:
int main() {
// deklaracija objekta ugnijeene klase
MemorijskiAlokator::Alokacija aloc, aloc1;
aloc.slijed = NULL;
aloc.pret = &aloc1;
// ...
return 0;
}
271
void Clan() {
broj = sirina;
//
//
broj += duljina; //
//
}
};
Lokalna lokVar;
// ... tijelo funkcije
}
X2,Y2
odrezujue podruje
X1,Y1
Slika 8.4. Dijelovi linije koji padaju izvan podruja odrezivanja se uklanjaju
272
Podatkovnim lanovima koji pamte koordinate dali smo javni pristup kako bismo
demonstrirali upotrebu pokazivaa na njih. Pokaziva na cjelobrojni lan klase Linija
ima sljedei tip:
int Linija::*
273
int Linija::*pokKoord;
// neispravno
// ispravno
U ovom sluaju uzima se adresa lana koji je dio objekta. Kako je objekt stvoren na
tono odreenom mjestu u memoriji, adresa lana je u potpunosti poznata te je njeno
uzimanje sasvim legalno.
Adresu lana klase moemo dohvatiti pomou operatora & te pridruiti pokazivau
na lan klase:
int Linija::*pokKoord = &Linija::X1;
// ispravno
Sada smo varijabli pokKoord pridruili podatak koji identificira pojedini lan klase
Linija. Valja primijetiti da se adresa realnog lana klase ne moe pridruiti
pokazivau na cjelobrojni lan. Takoer, nije mogue pridruiti adresu lana jedne
klase pokazivau na lan neke druge klase:
class Elipsa {
public:
int cx, cy, velikaPoluos;
float ekscentricitet;
};
class Linija { /* ... */ };
// ...
int Elipsa::*pokNaCjelobrojni = &Elipsa::ekscentricitet;
// neispravno: pokaziva pokazuje na cjelobrojni lan,
// a pokuano mu je pridruiti adresu realnog lana
int Linija::*pokNaClanLinije = &Elipsa::cx;
// neispravno: pokaziva pokazuje na lan klase Linija,
// a pridruena mu je adresa lana klase Elipsa
float Elipsa::*pokNaClanElipse = &Elipsa::ekscentricitet;
// ispravno: pokazivai se slau po tipu i po klasi
274
275
u tip void *. Takoer nema implicitne konverzije u aritmetiki tip te nije podrana
pokazivaka aritmetika. To znai da naredbom
if (pokNaClan)
276
277
278
Kao i kod pokazivaa na funkcije, nije potrebno navoditi operator za uzimanje adrese
prije navoenja funkcijskog lana. Tako je zapis pokazivaa &GrafObjekt::Gore i
GrafObjekt::Gore ekvivalentan.
Prilikom pozivanja funkcijskog lana pomou pokazivaa na njega koriste se
operatori .* i ->* pri emu se s lijeve strane operatora navodi objekt za operator .*
odnosno pokaziva na objekt za operator ->*, a s desne strane pokaziva na funkcijski
lan. Iza njega se u zagradama navode mogui parametri funkcijskog lana. Evo
primjera:
GrafObjekt grObj, *pokNaGrObj = &grObj;
// uzimanje adrese funkcijskog lana
int (GrafObjekt::*bezparam)() = GrafObjekt::Gore;
// poziv funkcijskog lana preko pokazivaa
(grObj.*bezparam)();
void (GrafObjekt::*dvaparam)(int, int);
dvaparam = GrafObjekt::PostaviVelicinu;
(pokNaGrObj->*dvaparam)(5, 6); // poziv preko pokazivaa
Upotreba zagrada oko naziva funkcije i objekta je obavezna zato jer operator () za
poziv funkcije ima vei prioritet izvoenja. U gore navedenom pozivu funkcijskog
lana na koji pokazuje bezparam redoslijed izvoenja operatora je sljedei: najprije se
pomou
grObj.*bezparam
279
grObj.*bezparam();
bi se interpretirala kao
grObj.*(bezparam());
280
Zadatak. Evo jednog primjera iz ivota (!). Nainite klasu Tijelo koje e opisivati
ljudsko tijelo. Pri tome sami izaberite skup organa koje elite ukljuiti u klasu. Svaki
organ neka je predstavljen jednim podatkovnim lanom tipa bool koji e pokazivati da
li dotini organ radi ispravno ili ne. Evo primjera:
class Tijelo {
private:
bool srce;
bool lijeviBubreg;
bool desniBubreg;
bool jetra;
bool slezena;
};
281
C++ jezika i u velikoj mjeri ovise o implementaciji prevoditelja. Svi primjeri navedeni u
ovom poglavlju su prevedeni pomou Borland C++ 4.5 prevoditelja.
8.13.1. Eksplicitno stvoreni privremeni objekti
Zbrajanje dvaju vektora moemo obaviti tako da deklariramo tri objekta klase Vektor
te pozovemo funkciju ZbrojiVektore():
Vektor a(10.0, 2.8);
Vektor b(-2.0, 5.0);
Vektor c;
ZbrojiVektore(a, b, c);
Ovakav nain rada je sasvim ispravan i regularan. Objekt c e na kraju sadravati zbroj
vektora a i b. Jedina je mana to vektor a, iako nikada kasnije u programu ne koristimo,
moramo deklarirati, i to je jo ozbiljnije, za njega odvojiti memorijski prostor koji
ostaje zauzet do kraja bloka unutar kojeg je deklariran. Mnogo praktinije je provesti
282
raunsku operaciju tako da stvorimo privremeni objekt klase Vektor koji e ivjeti
samo za vrijeme raunanja zbroja, a nakon toga e biti uniten. Trebamo, dakle, nain
da u kd upiemo vektorsku konstantu, ekvivalentnu cjelobrojnim konstantama koje
piemo u klasinim izrazima.
Privremeni objekt moemo stvoriti tako da navedemo naziv klase i u okruglim
zagradama stavimo parametre konstruktoru (ili samo otvorenu i zatvorenu zagradu ako
parametre izostavljamo) . Prema tome bismo isti rezultat kao i u gornjem primjeru mogli
postii sljedeim pozivom:
Vektor c;
ZbrojiVektore(Vektor(10.0, 2.8), Vektor(-2.0, 5.0), c);
Iako dovitljivi itatelj moe primijetiti da bi bilo daleko jednostavnije i efikasnije odmah
deklarirati vektor c i dodijeliti mu vrijednost izraunatu pomou depnog raunala, ovaj
primjer pokazuje kako se mogu stvoriti privremeni objekti. Prije ulaska u funkciju
ZbrojiVektore() prevoditelj e stvoriti dva privremena objekta, a zatim pozvati
funkciju adrese objekata e biti proslijeene preko referenci. Kada funkcija zavri,
prevoditelj e privremene objekte unititi.
ivot privremenih objekata je ogranien na izraz u kojem se pojavljuju. Na kraju
naredbe (kod toke-zarez) svi objekti stvoreni u izrazu se unitavaju. Da bi se mogao
pratiti tok stvaranja i unitavanja objekata, moemo proiriti konstruktore i destruktore
naredbama koje ispisuju poruke o tome da je objekt stvoren, odnosno uniten. Klasi
Vektor emo takoer dodati statiki lan brojac koji e brojati stvorene objekte.
Svaki objekt e zapamtiti broj pod kojim je stvoren u lanu redbr. Vrijednost tog lana
e se ispisati prilikom stvaranja i unitavanja objekta, te emo na taj nain moi
identificirati objekte.
#include <iostream.h>
class Vektor {
friend void ZbrojiVektore(Vektor &a, Vektor &b,
Vektor &rez);
private:
static int brojac;
int redbr;
float ax, ay;
public:
Vektor(float x = 0, float y = 0);
~Vektor();
float DajX() { return ax; }
float DajY() { return ay; }
void PostaviXY(float x, float y) { ax = x; ay = y; }
};
int Vektor::brojac = 0;
283
Vektor::Vektor(float x, float y) :
ax(x), ay(y), redbr(++brojac) {
cout << "Stvoren vektor pod brojem " << redbr << endl;
cout << "X: " << ax << "
Y: " << ay << endl;
}
Vektor::~Vektor() {
cout << "Uniten vektor pod brojem " << redbr << endl;
cout << "X: " << ax << "
Y: " << ay << endl;
}
void ZbrojiVektore(Vektor &a, Vektor &b, Vektor &rez) {
cout << "Zbrajam" << endl;
rez.ax = a.ax + b.ax;
rez.ay = a.ay + b.ay;
cout << "Zbrojio sam" << endl;
}
284
Zbrojio sam
Uniten vektor pod brojem 3
X: 10
Y: 2.8
Uniten vektor pod brojem 2
X: -2
Y: 5
Zavravam
Uniten vektor pod brojem 1
X: 8
Y: 7.8
Vektor pod brojem 1 je vektor c deklariran kao lokalni objekt. Prije ulaska u funkciju se
stvaraju privremeni vektori koji se unitavaju odmah nakon povratka iz nje. Program
zatim zavrava te se na samom kraju unitava i objekt 1.
Mogue je pozvati funkcijski lan privremenog objekta ili pristupati njegovim
podatkovnim lanovima. Dodajmo funkcijski lan IspisiVektor() za ispis vektora te
promotrimo rezultat sljedeeg kda:
class Vektor {
// ... ovdje ide deklaracija klase
public:
void IspisiVektor();
};
void Vektor::IspisiVektor() {
cout << "Ispis {" << ax << "," << ay << "}" << endl;
}
int main() {
cout << "Prije izraza" << endl;
Vektor(12.0, 3.0).IspisiVektor();
cout << "Nakon izraza" << endl;
return 0;
}
285
Ispis je sljedei:
Uao sam u main
Stvoren vektor pod brojem 1
X: 12
Y: 3
Pozivam NaNulu
Stvoren vektor pomou konstruktora kopije pod brojem 2
X: 12
Y: 3
Uao u NaNulu
Postavio sam
Uniten vektor pod brojem 2
286
X: 0
Y: 0
Zavravam
Uniten vektor pod brojem 1
X: 12
Y: 3
287
memoriju gdje je bio privremeni objekt. I dok pristup podatkovnom lanu pomou tog
pokazivaa vjerojatno nee sruiti sistem te e samo vratiti neku nedefiniranu
vrijednost, pristup funkcijskom lanu e vrlo vjerojatno uzrokovati ispad programa.
Takoer e pokuaj upisivanja vrijednosti u objekt vrlo vjerojatno rezultirati
prepisivanjem preko drugih podataka, to za aplikaciju moe biti pogubno. Slini efekti
se mogu postii i tako da se reference inicijaliziraju objektima koji se zatim unite.
Pouka ovog primjera je da su visei pokazivai i reference vrlo opasni po integritet
programa. Po mnogim studijama oni su jedni od najeih uzroka nepredvienog kraha
mnogih komercijalnih programa te valja biti vrlo oprezan da se ovakve situacije
izbjegnu.
Ponekad optimizacije izvedbenog koda koje prevoditelj provodi mogu dovesti do
toga da postupak prenoenja objekata odstupa od gore izloenog. Promotrimo sljedei
primjer:
int main() {
cout << "Uao sam u main" << endl;
cout << "Pozivam NaNulu" << endl;
NaNulu(Vektor(12, 3));
cout << "Zavravam" << endl;
return 0;
}
288
interpretira kao Stvori vektor a i zatim stvori vektor b tako da konstruktorom kopije
kopira a u b. Inicijalizacija
int main() {
cout << "Uao sam u main" << endl;
Vektor a = Vektor(12.0, 3.0);
// ...
return 0;
}
esto je neophodno vratiti objekt kao rezultat funkcije. Dobar kandidat za tako neto je
funkcija ZbrojiVektore(). Prepravimo funkciju tako da uzima za parametre dva
operanda te vraa zbroj kao rezultat. Pri tome joj je potrebno dodijeliti prava pristupa
privatnim lanovima klase Vektor.
Vektor ZbrojiVektore(Vektor &a, Vektor &b) {
cout << "Uao u ZbrojiVektore" << endl;
Vektor rez(a.ax + b.ax, a.ay + b.ay);
return rez;
}
289
return 0;
}
290
Sada se ne stvara lokalni objekt koji se zatim kopira u rezultat, nego se stvara
privremeni objekt koji se nakon zavretka proglaava rezultatom funkcije. Nakon
izvoenja program daje sljedee rezultate:
Uao u main
Stvoren vektor pod brojem
X: 12
Y: 3
Stvoren vektor pod brojem
X: -2
Y: -6
Stvoren vektor pod brojem
X: 0
Y: 0
Ulazim u ZbrojiVektore
Uao u ZbrojiVektore
Stvoren vektor pod brojem
X: 10
Y: -3
Uniten vektor pod brojem
X: 10
Y: -3
Zavravam
Uniten vektor pod brojem
X: 10
Y: -3
Uniten vektor pod brojem
X: -2
Y: -6
Uniten vektor pod brojem
X: 12
Y: 3
1
2
3
4
4
4
2
1
Funkcija takoer moe vratiti referencu na objekt te se tada objekt ne kopira. Tipian
primjer takve funkcije je funkcijski lan koji vraa referencu na neki od lanova
objekta. Klasa Tablica koju smo uveli u poglavlju 8.4.1 ima lanove za ubacivanje
elemenata u tablicu te njeno poveanje i smanjenje. No nigdje nismo naveli funkcijski
lan kojim se moe pristupiti pojedinom elementu tablice. Stoga emo dodati lan
Element koji e kao parametar imati redni broj elementa tablice koji elimo dohvatiti
te e vratiti referencu na eljeni lan. Evo kako to izgleda:
class Tablica {
private:
int *Elementi;
int BrojElem, Duljina;
public:
Tablica();
Tablica(int BrElem);
void PovecajNa(int NovaDulj);
void DodajElem(int Elt);
void BrisiElem(int Poz);
int &Element(int indeks);
};
291
Paljiv itatelj sigurno se pita zato se vraa referenca na element umjesto da se vraa
sam element. Razlog je u tome to se prilikom vraanja elementa stvara privremeni
objekt koji ivi do kraja izraza u kojem je stvoren te nema nikakve veze s elementom
tablice. U sluaju vraanja reference nema stvaranja privremenog objekta. Referenca
nije nita drugo nego adresa koja pokazuje gdje se u memoriji nalazi objekt i moe se
nai s lijeve strane operatora pridruivanja. Mogue je napisati sljedee:
int main() {
Tablica t;
// inicijaliziraj tablicu na 5 elemenata
for (int i = 0; i < 5; i++) t.DodajElem(i * 8);
cout << t.Element(3);
// pristup vrijednosti treeg
// elementa
t.Element(2) = 45;
// izmjena vrijednosti
// drugog elementa
return 0;
}
Nije ispravno vratiti referencu na lokalni objekt zato jer se lokalni objekt unitava nakon
to funkcija zavri. Vraena referenca e uvijek pokazivati na podruje u memoriji koje
ne sadrava objekt, na to e mnogi prevoditelji upozoriti prilikom prevoenja.
292
9. Strukture i unije
Da sam bio prisutan prilikom Stvaranja,
dao bih nekoliko korisnih savjeta
za bolji ustroj svemira.
Alfonso X, Mudri (12211284)
Jezik C++ uvodi novinu u odnosu na C prilikom deklaracije objekata iji je tip definiran
strukturom. U C jeziku prilikom deklaracije strukturnih varijabli potrebno je bilo navesti
293
kljunu rije struct iza koje se navodio naziv strukture te naziv varijable, kao na
primjer:
struct Par {
float a, b;
};
struct Par parBrojeva;
// deklaracija u C stilu
U C++ jeziku prilikom deklaracije varijabli nije potrebno navoditi poetnu rije
struct, nego se jednostavno samo navede naziv strukture iza kojeg se navedu nazivi
objekata koje se eli deklarirati, ba kao da se radi o klasama:
Par parBrojeva;
9.2. Unije
Zamislimo da elimo napraviti program koji simulira kalkulator. Radi jednostavnosti
pretpostavimo da korisnik najprije unosi prvi operand koji je broj, zatim operator, zatim
drugi operand te na kraju znak jednakosti. Pri tome elimo unos korisnika prikazati
jednim objektom koji e se dalje obraivati u programu za izraunavanje rezultata.
Vidimo da nam se unos sastoji od dva sutinski razliita tipa podataka: brojeva, u
sluaju da je korisnik unio broj, te niza znakova, u sluaju da je korisnik unio operator
294
(uzimamo niz znakova jer je korisnik mogao unijeti primjerice sqrt kao operator za
kvadratni korijen). Unos korisnika bismo mogli opisati sljedeom klasom:
enum VrstaUnosa { unosOperator, unosBroj };
class Unos {
public:
VrstaUnosa tip;
int broj;
char *nizOperator;
};
Klasa sadri podatkovni lan tip koji svojom vrijednou odreuje da li objekt
predstavlja broj ili operator. lanovi broj i nizOperator sadre podatke o broju
odnosno operatoru koji je unesen.
Vano je primijetiti da nikada nije potrebno pamtiti podatke i o broju i o operatoru:
prisutan je samo jedan ili drugi podatak. Upravo zbog toga gornje rjeenje nije efikasno
sa stajalita zauzea memorijskog prostora. Svaki objekt klase sadri oba lana, bez
obzira na to to je u jednom trenutku potreban samo jedan od njih. Bilo bi vrlo pogodno
kada bi lanovi broj i nizOperator mogli dijeliti memorijski prostor pa bi u svakom
objektu postojao samo jedan ili samo drugi lan, ovisno o potrebi.
Upravo to je mogue postii upotrebom posebnih tipova podataka nazvanih
unijama (engl. union). Unije se deklariraju slino klasama i strukturama s tom razlikom
to se koristi kljuna rije union. One mogu sadravati podatkovne i funkcijske
lanove, ugnijeene klase i rijei za dodjelu prava pristupa. Bitna razlika u odnosu na
klase je u tome to svi podatkovni lanovi unije dijele isti memorijski prostor. To znai
da se prilikom pridruivanja vrijednosti jednom podatkovnom lanu prepisuje vrijednost
podatkovnog lana koji je bio ranije upisan. Objekt Unos napisan pomou unija
izgledao bi ovako:
union Unos {
int broj;
char *nizOperator;
};
Unos moe sadravati ili broj ili nizOperator, ali nikako oba odjednom. Mogue je
definirati objekte tipa Unos. To se radi na isti nain kao i prilikom definiranja objekata
Unije su postojale i u C jeziku gdje se prije definiranja unije ispred naziva morala
stavljati kljuna rije union:
union Unos saTipkovnice;
// deklaracija la jezik C
U C++ jeziku to vie nije potrebno (no ako se koristi nije pogreno).
295
Kako svi lanovi dijele isti memorijski prostor, veliina unije je jednaka veliini
njenog najveeg lana. Budui da se razliiti tipovi podataka u memoriju pohranjuju
prema razliitim bitovnim predlocima, vrlo je vano biti oprezan prilikom koritenja
unija kako bi se uvijek pristupalo podatkovnom lanu iju vrijednost unija u tom
trenutku sadrava. Ako pridruimo neku vrijednost lanu broj unije Unos, nije
sintaktiki pogreno itati vrijednost lana nizOperator. No program koji to radi
zasigurno nee funkcionirati ispravno. Vrijednost koja e se u takvom sluaju proitati
iz lana nizOperator je u principu sluajna i ovisi o nainu na koji raunalo
pohranjuje cjelobrojni i pokazivaki tip. Na primjer, kd
#include <iostream.h>
int main() {
union Unos saTipkovnice;
saTipkovnice.nizOperator = "+";
cout << saTipkovnice.broj << endl;
return 0;
}
e ispisati broj ija e vrijednost ovisiti o mjestu na kojem se nalazi znakovni niz "+".
Kako se i cijeli broj i pokaziva na znak pohranjuju u isti memorijski prostor, prilikom
itanja lana broj raunalo e jednostavno pristupiti memoriji i proitati vrijednost
koja je tamo zapisana. Ta vrijednost ovisi o adresi znakovnog niza te o nainu njene
pohrane.
Unija moe sadravati objekte klasa pod uvjetom da oni nemaju definiran niti
konstruktor niti destruktor. Sama unija, naprotiv, moe sadravati i vie konstruktora i
destruktor. Statiki lanovi unija nisu dozvoljeni. Pojedinim lanovima se moe
dodijeliti proizvoljno pravo pristupa. Ako se pravo pristupa ne navede, podrazumijeva
se javni pristup. Unije mogu sadravati funkcijske lanove. Prilikom pisanja funkcijskih
lanova takoer valja imati na umu da unija odjednom sadri samo jedan objekt.
Kako bi se vodila evidencija o tome koji je podatak upisan u uniju, esto se unija
navodi kao podatkovni lan neke klase. Jedan lan klase obino prati koji je podatkovni
lan unije trenutno aktivan. Taj lan se zove diskriminanta unije (engl. union
discriminant). Ako se prilikom deklaracije unije odmah deklarira varijabla ili
podatkovni lan klase tipa te unije, a unija se vie nigdje ne koristi, njeno se ime moe
izostaviti. Demonstrirajmo to primjerom:
class Unos {
enum { unosOperator, unosBroj} tip;
union {
// unija nema ime
int broj;
char *nizOperator;
} vrijednost;
};
296
Klasa Unos se sada sastoji iz podatkovnog lana tip koji odreuje vrstu unosa te iz
lana vrijednost koji je definiran kao bezimena unija (engl. nameless union) i koji
odreuje vrijednost unosa. Sada je mogue napisati funkcijski lan za ispis vrijednosti s
tipkovnice na sljedei nain:
class Unos {
// Ovdje treba umetnuti gornju definiciju
void Ispis();
};
void Unos::Ispis() {
switch (tip) {
case unosOperator:
cout << vrijednost.nizOperator << endl;
break;
case unosBroj:
cout << vrijednost.broj << endl;
break;
}
}
U gornjoj definiciji uveden je podatkovni lan vrijednost koji sadri vrijednost unosa
te ga je potrebno navoditi svaki put prilikom pristupa lanovima broj i
nizOpertator. To je dosta nepraktino i moe se izbjei koritenjem anonimnih unija
(engl. anonymous unions). Anonimna unija nema niti naziv unije niti naziv varijable
koja se deklarira. Elementi unije tada pripadaju podruju u kojem je unija definirana te
im se pristupa direktno:
class Unos {
enum { unosOperator, unosBroj} tip;
union {
// anonimna unija
int broj;
char *nizOperator;
};
void Ispis();
};
void Unos::Ispis() {
switch (tip) {
case unosOperator:
cout << nizOperator << endl;
break;
case unosBroj:
cout << broj << endl;
break;
}
}
297
U ovoj zavrnoj verziji klase Unos lanovi broj i nizOperator pripadaju podruju
klase Unos te njihov naziv mora biti jedinstven unutar klase.
Zadatak. Napiite univerzalni tip koji e moi sadravati cijeli broj, realni broj ili
pokaziva na znakovni niz. Opi oblik je ovakav:
class MultiPraktik {
char tip;
union {
int cj_vrij;
double d_vrij;
char *zn_vrij;
};
};
{
short dozvoljen : 1;
short prioritet : 3;
short maska : 2;
Klasa Prekid ima tri polja bitova te e biti upakirana u memorijski prostor koji
zauzima tip unsigned short. Svakom od lanova mogue je nezavisno pristupati, a
prevoditelju se preputa da se brine o tome da modificira samo odreene bitove nekog
podatka. Pristup poljima bitova obavlja se standardnom C++ sintaksom:
298
Prekid int1;
// ...
int1.dozvoljen = 0;
if (int1.prioritet == 2) int1.maska = 1;
Prilikom pridruivanja je potrebno voditi rauna o opsegu vrijednosti koje pojedini lan
moe pohraniti. Na primjer, lan dozvoljen je duljine samo jednog bita, tako da moe
poprimiti samo vrijednosti 1 ili 0. lan prioritet je duljine tri bita, pa moe poprimiti
vrijednosti od 0 do 7 ukljuivo.
Operator za uzimanje adrese & se ne moe primijeniti na polje bitova pa tako niti
pokaziva na lan na polje bitova nema smisla. Takoer, polje bitova ne moe
sadravati statiki lan.
Zadatak. Promijenite program iz odsjeka 2.4.11 za definiranje parametara serijske
komunikacije tako da se umjesto direktnog pristupa pojedinim bitovima cjelobrojne
varijable koriste polja bitova.
299
300
Zbog toga emo uvesti tip ZnakovniNiz koji e sadravati pokaziva na niz
znakova te niz funkcijskih lanova, operatora i operatora konverzije potrebnih za
manipulaciju nizom. Evo deklaracije klase:
#include <iostream.h>
#include <string.h>
class ZnakovniNiz {
private:
char *pokNiz;
public:
// implementaciju klase emo dodavati postupno
};
Konstruktor koji ima jedan parametar obavlja konverziju podatka iz tipa parametra u
objekt klase. Dodajmo klasi ZnakovniNiz konstruktor, te destruktor koji e osloboditi
zauzetu memoriju. Kako bismo tono pokazali kako se provodi proces konverzije,
umetnut emo u konstruktor i destruktor naredbe koje ispisuju poruke prilikom
stvaranja i unitavanja objekta. Odmah emo dodati i konstruktor kopije koji e nam
kasnije biti potreban.
Konstruktor s jednim parametrom obavlja konverziju iz tipa parametra u tip
klase. Konstruktor kopije nije operator konverzije, jer bi on konvertirao tip u
samoga sebe.
class ZnakovniNiz {
private:
char *pokNiz;
public:
// konstruktor koji e obaviti i konvenrziju
// u tip ZnakovniNiz
ZnakovniNiz(char *niz = "");
ZnakovniNiz(const ZnakovniNiz &ref);
~ZnakovniNiz();
char *DajPokazivac() { return pokNiz; }
};
301
ZnakovniNiz::ZnakovniNiz(char *niz) :
pokNiz(new char[strlen(niz) + 1]) {
strcpy(pokNiz,niz);
cout << "Stvoren niz: " << niz << endl;
}
ZnakovniNiz::ZnakovniNiz(const ZnakovniNiz &ref) :
pokNiz(new char[strlen(ref.pokNiz) + 1]) {
strcpy(pokNiz, ref.pokNiz);
}
ZnakovniNiz::~ZnakovniNiz() {
cout << "Uniten niz: " << pokNiz << endl;
delete [] pokNiz;
}
//
//
//
//
konverzija prilikom
inicijalizacije
konverzija prilikom
prenoenja parametara
return 0;
}
niz: Niz a
niz: parametar
funkcija s parametrom: parametar
niz: parametar
niz: Niz a
302
Ponekad moe biti potrebno deklarirati eksplicitni konstruktor, odnosno konstruktor koji
se nee pozivati implicitno kao operator konverzije. To se moe uiniti tako da se
ispred deklaracije konstruktora navede kljuna rije explicit:
class Razlomak {
private:
float brojnik, nazivnik;
public:
// eksplicitni konstruktor
explicit Razlomak(float raz) : brojnik(raz),
nazivnik(1.0) {}
};
303
Pri tome je potrebno tip zamijeniti nazivom tipa u koji se pretvorba obavlja. Operator
za konverziju ne smije imati naveden rezultirajui tip niti parametre.
Dodajmo klasi ZnakovniNiz operator koji konvertira objekt u pokaziva na prvi
lan:
class ZnakovniNiz {
// ...
public:
operator char*() { return pokNiz; }
};
Sada je mogue koristiti objekt klase ZnakovniNiz svugdje gdje se moe pojaviti
pokaziva na znak. Na primjer:
#include <stdio.h>
int main() {
ZnakovniNiz a = "abrakadabra";
puts(a);// poziva konverziju objekta a u char *
return 0;
}
Jedna klasa moe definirati vie operatora konverzije u razliite tipove. Moemo dodati
operator koji konvertira ZnakovniNiz u cijeli broj jednak duljini niza:
class ZnakovniNiz {
// ...
public:
operator int() { return strlen(pokNiz); }
};
Kada klasa ima vie operatora konverzije, vrlo esto dolazi do situacije da prevoditelj ne
moe razluiti koju konverziju treba primijeniti:
void Ispisi(char *pokZnak) {
cout << "Ispisujem niz: " << pokZnak << endl;
}
void Ispisi(int broj) {
cout << "Ispisujem broj: " << broj << endl;
}
int main() {
ZnakovniNiz a = "U to e me pretvoriti?";
Ispisi(a);
// pogreka: koja konverzija?
return 0;
}
304
U ovom sluaju nije jasno treba li objekt a pretvoriti u cijeli broj ili u pokaziva na znak
te se zbog toga dobiva pogreka prilikom prevoenja. Programer mora eksplicitno
naznaiti koju konverziju eli pomou klasine sintakse za dodjelu tipa. U okruglim
zagradama se navede eljeni tip, a iza zatvorene zagrade se navede objekt. Eksplicitna
pretvorba u pokaziva na znak izgleda ovako:
Ispisi((char *)a);
Ako se ciljni tip ne moe direktno dostii jednom korisniki definiranom pretvorbom,
prevoditelj e pokuati pronai korisniki definiranu konverziju koja e podatak
pretvoriti u meutip, koji se zatim svodi na traeni tip standardnom konverzijom.
Korisniki definirana konverzija se ne obavlja ako je na rezultat konverzije potrebno
primijeniti jo jednu korisniki definiranu konverziju. Na primjer:
void PisiLong(long broj) {
cout << "Long broj: " << broj << endl;
}
int main() {
ZnakovniNiz a = "Dugi niz";
PisiLong(a);// poziva se konverzija u int, a int
// se zatim standardnom konverzijom
// pretvara u long
return 0;
}
Radi daljnje demonstracije svojstava operatora konverzije, dodat emo jo jednu klasu
Identifikator koja moe biti od koristi prilikom izgradnje prevoditeljskog
programa. Klasa modelira svojstva identifikatora te sadri objekt naziv klase
ZnakovniNiz koji pamti naziv identifikatora, te cjelobrojni lan indeks koji pokazuje
indeks identifikatora u tablici identifikatora koju prevoditelj generira prilikom
prevoenja. Klasu emo opremiti s dva operatora konverzije, i to konverzijom u tip
ZnakovniNiz koji e vraati vrijednost lana naziv, te u int koji e vraati
vrijednost lana indeks.
class Identifikator {
private:
int indeks;
ZnakovniNiz naziv;
public:
Identifikator(int ind, const ZnakovniNiz &ref) :
indeks(ind), naziv(ref) { }
305
Ako bismo eljeli da nam ovaj kd proradi, morali bismo umetnuti u klasu
Identifikator operator za konverziju u char *:
class Identifikator {
public:
// ...
operator char*() { return naziv; }
// implicitno se poziva konverzija
// klase ZnakovniNiz u char*
};
Sada je gornji poziv ispravan. Pri tome se u naredbi return provodi implicitna
konverzija iz tipa ZnakovniNiz u char * pomou korisniki definirane konverzije iz
klase ZnakovniNiz. Opisana konverzija u char * moe imati neugodne popratne
pojave:
int main() {
ZnakovniNiz pobjednikNaLutriji = "Janko";
char *pokNiz = pobjednikNaLutriji;
// gornja naredba provodi implicitnu konverziju tako da
// vraa pokaziva na implementaciju
*pokNiz = 'R'; // pobjednik postaje Ranko
return 0;
}
306
Zadatak. Dodajte klasi ZnakonvniNiz konstruktor koji e kao parametar uzimati cijeli
broj. Pri tome e se znakovni niz inicijalizirati tako da sadri zadani broj. Na primjer,
deklaracija
ZnakovniNiz godina(1997);
307
Zadatak. Konstruktor klase Razlomak promijenite tako da realni broj svede na najblii
razlomak (pri tome razlomak mora imati cijeli brojnik i prirodni nazivnik).
.*
::
?:
sizeof
Takoer, nije mogue uvesti novi operator. Na primjer, jezik FORTRAN posjeduje
operator ** za potenciranje koji bi vrlo esto bio koristan i u C++ programima. No
308
naalost, nije mogue dodati novi operatorski simbol, ve samo proiriti znaenje ve
postojeih simbola.
Svaki operator zadrava broj argumenata koji ima po standardnoj C++ sintaksi. To
znai da zato to je operator << definiran kao binarni, mora biti i preoptereen kao
binarni. Redoslijed obavljanja raunskih operacija takoer ostaje nepromijenjen.
Tablica 10.2. Operatori koji se mogu preopteretiti
+
~
++
+=
<<=
!
--=
>>=
*
,
<<
/=
[]
/
=
>>
%=
()
%
<
==
^=
->
^
>
!=
&=
->*
&
<=
&&
|=
new
|
>=
||
*=
delet
e
Operator se definira kao funkcija ili funkcijski lan koji za naziv ima kljunu rije
operator iza koje se stavlja simbol operatora. Operator se moe, a i ne mora odvojiti
razmakom od kljune rijei operator i liste parametara. Operatori
+
&
postoje u unarnoj i binarnoj varijanti (dakle, mogu uzimati jedan ili dva parametra), pa
se mogu i preopteretiti kao unarni ili binarni.
Kako ima smisla zbrajati vektore, moemo u klasu Vektor ubaciti operator za
zbrajanje:
class Vektor {
friend Vektor operator+(Vektor &a, Vektor &b);
private:
float ax, ay;
public:
Vektor(float x = 0, float y = 0) { ax = x; ay = y; }
void PostaviXY(float x, float y) { ax = x; ay = y; }
float DajX() const { return ax; }
float DajY() const { return ay; }
void MnoziSkalarom(float skalar);
};
inline
void Vektor::MnoziSkalarom(float skalar) {
ax *= skalar;
ay *= skalar;
}
Vektor operator+(Vektor &a, Vektor &b) {
return Vektor(a.ax + b.ax, a.ay + b.ay);
}
309
se interpretira kao
c = operator+(a, b);
310
[]
()
->
mogu iskljuivo biti definirane kao funkcijski lanovi. Ako se operatorska funkcija
definira kao funkcijski lan, broj parametara je za jedan manji nego u sluaju kada je
funkcija definirana izvan klase.
Definirajmo klasu X te operatore + i -. Binarni operatori ije funkcije su definirane
kao lanovi imaju jedan parametar, dok unarni nemaju parametara. Takoer emo
definirati objekte a i b klase X:
class X {
public:
X operator-(const X &desno);
};
X operator+(const X &lijevo, const X &desno);
X a, b;
Poziv
a + b;
interpretira se kao
operator+(a, b);
dok se poziv
a - b;
interpretira kao
a.operator-(b);
Dakle, ako je operator definiran izvan klase, lijevi i desni objekt se prosljeuju
operatorskoj funkciji kroz prvi odnosno drugi parametar. Kada je operatorska funkcija
definirana kao funkcijski lan, poziva se funkcijski lan za objekt lijevo od operatora,
dok se objekt desno od operatora prosljeuje kroz parametar.
Operatori se mogu preopteretiti samo za korisniki definirane tipove. U
sluaju da se operatorska funkcija definira izvan klase, barem jedan od
parametara mora biti klasa.
Kako operatori +, -, * i & imaju svoju unarnu i binarnu varijantu, obje varijante mogu
biti preoptereene neovisno, kao u sljedeem primjeru:
311
class Y {
public:
// definicija unutar klase
void operator*();
Y operator*(const Y &ref);
};
// unarni *
// binarni *
a.operator*();
a.operator*(b);
operator&(a);
operator&(a, b);
Iako se ini da je svejedno je li operatorska funkcija definirana unutar ili izvan klase,
postoji sutinska razlika u jednom i drugom pristupu.
Ako je operator definiran kao funkcijski lan, na lijevi argument se ne
primjenjuju korisniki definirane konverzije.
Na primjer:
class Kompleksni {
friend Kompleksni operator-(const Kompleksni &l,
const Kompleksni &d);
private:
float real, imag;
public:
Kompleksni(float r = 0, float i = 0) :
real(r), imag(i) {}
Kompleksni operator+(const Kompleksni &d);
};
Kompleksni Kompleksni::operator+(const Kompleksni &d) {
return Kompleksni(real + d.real, imag + d.imag);
}
Kompleksni operator-(const Kompleksni &l,
const Kompleksni &d) {
312
Kako je bilo koji realni broj ujedno i kompleksni (njegov imaginarni dio je nula), klasa
Kompleksni ima konstruktor koji moe svaki realni broj konvertirati u kompleksni.
Operator + je definiran kao funkcijski lan, dok je operator - definiran izvan klase.
Matematiki je sasvim ispravno zbrajati ili oduzimati realan i kompleksan broj, ali e
pokuaj zbrajanja u sljedeem primjeru izazvati nevolje:
int main() {
Kompleksni a, b;
b = 5 + a; // pogreka: 5 se ne konvertira u Kompleksni
b = 5 - a; // OK: 5 se konvertira u Kompleksni
return 0;
}
interpretira kao
5.operator+(a)
// pogreka
to je oigledna glupost, jer je objekt s lijeve strane ugraeni tip za koji se ne mogu
uope pozivati funkcijski lanovi. Naprotiv, poziv
5 - a;
se interpretira kao
operator-(5, a);
// OK
313
Tako nije mogue, na primjer, definirati novi operator za zbrajanje realnih brojeva, jer
se time zapravo ne preoptereuje operator za novi tip, nego se mijenja znaenje
ugraenog operatora za ve postojee tipove:
Kompleksni operator+(float l, float d); // pogreka
314
U gornjem kdu se prije dodjele oslobaa memorija na koju pokazuje pokNiz prije
pridruivanja te se alocira novi memorijski prostor za smjetaj niza s desne strane.
Vano je primijetiti da je potpuno ispravno pridodijeliti objekt samome sebi. Zato se
prvo ispituje je li adresa desnog operanda razliita od adrese objekta kojemu se
vrijednost pridruuje. Kada tog uvjeta ne bi bilo, naredbom
a = a;
se interpretira kao
b.operator=(a.operator=(ZnakovniNiz("copycat")));
315
se interpretira kao
x.operator[](y);
Ovim je nainom omoguen pristup pojedinim znakovima znakovnog niza na isti nain
kao i u sluaju obinih znakovnih nizova (char *), na primjer
ZnakovniNiz a("Ivica i Marica");
cout << a[0] << a[6] << a[8];
316
Zbog toga se kao rjeenje namee razviti klasu Matrica kojom bi se taj nedostatak
izbjegao. Takva klasa mora biti indeksirana po dvije dimenzije, jer bi u suprotnom
dohvaanje pojedinih lanova bilo vrlo neitko. Zato emo se morati posluiti raznim
trikovima. Osnovna ideja je da se izraz
obj[m][n]
interpretira kao
(obj[m])[n]
Prvo se operator [] primjeni na objekt obj. Operator vraa privremeni objekt u kojem
je pohranjen podatak o prvoj dimenziji. Na taj se objekt zatim primijeni operator [] koji
sada odreuje drugu dimenziju. Privremeni objekt zna sada i prvu i drugu dimenziju te
moe obaviti indeksiranje matrice. Evo rjeenja problema:
class Matrica {
private:
float *mat;
int redaka, stupaca;
class Pristupnik {
public:
int prvadim, stupaca;
float *mat;
Pristupnik(int pd, int st, float *mt) :
prvadim(pd), stupaca(st), mat(mt) {}
float& operator[](int drugadim) {
return mat[prvadim * stupaca + drugadim];
}
};
public:
Matrica(int red, int stu);
~Matrica() { delete [] mat; }
Pristupnik operator[](int prvadim) {
return Pristupnik(prvadim, stupaca, mat);
}
};
Matrica::Matrica(int red, int stu) : redaka(red),
stupaca(stu), mat(new float[red * stu]) {}
U klasi Matrica lanovi redaka i stupaca pamte broj redaka i stupaca matrice te se
oni moraju navesti u konstruktoru, ime se matrica postavlja na neku poetnu dimenziju.
Pokaziva mat pokazuje na podruje memorije veliine redaka * stupaca, te se u
njega smjetaju lanovi matrice. Podruje je alocirano kao jednodimenzionalan niz zato
jer prilikom koritenja operatora new [] za alokaciju niza sve dimenzije osim prve
317
moraju biti poznate prilikom prevoenja. U naem sluaju su obje dimenzije nepoznate
prilikom prevoenja, te se pozicija elementa unutar podruja izraunava prilikom
pristupa elementu.
Klasa ima preoptereen operator [] koji kao rezultat vraa privremeni objekt klase
Pristupnik. Konstruktorom se objekt inicijalizira tako da se zapamti prva dimenzija,
broj stupaca i pokaziva na poetak matrice. Klasa Pristupnik ima takoer operator
[] koji vraa referencu na element matrice.
Nedostatak ovakve simulacije dvodimenzionalnog indeksiranja jest taj da se svaki
put prilikom pristupa matrici stvara privremeni objekt koji se unitava na kraju izraza,
to moe bitno usporiti rad programa. Ponekad to moe biti jedino rjeenje, pogotovo
ako je potrebno provjeravanje je li indeks unutar dozvoljenog raspona, no na problem
moemo pokuati rijeiti i drukije. Ideja je da operator [] kao svoj rezultat vrati
pokaziva na podruje unutar matrice gdje poinje traeni redak. Na to se primjeni
ugraeni operator [] koji oznaava pomak od pokazivaa te se njime pristupi traenom
elementu matrice. Sada vie nije mogue provjeriti da li je drugi indeks unutar
dozvoljenog podruja, no indeksiranje ide bre. Klasa Pristupnik vie nije potrebna:
class Matrica {
private:
float *mat;
int redaka, stupaca;
public:
Matrica(int red, int stu);
~Matrica() { delete [] mat; }
float *operator[](int prvadim) {
return mat + prvadim * stupaca;
}
};
Ovakvo rjeenje radi bre, no mogue ga je primijeniti samo zato jer smo na prikladan
nain realizirali implementaciju objekta (matricu smo pamtili po recima). Veliki je
nedostatak u tome to operator vraa pokaziva u implementaciju, ime se omoguava
programeru da mijenja unutranjost objekta na nain zavisan od implementacije. To
ugroava integritet objekta jer ako se pokae da je matricu potrebno pamtiti na drukiji
nain, program koji koristi pokazivae unutar objekta pada u vodu. Kao sve stvari u
ivotu, C++ programi su kompromisi izmeu elja i mogunosti.
10.3.3. Operator ()
Operator za poziv funkcije () mora biti definiran kao funkcijski lan klase te moe
imati niti jedan ili proizvoljan broj parametara, a takoer moe pomou ... (tri toke)
imati neodreeni broj parametara. Izraz
x( parametri );
se interpretira kao
318
x.operator()( parametri );
Ovaj operator se uvijek preoptereuje kao unarni i mora biti definiran kao funkcijski
lan. Kao rezultat on mora vratiti ili pokaziva na objekt, referencu na objekt ili sam
objekt neke klase. Izraz
x->m
319
class Y {
public:
Z &referencaNaZ;
int clan;
Y(Z &rz, int mm) : referencaNaZ(rz), clan(mm) {}
Z *operator ->() {
cout << "Pozvan oeprator -> od Y" << endl;
return &referencaNaZ;
}
};
class X {
public:
Y &referencaNaY;
int clan;
X(Y &ry, int mm) : referencaNaY(ry), clan(mm) {}
Y& operator ->() {
cout << "Pozvan operator -> od X" << endl;
return referencaNaY;
}
};
int main() {
Z objZ(1);
Y objY(objZ, 2);
X objX(objY, 3);
cout << "lan: " << objX->clan;
320
return 0;
}
321
class XX {
// klasa XX je sam objekt koji ima svoje lanove
// ovisno o samom problemu koji se rjeava
};
class PokXX {
private:
int uMemoriji;
XX *pokXX;
//
//
//
//
//
postavljen na 1 pokazuje da
je objekt trenutno u memoriji
pokaziva na objekt u memoriji
preostali lanovi ovise o nainu
na koji se objekti smjetaju na disk
public:
XX* operator->();
};
XX* PokXX::operator->() {
if (!uMemoriji) {
// pozovi objekt s vanjske jedinice
uMemoriji = 1;
}
return pokXX;
Dakle, izraz
a->*b
se interpretira kao
a.operator->*(b)
U C++ jeziku postoje dvije verzije operatora ++ i --. Prefiks verzija se pie ispred
operanda, dok se postfiks verzija navodi iza njega. U konanoj varijanti standarda jezika
mogue je posebno preopteretiti obje verzije operatora.
322
Operatorska funkcija moe biti lan klase, a moe biti i definirana izvan klase te tada
uzima jedan parametar. Postfiks operator ++ ima isti potpis te se prilikom prevoenja ne
moe razaznati da li definicija operatorske funkcije pripada prefiks ili postfiks verziji.
Kako bi se ipak omoguilo preoptereenje i jedne i druge varijante operatora, uvedena
je konvencija po kojoj postfiks verzija ima jo jedan dodatni parametar tipa int. Evo
primjera definicija prefiks i postfiks verzija operatora:
class Primjer {
public:
// deklaracije unutar klase
void operator++();
// prefiks verzija
void operator++(int);
// postfiks verzija
};
// deklaracije izvan klase
void operator--(Primjer &obj) {
// prefiks verzija
}
void operator--(Primjer &obj, int) {
// postfiks verzija
}
323
Prvi parametar daje veliinu bloka kojeg je potrebno alocirati i on je obavezan. Njega
izraunava sam prevoditelj prilikom poziva operatora te ga nije mogue navesti
324
prilikom poziva. Preostali parametri se navode u okruglim zagradama prije naziva tipa
kojeg je potrebno alocirati. Prilikom traenja prikladne verzije operatora potuju se
pravila preoptereenja, to znai da se parametri moraju poklapati po tipovima i
pozicijama. Ako operator new nema parametara, zagrade se ne navode. Operator mora
vratiti pokaziva na alociranu memoriju. Isto tako izgleda i deklaracija operatora
new []:
void *operator new[](size_t velicina, int poz);
// primjer poziva
Vektor *pok2 = new (49) Vektor[4];
Iako klasa moe imati preoptereen operator new, ponekad je potrebno pozvati
globalnu verziju operatora new. To se moe uiniti pomou operatora za odreivanje
podruja tako da se navede ::new. U suprotnom, ako se operator :: izostavi, pozvat e
se operator new iz klase. Na primjer:
class Primjer {
public:
void *operator new(size_t vel);
};
int main() {
Primjer *pok1 = new Primjer;// poziva se new iz
// klase Primjer
Primjer *pok2 = ::new Primjer; // poziva se globalna
// verzija
return 0;
}
Reeno vrijedi i za operator new[]: ako elimo pozvati globalni operator, napisat emo
::new[].
325
<stddef.h>
<malloc.h>
<string.h>
<iostream.h>
struct Alokacija {
Alokacija *sljeceda;
void *mjesto;
int linija;
size_t velicina;
char datoteka[80];
};
Alokacija *Prva = NULL, *Zadnja = NULL;
void DodajAlokaciju(int lin, size_t vel, char *dat,
void *mj) {
Alokacija *pom = (Alokacija*)malloc(sizeof(Alokacija));
pom->sljeceda = NULL;
pom->linija = lin;
pom->velicina = vel;
pom->mjesto = mj;
if (dat)
strcpy(pom->datoteka, dat);
else
pom->datoteka[0] = 0;
if (Zadnja)
Zadnja->sljeceda = pom;
else
Prva = pom;
Zadnja = pom;
}
void UkloniAlokaciju(void *mj) {
Alokacija *pom = Prva, *pom1 = NULL;
while (pom) {
if (pom->mjesto == mj) {
if (pom1)
pom1->sljeceda = pom->sljeceda;
else
326
Prva = pom->sljeceda;
if (!pom->sljeceda) Zadnja = pom1;
free(pom);
break;
}
pom1 = pom;
pom = pom->sljeceda;
}
}
void *operator new(size_t vel, int lin, char *dat) {
void *alo = malloc(vel);
DodajAlokaciju(lin, vel, dat, alo);
return alo;
}
void *operator new[](size_t vel, int lin, char *dat) {
void *alo = malloc(vel);
DodajAlokaciju(lin, vel, dat, alo);
return alo;
}
void operator delete(void *mjesto) {
UkloniAlokaciju(mjesto);
}
void operator delete[](void *mjesto) {
UkloniAlokaciju(mjesto);
}
void IspisiListu() {
Alokacija *pom = Prva;
while (pom) {
cout << pom->linija << ":";
cout << pom->datoteka << ":";
cout << pom->velicina << endl;
pom = pom->sljeceda;
}
}
Svaka alokacija memorije se biljei u listi. Prilikom izlaska iz programa lista se moe
ispisati te se moe ustanoviti je li sva memorija ispravno vraena. Operatori new i
new [] imaju dva parametra pomou kojih se moe locirati mjesto u programu na
kojem je alokacija obavljena. Prvi parametar je broj linije kda, a drugi je naziv
datoteke u kojoj je naredba smjetena. Pretprocesorski simbol _ _LINE_ _ se prilikom
prevoenja zamjenjuje brojem linije, dok se simbol _ _FILE_ _ zamjenjuje nazivom
datoteke. Evo primjera poziva preoptereenog operatora new:
int main() {
char *p = new (_ _LINE_ _, _ _FILE_ _) char[20];
327
328
rn = an
rn 1 = rn x + an 1
...
r0 = r1 x + a0
Raunaju se redom brojevi ri po gornjim formulama. Pri tome x predstavlja toku u
kojoj se izraunava vrijednost polinoma, a ai predstavlja i-ti koeficijent polinoma.
329
330
Svaki ozbiljan objektno orijentirani jezik koji danas postoji ima implementiran barem
nekakav rudimentaran mehanizam nasljeivanja, koji znaajno moe skratiti vrijeme
potrebno za razvoj sloenih programa. Nasljeivanje u jeziku C++ je vrlo razraeno, to
omoguava brzo i efikasno stvaranje novih klasa iz ve postojeih.
Osnovna ideja nasljeivanja jest da se prilikom razvoja identificiraju klase koje
imaju slinu funkcionalnost, te da se u izvedenim klasama samo redefiniraju specifina
svojstva, dok se preostala svojstva nasljeuju u nepromijenjenom obliku. Razumijevanje
mehanizama nasljeivanja i graenja hijerarhije klasa je od kljune vanosti za ispravnu
primjenu koncepta objektno orijentiranog programiranja.
331
332
class Linija {
private:
int x1, y1, x2, y2;
public:
GrafObjekt osnovna;
void Crtaj();
void Translatiraj(int vx, int vy);
void Rotiraj(int cx, int cy, int kut);
};
Kada bi se sada htjelo postaviti boju nekoj liniji, to bi se moralo uiniti ovako:
#define CRVENA
14
Linija l;
l.osnovna.PostaviBoju(CRVENA);
Iako je ovakvo rjeenje znatno prikladnije, pisanje kda je izuzetno zamorno, a sam
program je optereen funkcijama koje slue samo pozivanju funkcija iz osnovne klase.
Jezik C++ na elegantan nain rjeava sve te probleme pomou nasljeivanja (engl.
inheritance). Pri tome se definira nova izvedena klasa (engl. derived class) na osnovu
ve postojee osnovne klase (engl. base class). Objekti izvedene klase sadre sve
funkcijske i podatkovne lanove osnovne klase, te mogu dodati nova svojstva.
333
Nasljeivanje se specificira tako da se u deklaraciji klase iza naziva klase navede lista
osnovnih klasa, kao u sljedeem primjeru:
class Linija : public GrafObjekt {
// ...
};
Lista osnovnih klasa navodi se tako da se iza naziva klase stavi dvotoka te se navedu
nazivi osnovnih klasa razdvojeni zarezom. Ispred svakog od naziva mogue je staviti
jednu od kljunih rijei public, private ili protected ime se navodi tip
nasljeivanja. Ovime se jednim potezom specificira da e objekt klase Linija
sadravati sve funkcijske i podatkovne lanove klase GrafObjekt. Slian bi se uinak
postigao da se u klasu Linija ukljuio potpun sadraj osnovne klase. Gornjom
deklaracijom je klasa Linija izvedena te nasljeuje svojstva osnovne klase
GrafObjekt.
Podatkovni i funkcijski lanovi osnovne klase u sluaju javnog nasljeivanja (kada
se koristi kljuna rije public u listi nasljeivanja) zadravaju svoje pravo pristupa. To
znai da podatkovni lan Boja ostaje privatan, te mu se ne moe pristupiti niti unutar
naslijeene klase Linija, dok lanovi PostaviBoju() i CitajBoju() imaju javni
pristup te im se moe pristupiti i izvan klase.
Klasa Linija dodaje nove podatkovne lanove x1, y1, x2 i y2 koji e pamtiti
koordinate poetne i krajnje toke linije. U izvedenoj klasi je mogue zamijeniti eljene
funkcijske lanove osnovne klase novim lanovima. Taj postupak se zove zaobilaenje
(engl. overriding). Tako klasa Linija definira svoj postupak za crtanje koje se
razlikovati od postupka za crtanje navedenog u osnovnoj klasi.
Razmotrimo kako se dalje moe graditi hijerarhijsko stablo grafikih objekata.
Neka klasa ElipsinIsj definira elipsin isjeak. Ta klasa sadri podatkovne lanove
centarX i centarY koji pamte koordinate centra elipse, zatim poluosA i poluosB
koji pamte koordinate velike i male poluosi elipse te pocetniKut i zavrsniKut koji
specificiraju poetni i krajnji kut crtanja isjeka. Kako bi se omoguilo postavljanje
parametara objekta, potrebno je dodati funkcijske lanove za postavljanje i itanje svih
podatkovnih lanova. Na prikladan nain se definiraju i funkcijski lanovi za crtanje,
rotaciju i translaciju.
Krug emo opisati klasom Krug. Krug je grafiki objekt koji je zapravo jedna
podvrsta elipsinog isjeka, te je najbolji nain za implementaciju kruga naslijediti klasu
ElipsinIsj. Pri tome je potrebno dodati funkcijske lanove koji e omoguiti
postavljanje i itanje radijusa. Vano je primijetiti da u ovom sluaju nije potrebno
ponovo definirati funkcijski lan za crtanje. Kako je krug elipsin isjeak koji ima obje
poluosi jednake te poetni kut 0, a zavrni 360 stupnjeva, operacija crtanja elipsinog
isjeka e u ovom sluaju nacrtati krug.
Slina je situacija i s poligonom i pravokutnikom. Klasa Poligon opisuje openiti
objekt koji moe imati proizvoljno mnogo vrhova. Klasa Pravokutnik nasljeuje od
klase Poligon te jedino na prikladan nain postavlja podatkovne lanove objekta.
Operacije za translaciju, rotaciju i crtanje nije potrebno ponavljati jer one definirane u
334
ElipsinIsj
Linija
Krug
Poligon
Pravokutnik
Klasa moe naslijediti i nekoliko drugih klasa. U tom sluaju e svi podatkovni i
funkcijski lanovi svih osnovnih klasa biti ukljueni u novu klasu. Zamislimo da elimo
stvoriti nove grafike objekte koji e sadravati neki tekst. Sve postojee elemente
emo naslijediti kako bi se dodalo novo funkcijsko svojstvo.
Iako je mogue jo jednom proi cijelo hijerarhijsko stablo, te kod svake klase gdje
je to potrebno dodavati elemente potrebne za ispis teksta, jednostavnije je definirati
zasebnu klasu TekstObj koja e definirati tekstovni objekt. Objekti te klase moraju
pamtiti znakovni niz koji se ispisuje te poziciju niza na ekranu. Dodavanje teksta na bilo
koji postojei grafiki element sada se jednostavno moe napraviti nasljeivanjem klase
TekstObj i eljene klase grafikog objekta. Na primjer, klasa TPravokutnik koja
opisuje pravokutnik u kojem je ispisan tekst, nasljeuje klasu TekstObj i klasu
Pravokutnik, dok se klasa TKrug koja opisuje krug s upisanim tekstom dobiva tako
da se naslijedi klasa TekstObj i klasa Krug. Takvo hijerarhijsko stablo prikazano je na
slici 11.2.
GrafObjekt
ElipsinIsj
Linija
Krug
Poligon
Pravokutnik
TKrug
TekstObj
TPravokutnik
335
336
class GrafObjekt {
// osnovna klasa
private:
int Boja;
public:
void PostaviBoju(int nova) { Boja = nova; }
int CitajBoju() { return Boja; }
void Crtaj() {}
void Translatiraj(int, int) {}
void Rotiraj(int, int, int) {}
};
class Linija : public GrafObjekt {
// izvedena klasa
private:
int x1, y1, x2, y2;
public:
void Crtaj();
void Translatiraj(int vx, int vy);
void Rotiraj(int cx, int cy, int kut);
};
Kako bismo smisao nasljeivanja pojasnili u praksi, opisat emo jedan od moguih
naina za realizaciju dvostruko vezane liste objekata. Vezana lista se esto koristi u
sluajevima kada nije poznat ukupan broj objekata koji se ele zapamtiti, ili kad se taj
broj mijenja tijekom izvoenja programa. Naime, ako objekte elimo pamtiti u polju,
potrebno je prilikom alokacije niza navesti broj objekata. Takoer, ako elimo ubaciti
novi objekt u polje na odreeno mjesto, ili ako elimo izbrisati neki objekt iz polja, a da
pri tome ne ostavimo rupu, morat emo premjetati lanove polja uzdu i poprijeko.
Vezana lista (engl. linked list) je u tom sluaju znatno efikasnije rjeenje.
Prije nego to zaponemo objanjavati princip rada liste te damo programski kd
koji ju realizira, evo vane napomene: lista je kontejnerska klasa, a takve klase se u C++
jeziku daleko kvalitetnije rjeavaju predlocima. Tome je vie razloga, a osnovni je taj
to predloci znaju toan tip objekta koji se smjeta u kontejner. Naprotiv, rjeenje koje
je ovdje prikazano ne zna toan tip objekta, pa tako kontejner niti ne moe pristupiti
specifinostima objekta. Ovakav pristup rezultira intenzivnim koritenjem operatora za
dodjelu tipa kako bi se izigrao sistem statikih tipova koji je duboko usaen u C++.
Zbog toga e lista biti ponovo obraena u poglavlju 11. No i ovakva realizacija liste ima
svoje prednosti u sluaju kada u listi elimo uvati objekte razliitih klasa. Tada nam
predloci ne pomau znatno, a koritenje dodjela tipova je neumitno.
337
Vezana lista se ostvaruje tako da svaki lan liste osim vrijednosti sadri i pokaziva
na sljedei element liste. U jaoj varijanti vezanih lista dvostruko povezanoj listi
svaki lan liste sadrava i pokaziva na prethodni lan. U naem primjeru to e biti
pokazivai pokPrethodni i pokSljedeci. Struktura svakog lana liste prikazana je
na slici 11.3a. Kako nema lana ispred prvoga, njegov pokPrethodni bit e jednak
nul-pokazivau, ime se signalizira poetak liste. Ista je stvar i s posljednjim lanom
liste: njegov pokSljedeci e biti jednak nul-pokazivau ime se oznaava kraj liste.
Samoj listi se pristupa preko dva pokazivaa: glava i rep. Pokaziva glava
pokazuje na prvi lan liste, dok pokaziva rep pokazuje na posljednji lan. Shematski je
to prikazano na slici 11.3b. Pojedini lan se dohvaa tako da se lista slijedno pretrauje,
tako da se krene od glave ili od repa, te se uzastopno iitavaju pokazivai na sljedei
odnosno prethodni lan. Umetanje i brisanje lana iz liste je trivijalno: umjesto da se
lanovi premjetaju po memoriji i time troi vrijeme, potrebno je samo promijeniti
pokazivae prethodnog i sljedeeg lana. Ono to listama gubimo jest mogunost
izravnog dohvaanja lana pomou indeksa da bismo dohvatili pojedini lan uvijek je
potrebno krenuti od glave ili od repa i postupno doi do eljenog lana.
Vrijednost
pokSljedeci
a)
pokprethodni
glava
rep
b)
0
0
Zamislimo sada da elimo u listu sa slike 11.3b umetnuti novi lan izmeu prvog i
drugog lana. Pri tome je samo potrebno preusmjeriti pokaziva pokSljedeci prvog
lana i pokaziva pokPrethodni drugog lana na novi lan. Pokazivae
pokPrethodni i pokSljedeci novoga lana potrebno je usmjeriti na prvi odnosno
drugi lan liste, kao na slici 11.4.
Definirat emo klasu Atom koja e opisivati objekt koji se dri u listi. Ta klasa e
sadravati pokaziva na prethodni i sljedei lan u listi, koji e biti privatni kako bi im
se sprijeio pristup izvan klase. Takoer, klasa Atom e sadravati funkcijske lanove za
338
glava
rep
0
0
Evo i klase Lista koja e sadravati funkcijske lanove za umetanje i brisanje lanova.
Pri tome klasa Lista nije odgovorna za alokaciju memorije za pojedini lan liste. To
mora uiniti vanjski program pomou operatora new. Funkcijskim lanovima klase
Lista se tada samo prosljeuje pokaziva na taj lan.
class Lista {
private:
Atom *glava, *rep;
public:
Lista() : glava(NULL), rep(NULL) {}
Atom *AmoGlavu() { return glava; }
Atom *AmoRep() { return rep; }
void UgurajClan(Atom *pok, Atom *izaKojeg);
void GoniClan(Atom *pok);
};
void Lista::UgurajClan(Atom *pok, Atom *izaKojeg) {
// da li se dodaje na poetak?
if (izaKojeg != NULL) {
// ne dodaje se na poetak
339
// da li se dodaje na kraj?
if (izaKojeg->Sljedeci() != NULL)
// ne dodaje se na kraj
izaKojeg->Sljedeci()->StaviPrethodni(pok);
else
// dodaje se na kraj
rep = pok;
pok->StaviSljedeci(izaKojeg->Sljedeci());
izaKojeg->StaviSljedeci(pok);
}
else {
// dodaje se na poetak
pok->StaviSljedeci(glava);
if (glava != NULL)
// da li ve ima lanova u listi?
glava->StaviPrethodni(pok);
glava = pok;
}
pok->StaviPrethodni(izaKojeg);
}
void Lista::GoniClan(Atom *pok) {
if (pok->Sljedeci() != NULL)
pok->Sljedeci()->StaviPrethodni(pok->Prethodni());
else
rep = pok->Prethodni();
if (pok->Prethodni() != NULL)
pok->Prethodni()->StaviSljedeci(pok->Sljedeci());
else
glava = pok->Sljedeci();
}
340
Kao posljedica nasljeivanja, u svakom LVektor objektu postoji podobjekt Atom koji
sadrava sve potrebno da bi se objekt mogao pohraniti u listu. Prisutni su svi podatkovni
i funkcijski lanovi. Javnim lanovima je mogue pristupiti i na klasian nain:
LVektor lv;
lv.StaviSljedeci(NULL);
if (lv.Prethodni()) {
// ...
}
Sada uope nije potrebno ponavljati funkcijske lanove za postavljanje i itanje realnog
i imaginarnog dijela; oni su jednostavno naslijeeni iz klase Vektor. Grafiki se objekt
klase LVektor moe prikazati kao na slici 11.5.
LVektor
Vektor
Atom
341
// ...
// itanje liste
LVektor *pok = (LVektor *)lst.AmoGlavu();
while (pok) {
// obrada vektora, na primjer ispis:
cout << "(" << pok->DajX() << ","
<< pok->DajY() << ")" << endl;
pok = (LVektor *)pok->Sljedeci();
}
342
#include <iostream.h>
class Osnovna {
public:
int i;
void Var() { cout << "Osnovna::i " << i << endl; }
};
class Izvedena : public Osnovna {
public:
int i;
// prekriva Osnovna::i !!!
void Int() { cout << "Izvedena::i " << i << endl; }
};
se Izvedena::i
se Osnovna::i
'Osnovna::i 20'
'Izvedena::i 9'
Situacija je neto sloenija ako u osnovnim klasama postoje dva ili vie ista
identifikatora.
Ako dvije osnovne klase sadre isti identifikator, njegovo navoenje bez
specificiranja pripadnog podruja je neprecizno te rezultira pogrekom
prilikom prevoenja.
Upotreba operatora za odreivanje podruja je obavezna kako bi se jednoznano
odredio lan kojem se pristupa. Na primjer, za deklaracije
class A {
public:
void Opis() { cout << "Ovo je klasa A" << endl; }
};
class B {
public:
void Opis() { cout << "Ovo je klasa B" << endl; }
};
class D : public A, public B { };
343
Objekt D u biti posjeduje dva funkcijska lana Opis, jedan u A dijelu i jedan u B dijelu.
Prilikom poziva neophodno je eksplicitno odrediti kojemu se pristupa:
D obj;
obj.A::Opis();
obj.B::Opis();
344
prava pristupa pojedinim lanovima klase definira se javno suelje pomou kojeg
objekt komunicira s okolinom.
Uvoenjem nasljeivanja, osim javnog suelja namijenjenog korisnicima klase,
potrebno je uvesti i suelje vidljivo programerima koji e izvoditi nove klase iz
postojee. Time je mogue sakriti osjetljive informacije u izvedenoj klasi to osigurava
konzistentnost objekta.
Pravo pristupa lanu u izvedenoj klasi odreeno je pravom pristupa u osnovnoj
klasi te vrstom nasljeivanja. Kako postoje tri prava pristupa te tri mogua tipa
nasljeivanja, imamo ukupno devet moguih kombinacija.
11.4.1. Zatieno pravo pristupa
U gornjem primjeru klasa GrafObjekt definira lan Boja zatienim. Njemu se i dalje
ne moe pristupiti izvan klase, na primjer, pomou objekta te klase, no mogue ga je
koristiti u funkcijskim lanovima izvedenih klasa. Na primjer:
class Linija : public GrafObjekt {
private:
int x1, y1, x2, y2;
public:
void Crtaj();
void Translatiraj(int vx, int vy);
void Rotiraj(int cx, int cy, int kut);
};
void Linija::Crtaj() {
// crni objekti se ne crtaju
if (Boja == CRNA) return;
// ...
}
345
Klasa Linija moe potpuno regularno pristupati lanu Boja. To nipoto ne treba
shvatiti da je mogue tom lanu pristupiti pomou objekta klase Linija:
Linija ln;
ln.Boja = CRNA;
// pogreka
lan Boja je jednostavno dio suelja za nasljeivanje: time se odreuje koji lanovi su
namijenjeni koritenju u izvedenim klasama. Privatni lanovi nisu dostupni u izvedenim
klasama, a javni lanovi su dostupni i korisnicima objekta.
Zatieni lanovi nisu javno dostupni, ali su dostupni iz naslijeenih klasa i
ine suelje za nasljeivanje.
Na kraju je jo potrebno napomenuti da pravo pristupa lanovima osnovne klase u
izvedenoj klasi ovisi o tipu nasljeivanja, o emu e biti vie rijei u narednim
odjeljcima.
11.4.2. Javne osnovne klase
Odreena klasa je javna osnovna klasa (engl. public base class) ako je u listi prilikom
nasljeivanja navedena pomou kljune rijei public. Svi elementi osnovne klase bit
e ukljueni u izvedenu klasu u kojoj e zadrati svoje originalno pravo pristupa.
Privatni lanovi nee biti dostupni iz izvedenih klasa niti iz preostalog programa.
Zatienim lanovima moi e se pristupiti iz izvedene klase, ali ne i iz glavnog
programa. Javni lanovi ostat e dostupni i iz glavnog programa i iz izvedene klase. To
se moe pokazati sljedeim primjerom:
class GrafObjekt {
private:
int Brojac;
protected:
int Boja;
public:
void PostaviBoju(int nova) { Boja = nova; }
int CitajBoju() { return Boja; }
void Crtaj() {}
void Translatiraj(int, int) {}
void Rotiraj(int, int, int) {}
void PostaviBrojac(int br) { Brojac = br; }
};
class Linija : public GrafObjekt {
private:
int x1, y1, x2, y2;
public:
void Crtaj();
void Translatiraj(int vx, int vy);
346
Klasi GrafObjekt je dodan cjelobrojni lan Brojac koji pamti redni broj objekta i
inicijalizira se pomou javnog funkcijskog lana PostaviBrojac(). Kako je lan
privatan, to znai da mu se ne moe pristupiti iz klase Linija. Svaki objekt klase e
sadravati taj lan, no nee mu se moi pristupiti, na primjer:
void Linija::Crtaj() {
if (Brojac) { // pogreka: lan nije dostupan
// ...
}
}
35
22
void Linija::Crtaj() {
if (Boja == PLAVA) { // OK: Boja je zatieni lan
// ...
}
}
int main() {
Linija l;
l.Boja = BIJELA;// pogreka: lan Boja nije
// dostupan izvan klase
return 0;
}
347
Klasa je privatna osnovna klasa (engl. private base class) ako se u listi nasljeivanja
navede s kljunom rijei private. Takoer, ako se izostavi kljuna rije koja odreuje
tip nasljeivanja, podrazumijeva se privatno nasljeivanje.
Prilikom privatnog nasljeivanja, privatni lanovi osnovne klase nisu dostupni
izvedenoj klasi, dok javni i zatieni lanovi osnovne klase postaju privatni lanovi
izvedene klase. Zamislimo da elimo na to jednostavniji nain od klase Vektor
nainiti klasu Kompleksni. Iako bi sa stanovita principa ispravnog objektno
orijentiranog dizajna bilo ispravnije napisati klasu Kompleksni neovisno od klase
Vektor, na prvi pogled se ini da vektori i kompleksni brojevi imaju mnogo
zajednikih svojstava: i jedni i drugi imaju dva realna lana koji pamte x i y vrijednosti,
odnosno realni i imaginarni dio. Na jednak se nain zbrajaju i oduzimaju, pa ak i
mnoe sa skalarom. Razlika je primjerice u tome to ne postoji skalarni produkt
kompleksnih brojeva; produkt kompleksnih brojeva se rauna po drugim pravilima.
Zato se moe initi prikladnim da se klasa Kompleksni jednostavno dobije
nasljeivanjem klase Vektor. Pretpostavimo za poetak da koristimo javno
nasljeivanje:
class Vektor {
private:
float ax, ay;
public:
Vektor(float a = 0, float b = 0) : ax(a), ay(b) {}
void PostaviXY(float x, float y) { ax = x; ay = y; }
float DajX() { return ax; }
float DajY() { return ay; }
float MnoziSkalarnoSa(Vektor &vekt);
};
348
Sada nije vie mogue pozvati primjerice lan MnoziVektorskiSa() na objektu klase
Kompleksni.
Privatno nasljeivanje se koristi u sluajevima kada izvedena klasa nije
podvrsta osnovne klase nego osnovna i izvedena klasa samo dijele
implementacijske detalje.
Nije mogua implicitna konverzija iz izvedene klase u privatnu osnovnu klasu. Ako bi
to bilo dozvoljeno, tada bi sljedei programski odsjeak omoguio pristup klasi na
neprikladan nain preko javnog suelja osnovne klase:
Kompleksni *pokKompl = new Kompleksni(4.0, 6.0);
Vektor *pokVekt = pokKompl;
349
350
// klasu
return 0;
}
Tip
nasljeivanja
protected
private
public
public
protected
private
protected
protected
protected
private
private
private
private
private
351
pogled moe se pomisliti da time nasljeivanje definira odnos slian prijateljstvu klasa,
no to nije tako. Vrlo je vano uoiti bitnu razliku izmeu nasljeivanja i prijateljstva.
Prijateljska funkcija ili klasa ima potpuna prava pristupa svim lanovima objekata
neke klase. Uz modifikacije u deklaraciji klase GrafObjekt, funkcija
AnimirajBoju() moe pristupati svim lanovima objekta:
class GrafObjekt {
friend void AnimirajBoju(); // prijateljska funkcija
private:
int Brojac;
protected:
int Boja;
public:
void PostaviBoju(int nova) { Boja = nova; }
int CitajBoju() { return Boja; }
void Crtaj() {}
void Translatiraj(int, int) {}
void Rotiraj(int, int, int) {}
void PostaviBrojac(int br) { Brojac = br; }
};
void AnimirajBoju() {
GrafObjekt obj;
obj.Brojac++;
// dozvoljeno
obj.Boja = (obj.Boja + 1) % 16; // takoer dozvoljeno
}
352
Ako bismo htjeli da funkcija ima pristup lanu x1, potrebno je funkciju uiniti
prijateljem klase Linija.
Funkcijski lanovi izvedenih klasa imaju mogunost pristupa javim i zatienim
naslijeenim lanovima osnovnih klasa, no nemaju nikakvo posebno pravo pristupa
lanovima objekata osnovnih klasa. Na primjer, funkcijski lan Crtaj() klase Linija
moe pristupiti naslijeenom lanu Boja, no ne moe mu pristupiti u nekom drugom
objektu:
void Linija::Crtaj() {
GrafObjekt go;
Linija ln;
Boja = CRVENA; //
go.Boja = CRNA; //
//
ln.Boja = CRNA; //
//
}
Funkcije prijatelji klasa imaju ista prava pristupa kao da su lanovi te klase. To znai da
u sluaju da je prijateljstvo funkcije AnimirajBoju() pomaknuto u klasu Linija,
sljedei pristupi bi bili mogui:
class GrafObjekt {
private:
int Brojac;
protected:
int Boja;
public:
// funkcijski lanovi su nepromijenjeni
};
class Linija : public GrafObjekt {
friend void AnimirajBoju();
private:
int x1, y1, x2, y2;
public:
// funkcijski lanovi su nepromijenjeni
};
void AnimirajBoju() {
Linija ln;
GrafObjekt go;
ln.Boja = BIJELA;
go.Boja = BIJELA;
ln.Brojac = CRNA;
}
// OK
// pogreka
// pogreka
353
Pristup ln.Boja je ispravan jer svaki funkcijski lan klase Linija (a time i svaki
prijatelj klase) ima pristup naslijeenim lanovima osnovne klase. Pristup go.Boja nije
ispravan jer funkcijski lan klase Linija (a time i svaki prijatelj klase) nema posebna
prava pristupa lanovima objekata osnovnih klasa. Pristup ln.Brojac nije ispravan jer
je Brojac lan osnovne klase s privatnim pristupom.
11.4.6. Iskljuivanje lanova
Prilikom privatnog ili zatienog nasljeivanja ponekad moe biti prikladno ostaviti
originalno pravo pristupa pojedinom lanu osnovne klase. Na primjer, prilikom
privatnog izvoenja klase Kompleksni iz klase Vektor, javni lanovi DajX() i
DajY() automatski postaju skriveni. No kompleksni brojevi imaju svoj realni i
imaginarni dio kojima bi se takoer moglo pristupiti pomou tih lanova. Iako je
mogue u klasi Kompleksni ponoviti te lanove tako da se oni jednostavno pozovu
lanove osnovne klase, to samo dodatno unosi probleme u stablo nasljeivanja. Zbog
toga je mogue iskljuiti pojedini lan iz privatnog nasljeivanja te mu ostaviti njegovo
osnovno pravo pristupa:
class Vektor {
private:
float ax, ay;
public:
Vektor(float a = 0, float b = 0) : ax(a), ay(b) {}
void PostaviXY(float x, float y) { ax = x; ay = y; }
float DajX() { return ax; }
float DajY() { return ay; }
float MnoziSkalarnoSa(Vektor &vekt);
};
class Kompleksni : public Vektor {
public:
Kompleksni(float a = 0, float b = 0) : Vektor(a, b) {}
void MnoziSa(Kompleksni &broj);
Vektor::DajX;
// iskljuuje DajX iz mehanizma
// privatnog nasljeivanja
Vektor::DajY;
// iskljuuje DajY iz mehanizma
// privatnog nasljeivanja
};
354
355
Relacije tipa sadri se opisuju pripadnou: kako se poligon sastoji od etiri linije,
ispravna implementacija klase Pravokutnik je ona koja nasljeuje klasu GrafObjekt
i sadri etiri objekta klase Linija:
class Pravokutnik : public GrafObjekt {
private:
Linija L1, L2, L3, L4;
public:
// funkcijski lanovi
};
356
};
357
public:
Druga(Prva &);
};
class Treca : public Druga, public Prva {
public:
Treca(int a) : Prva(a), Druga(*this) {}
};
Zadatak. Dodajte klasi Stog iz zadatka na stranici 354 konstruktor koji e kao
parametar imati cijeli broj koji e definirati maksimalan broj elemenata u stogu.
358
int redBroj;
// funkcijski lanovi nisu bitni
};
pokSljedeci
pokPrethodni
ax
ay
redBroj
359
360
361
pretrauje se postoji li takav naslijeeni lan u bilo kojoj osnovnoj klasi. Ako takav lan
postoji u vie osnovnih klasa, onda nije jednoznano jasno kojem se lanu pristupa te
prevoditelj javlja pogreku. Zatim se po istom principu pretrauje podruje osnovne
klase sve dok se ne pronae traeni identifikator. Ako se identifikator ne pronae, javlja
se pogreka. Tako je u gornjem primjeru lan pokSljedeci u funkcijskom lanu
DajSljedeci() identificiran kao naslijeeni lan Atom::pokSljedeci.
11.8.1. Razlika nasljeivanja i preoptereenja
362
Ovakav pristup ima vie nedostataka. Kao prvo, naziv Elem se pojavljuje u globalnom
podruju imena iako se ne moe koristiti iz tog podruja. Time se onemoguava
njegova upotreba za klasu koja e imati smisla u globalnom podruju. Nadalje, ako se
kasnije eli proiriti skup novim operacijama, to se moe izvesti tako da se naslijedi
klasa SkupNizova. Problem je to je novu klasu potrebno navesti prijateljem klase
Elem kako bi dotina klasa imala pristup konstruktoru te mogla stvarati objekte klase.
Konano, mogue bi bilo naslijediti klasu Elem kako bi se primjerice osigurao rad
dvostruke povezane liste. U tom sluaju, naslijeena klasa bi morala ponoviti sve klase
kao prijatelje da bi im omoguila stvaranje objekata klase. Takvo pisanje sloenih
programa je vrlo nepraktino.
Ti problemi se mogu rijeiti tako da se klasa Elem ugnijezdi u podruje klase
SkupNizova. Ako se postavi u privatni ili zatieni dio, glavni program nee imati
pristup identifikatoru te e time automatski biti onemogueno stvaranje objekata klase.
Nadalje, naziv Elem bit e automatski uklonjen iz globalnog podruja.
Ako se klasa Elem ugnijezdi u zatieni dio klase SkupNizova, sve klase koje
nasljeuju klasu SkupNizova automatski e moi koristiti klasu Elem. Kako nema vie
opasnosti od pristupa izvan klase SkupNizova, klasa Elem sada moe imati i javni
konstruktor, te vie nema potrebe za davanjem posebnih prava pristupa klasama koje
koriste klasu Elem. Opisane deklaracije klasa sada bi izgledale ovako:
363
class SkupNizova {
protected:
class Elem {
Elem(char *niz);
// ...
};
// ...
};
Pristup ugnijeenoj klasi iz izvedene klase slijedi prava pristupa za obine lanove:
mogunost pristupa ovisi o pravu pristupa u osnovnoj klasi i o tipu nasljeivanja, a
pravila koja to reguliraju opisana su u prethodnim odjeljcima.
lanovi ugnijeene klase se mogu definirati i izvan okolne klase. Tada se podruja
jednostavno nadovezuju operatorom :: po sljedeem principu:
SkupNizova::Elem::Elem(char *niz) {
// ...
}
Takoer, ugnijeena klasa moe posluiti kao objekt nasljeivanja, s time da u tom
sluaju ona mora biti javno dostupna:
class SkupNizova {
public:
class Elem {
Elem(char *niz);
};
};
class NaslijediOd : public SkupNizova::Elem {
// ...
};
364
listi parametara navodi neku drugu klasu. Jezik C++ definira niz pravila kojima se tada
navedeni tip moe pokuati svesti na tip naveden u parametrima preoptereene funkcije
i tako odrediti poziv. Ta pravila ukljuuju tono podudaranje tipova, primjenu
standardne konverzije i primjenu korisniki definiranih konverzija.
11.9.1. Tono podudaranje tipova
U gornjem primjeru se poziv funkcije preusmjerava na funkciju koja ima toan tip kao
parametar. Prilikom preoptereenja funkcija vano je imati na umu da algoritam za
odreivanje argumenata ne moe razlikovati objekt i referencu na taj objekt, te e takva
preoptereenja prouzroiti pogreku prilikom preoptereenja:
// pogreka: nije mogue razlikovati objekt i referencu na
// objekt
void Rastegni(Linija &obj);
void Rastegni(Linija obj);
Ako ne postoji tono podudaranje tipova, prevoditelj e pokuati svesti tip stvarnog
argumenta na tip naveden u parametrima funkcije pomou standardne konverzije.
Objekt izvedene klase, referenca ili pokaziva e se implicitno pretvoriti u
odgovarajui tip javne osnovne klase. Na primjer:
void Deformiraj(GrafObjekt &obj);
Linija lin;
// OK: lin se konvertira u GrafObjekt&
Deformiraj(lin);
365
Ako postoje dvije funkcije od kojih u parametrima svaka ima po jednu neposrednu
javnu osnovnu klasu, poziv funkcije se smatra automatski nejasnim te se javlja pogreka
prilikom prevoenja. Programer tada mora pomou eksplicitne dodjele tipa odrediti
koja se varijanta funkcije poziva:
void Obradi(Atom &obj);
void Obradi(Vektor &obj);
LVektor lv;
//pogreka: postoje funkcije s obje javne osnovne klase
Obradi(lv);
// programer odreuje poziv funkcije Obradi(Vektor&) pomou
// eksplicitne dodjele tipa
Obradi((Vektor&)lv);
Ako postoji vie osnovnih klasa, pri emu je samo jedna blia klasi navedenog objekta,
provodi se pretvorba u taj tip te se poziva odgovarajua funkcija:
class A { };
class B : public A { };
class C : public B { };
void funkcija(A &obj);
void funkcija(B &obj);
int main() {
C objc;
// OK: poziva se funkcija(B &) jer je klasa B blia
// klasi C nego klasa A
funkcija(objc);
return 0;
}
366
Tip void * je najudaljeniji od bilo koje klase, odnosno neki pokaziva e biti pretvoren
u taj tip samo ako niti jedna druga konverzija nije mogua.
11.9.3. Korisniki definirane pretvorbe
Ako bismo pozvali primjerice funkciju Korijen1() tako da joj kao parametar
navedemo realan broj, prvo bi se pozvao konstruktor klase Kompleksni koji bi
konvertirao realan broj u kompleksni tako to bi stvorio privremeni objekt. Taj objekt bi
se zatim proslijedio pozvanoj funkciji. Na primjer:
Kompleksni kor = Korijen1(3.9);
Ovako korisnik klase tretira realne i kompleksne brojeve na isti nain. No i cijeli brojevi
su kompleksni. Na svu sreu, nije potrebno posebno navoditi konstruktor koji e
konvertirati cijele brojeve u kompleksne zato jer postoji ugraena konverzija izmeu
cijelih i realnih brojeva. Poziv
367
Ako postoji funkcija koja kao parametar oekuje objekt klase GrafObjekt, mogue joj
je kao parametar navesti objekt klase Vektor:
void NestoRadi(GrafObjekt &go) {
// ...
}
Vektor v;
368
NestoRadi((Linija)v);
// rezultira konverzijom objekta klase Vektor u objekt
// klase Linija koja se ugraenom konverzijom moe
// svesti na klasu GrafObjekt
369
class Osnovna {
public:
// ...
int operator==(int i);
};
class Izvedena : public Osnovna {
public:
// ...
int operator==(char *niz);
};
Izvedena obj;
if (obj == "ab") cout << "Jednako!" << endl;
if (obj == 1) cout << "Podjednako!" << endl;
// OK
// pogreka
Prva usporedba se moe prevesti jer u klasi Izvedena postoji operator koji moe
usporediti objekt sa znakovnim nizom. Druga usporedba, naprotiv, nije ispravna jer je
operator usporedbe definiran u osnovnoj klasi, te ga operator u izvedenoj klasi skriva
nasljeivanje nije isto to i preoptereenje. Ako elimo u klasi Izvedena ostaviti
mogunost usporedbe sa cijelim brojevima te dodati jo usporedbu sa znakovnim
nizovima, potrebno je ponoviti definiciju operatora operator==(int) i u izvedenoj
klasi:
class Izvedena : public Osnovna {
// ...
public:
int operator==(char *niz);
int operator==(int i) {
return Osnovna::operator==(i);
}
};
370
Osnovna klasa definira operator kojim je mogue nekom objektu pridruiti cijeli broj.
Izvedena klasa ne definira operator pridruivanja, no definira konstruktor. Kako se
operator pridruivanja ne nasljeuje, klasa Izvedena nee imati operator pridruivanja
te e se primjenjivati podrazumijevani operator koji e provesti kopiranje objekata
jedan u drugi, bit po bit. No da bi se to moglo provesti, objekti moraju biti istog tipa.
Prevoditelj e zbog toga pokuati konvertirati cijeli broj 2 u objekt klase Izvedena te
e pronai konstruktor s jednim parametrom koji e obaviti konverziju. Takav
konstruktor ne postoji te e se prijaviti pogreka.
Ako bi kojim sluajem postojao konstruktor s jednim cjelobrojnim
parametrom, operacija dodjeljivanja bi se preutno i bez upozorenja provela
kao kopiranje bit po bit privremenog objekta nastalog kao rezultat
konverzije.
U sluaju koritenja dinamike dodjele memorije unutar klase Izvedena ovakvo
pridruivanje e vrlo vjerojatno rezultirati pogrekom prilikom izvoenja programa. Da
bi se takve situacije izbjegle, potrebno je biti vrlo oprezan prilikom nasljeivanja klasa s
definiranim operatorom pridruivanja.
Takoer, zbog toga to se konstruktori ne nasljeuju, konverzije konstruktorom
takoer nee biti naslijeene. Ako je neka konverzija izriito potrebna, neophodno je u
izvedenoj klasi ponoviti konstruktor koji e ju obaviti. Na primjer, klasa Kompleksni
sadrava konstruktor kojim se realni brojevi mogu konvertirati u kompleksne. Ako
bismo napravili klasu LKompleksni koja bi nasljeivala i klasu Kompleksni i klasu
Atom te bi opisivala kompleksni broj u vezanoj listi, konstruktor konverzije bi trebalo
ponoviti:
class LKompleksni : public Kompleksni, public Atom {
public:
LKompleksni(double a = 0, double b = 0) :
Kompleksni(a, b) {}
// ...
};
371
372
Operacija crtanja primijenjena na taj objekt bi uvijek trebala rezultirati crtanjem linije.
No klasa GrafObjekt sadri funkcijski lan Crtaj() koji nita ne radi, zato jer ona
definira samo opa svojstva svih grafikih objekata. Izneseni problem se moe ilustrirati
sljedeim programskim odsjekom:
class GrafObjekt {
public:
// ...
void Crtaj() {}
};
class Linija : public GrafObjekt {
public:
// ...
void Crtaj();
// ovaj funkcijski lan e stvarno
// nacrtati liniju
};
Linija *pokLinija = new Linija;
GrafObjekt *pokGO = pokLinija;
// poziva Linija::Crtaj()
// poziva GrafObjekt::Crtaj()
e pozvati funkcijski lan Crtaj() iz klase GrafObjekt, zato jer pokaziva pokGO
pokazuje na objekt klase GrafObjekt. Ovime operacija crtanja, umjesto da je vezana
za objekt, postaje ovisna o nainu poziva. Takvo ponaanje znatno naruava objektno
orijentirani pristup programiranju.
Na prvi pogled se gornje ponaanje moe initi loginim: korisnik mora naznaiti
kako eli gledati na objekt. U prvom sluaju korisnik gleda na objekt kao na liniju, pa se
stoga i poziva operacija iz klase Linija. U drugom sluaju korisnik eli dotini objekt
promatrati kao podobjekt poetnog objekta pa se zbog toga poziva pripadna funkcija
crtanja. Ovakvo ponaanje je u suprotnosti s konceptima objektno orijentiranog
programiranja. Svaki objekt ima operacije koje se uvijek izvode na isti nain, to
odgovara stvarnom ponaanju objekata u prirodi.
Na primjer, mogli bismo sve ljude klasificirati u hijerarhiju klasa koja poinje s
podjelom ljudi po boji koe. Svaki (razuman) ovjek e odgovoriti na pitanje Kako se
373
Svaki objekt tono zna kako treba obaviti pojedinu radnju te nain njenog izvoenja
sada ne ovisi o nainu gledanja na objekt. No ovaj primjer, kako je napisan, sam za sebe
ne radi. Kako je svaki element polja nizObj pokaziva na GrafObjekt, operacija
crtanja pozivat e GrafObjekt::Crtaj(). Ovakvo odreivanje funkcijskog lana
pomou tipova dostupnih prilikom prevoenja zove se statiko povezivanje (engl. static
binding).
Promotrimo kako bi se izneseni problem mogao rijeiti. Osnovna potekoa je u
tome to prevoditelj prilikom prevoenja ima dostupnu samo informaciju o tipu
pokazivaa, odnosno o nainu gledanja. Taj pokaziva moe pokazivati na razne
objekte, te bismo htjeli u pojedinim situacijama pozivati odgovarajue funkcijske
lanove razliitih klasa. Oito se prilikom prevoenja ne moe odrediti koja se funkcija
poziva, nego se odluka o samom pozivu mora odgoditi za trenutak izvoenja programa.
Zajedno s objektom, u memoriju mora biti pohranjen podatak o stvarnom tipu objekta.
Prilikom prevoenja poziva funkcije prevoditelj mora stvoriti kd koji e proitati tu
informaciju te na osnovu nje odrediti koja se funkcija u poziva. Takvo odreivanje
pozvanih lanova naziva se u objektno orijentiranim jezicima kasno povezivanje (engl.
late binding), a u C++ terminologiji uvrijeen je naziv dinamiko povezivanje (engl.
dynamic binding).
Slino ponaanje moe se simulirati dodavanjem klasi GrafObjekt cjelobrojnog
lana koji e oznaavati tip objekta. Prilikom poziva funkcijskog lana potrebno je,
ovisno o vrijednosti tog lana, pretvoriti pokaziva u pokaziva na neki drugi tip:
enum TipObj {GRAFOBJEKT, LINIJA, POLIGON, PRAVOKUTNIK,
ELIPSINISJ, KRUG};
374
class GrafObjekt {
// ...
public:
TipObj tip;
void Crtaj() {}
};
// deklaracije ostalih klasa se ne mijenjaju
int const MAX_ELT = 10;
GrafObjekt *nizObj[MAX_ELT];
// niz nizObj se popunjava po istom principu
// crtanje sada izgleda ovako:
for (int i = 0; i < MAX_ELT; i++)
switch (nizObj[i]->tip) {
case GRAFOBJEKT:
nizObj[i]->Crtaj();
break;
case LINIJA:
((Linija*)(nizObj[i]))->Crtaj();
break;
case POLIGON:
((Poligon*)(nizObj[i]))->Crtaj();
break;
// po istom principu se navode svi ostali tipovi
}
C++ nudi vrlo elegantno i uinkovito rjeenje gore navedenog problema u obliku
virtualnih funkcijskih lanova. Osnovna ideja je u tome da se funkcijski lanovi za koje
elimo dinamiko povezivanje oznae prilikom deklaracije klase. To se ini
navoenjem kljune rijei virtual, a takvi lanovi se nazivaju virtualnim funkcijskim
lanovima (engl. virtual function members). Prevoditelj automatski odrava tablicu
virtualnih lanova koja se pohranjuje u memoriju zajedno sa samim objektom. Prilikom
poziva lana, prevoditelj e potraiti adresu lana u tablici koja je privezana uz objekt
te na taj nain pozvati lan.
375
Klasu GrafObjekt emo modificirati tako da funkcijske lanove koji moraju biti
povezani uz sam objekt oznaimo virtualnima:
class GrafObjekt {
private:
int Boja;
public:
void PostaviBoju(int nova) { Boja = nova; }
int CitajBoju() { return Boja; }
virtual void Crtaj() {}
virtual void Translatiraj(int, int) {}
virtual void Rotiraj(int, int, int) {}
};
class Linija : public GrafObjekt {
private:
int x1, y1, x2, y2;
public:
Linija(int lx1, int ly1, int lx2, int ly2) :
x1(lx1), y1(ly1), x2(lx2), y2(ly2) {}
virtual void Crtaj();
virtual void Translatiraj(int vx, int vy);
virtual void Rotiraj(int cx, int cy, int kut);
};
376
vptr
Boja
x1
y1
x2
y2
vptr
Boja
x1
y1
x2
y2
vtable
Crtaj()
Translatiraj()
Rotiraj()
377
lanovi nisu vezani za objekt pa niti ne mogu biti virtualni. Da bi se izbjegle zabune i
ruan termin nevirtualni, takve lanove emo zvati lanovi sa statikim pozivom.
lanovi sa statikim pozivom osnovne klase mogu biti redeklarirani kao virtualni u
izvedenoj klasi. Odreivanje da li se lan poziva virtualno ili statiki u tom se sluaju
odnosi na klasu iz koje se poziva lan, na primjer:
#include <iostream.h>
class A {
public:
void func() { cout << "A::func()" << endl; }
};
class B : public A {
public:
virtual void func() { cout << "B::func()" << endl; }
};
class C : public B {
public:
virtual void func() { cout << "C::func()" << endl; }
};
int main() {
C objc;
A *pokA = &objc;
B *pokB = &objc;
pokA->func();
pokB->func();
return 0;
}
// ispisuje A::func()
// ispisuje C::func()
Prvi poziv rezultira pozivom funkcije A::func() zato jer se funkcija poziva preko
pokazivaa na klasu A. U toj klasi je func() definiran bez kljune rijei virtual pa se
on poziva statiki, a to rezultira pozivom iz klase A. U drugom sluaju lan se poziva
preko pokazivaa na klasu B. U toj klasi lan je redeklariran kao virtualni, pa se on
378
Virtualni funkcijski lanovi mogu se pozvati pomou objekta klase, preko pokazivaa
ili reference na neki objekt te pozivom unutar klase. C++ jezik definira skup pravila
pomou kojih se moe odrediti kako e se pozvati neki funkcijski lan.
Modificirat emo hijerarhiju grafikih objekata tako to emo dodati mogunost
brisanja objekta s ekrana te mogunost premjetanja objekta s jednog mjesta na drugo.
Kako ne postoje funkcije za crtanje koje bi se na isti nain izvodile na svim raunalnim
platformama, radi openitosti kda stavit emo da pojedini funkcijski lan umjesto
stvarnog crtanja na ekran ispisuje poruku o tome koja je funkcija pozvana. Valja uoiti
da se postupak premjetanja objekta sastoji iz tri osnovne operacije: brisanja objekta s
ekrana, promjene koordinata te njegovog ponovnog crtanja. Svaki objekt mora definirati
kako se za njega obavlja pojedini od tih koraka, odnosno kako e se objekt nacrtati,
kako e se izbrisati te kako e mu se promijeniti koordinate. Operaciju crtanja obavit e
funkcijski lan Crtaj(), operaciju brisanja lan Brisi(), a operaciju pomaka lan
Translatiraj(). Funkcijski lan koji e objedinjavati te tri operacije nazvat emo
Pomakni(), a kao parametre imat e dva broja koji e definirati vektor za koji se
pomak obavlja. Taj lan e se definirati samo jednom te e pomou mehanizma
virtualnih funkcija pozivati ispravnu funkciju za brisanje, pomak i crtanje.
class GrafObjekt {
private:
int Boja;
public:
void PostaviBoju(int nova) { Boja = nova; }
int CitajBoju() { return Boja; }
virtual void Crtaj() {}
virtual void Brisi() {}
virtual void Translatiraj(int, int) {}
void Pomakni(int px, int py);
};
void GrafObjekt::Pomakni(int px, int py) {
Brisi();
Translatiraj(px, py);
Crtaj();
}
// virtualan poziv
// ponovo virtualan poziv
379
Postoje tri sluaja kada se poziv lana obavlja statiki iako je lan deklariran kao
virtualan. Prvi takav sluaj je kada se funkcijski lan poziva preko objekta neke klase.
To je i logino, jer ako se prilikom prevoenja tono zna klasa objekta za koji se lan
poziva, mogue je odmah tono odrediti i odgovarajui funkcijski lan. To ne vrijedi
za dereferencirane pokazivae; za njih se pozivi takoer provode virtualno. Na primjer:
duzina.Crtaj();
(*go1).Crtaj();
// statiki poziv
// virtualan poziv
Prvi poziv moe biti statiki jer se tono zna o kojoj se klasi radi; identifikator duzina
jednoznano oznaava objekt klase Linija. Drugi poziv je virtualan. Vidi se da je
virtualan mehanizam u ovom sluaju potreban, jer *go1 oznaava objekt klase
GrafObjekt, dok je u memoriji stvarno objekt klase Linija.
Mehanizam virtualnih poziva moe se zaobii i tako da se operatorom za
odreivanje podruja eksplicitno navede klasa iz koje se eli pozvati funkcijski lan. Na
primjer:
duzina.GrafObjekt::Crtaj();
go1->GrafObjekt::Crtaj();
//
//
//
//
statiki poziva
GrafObjekt::Crtaj()
statiki poziva
GrafObjekt::Crtaj()
// pogreka
Gornji primjer je neispravan zato to prilikom prevoenja nije poznato na koji objekt
pokaziva go1 pokazuje. Ako bi go1 pokazivao na objekt klase GrafObjekt, lan
Linija::Crtaj() bi mogao pristupiti lanovima x1 ili y1 koji ne postoje u objektu
GrafObjekt, to bi rezultiralo neispravnim radom programa.
Statiki poziv pomou operatora za odreivanje podruja odnosi se samo na onaj
lan koji je eksplicitno naveden u pozivu. Eventualni virtualni pozivi iz tog lana ostaju
nepromijenjeni. Na primjer:
go1->GrafObjekt::Pomakni(5, 7);
380
Ovakvom
Poput svih drugih funkcijskih lanova, i destruktor moe biti virtualan. Razmotrimo
razloge zbog kojih bi nam to moglo biti od koristi.
381
objekta:
int const MAX_ELT = 3;
GrafObjekt *nizObj[MAX_ELT];
nizObj[0] = new Linija;
nizObj[1] = new Poligon;
nizObj[2] = new ElipsinIsj;
382
u izvedenim klasama dodati kljuni rije virtual ispred deklaracije destruktora. Tada
je svakom odmah jasno kako poziva li se destruktor virtualno ili ne, bez nepotrebnog
gledanja u osnovnu klasu.
Destruktori apstraktnih klasa se u pravilu navode kao virtualni. Razlog tome je to
objekt virtualne klase ne moe biti deklariran, pa niti destruktor sa statikim pozivom za
takav objekt nema smisla.
Iz izloenoga se vidi da je pozivanje ispravnog destruktora eljeno ponaanje za
svaki objekt. Teko je zamisliti situaciju u kojoj bi pozivanje neispravnog destruktora
moglo biti eljeno ponaanje. Postavlja se logino pitanje zato onda uope postoje
destruktori sa statikim pozivom. Odgovor je praktine naravi: mehanizam virtualnih
funkcija proiruje svaki objekt s vptr pokazivaem na tablicu virtualnih funkcija.
Takoer, za svaku klasu je potrebno zauzeti u memoriji tablicu virtualnih funkcija. U
mnogim sluajevima, naprotiv, klasa nee uope biti naslijeena te nee niti doi do
opasnosti od poziva neodgovarajueg destruktora. Zbog toga bi resursi za smjetaj
objekata u memoriji bili uzalud potroeni. C++ jezik prua programeru na volju da sam
optimizira svoj program kako smatra najprikladnijim. Kako programer sije, tako e i
eti.
Zadatak. Listi opisanoj u odsjeku 11.2 dodajte lan UPotrazi() koji e kao
parametar imati referencu na neki objekt naslijeen od klase Atom. On e pretraiti
cijelu listu dok se ne nae lan koji odgovara zadanom. Usporedba e se obavljati
pomou preoptereenog operatora ==. Zbog toga je klasu Atom potrebno proiriti
istim virtualnim operatorom == te definirati taj operator u klasi koja nasljeuje klasu
Atom.
Zadatak. Umjesto smjetaja grafikih objekata u polje, prepravite klase grafikih
objekata tako da se objekti mogu smjetati u vezanu listu definiranu klasom Lista. (To
znai da GrafObjekt mora naslijediti klasu Atom.) Napiite program koji e crtati
grafike objekte iz liste, umjesto iz polja.
Zadatak. Napiite funkciju Animacija() koja kao parametar ima pokaziva na
grafiki objekt te pomie taj objekt po ekranu. Funkcija mora raditi ispravno sa svakim
objektom, to zapravo znai da e koristiti virtualne pozive funkcija za crtanje i
brisanje.
383
public:
virtual
virtual
virtual
virtual
virtual
};
Ponekad je potrebno tekst uokviriti pravokutnikom. Bilo bi vrlo praktino stvoriti novu
klasu UokvireniTekst koja bi definirala upravo takav objekt. Takav objekt je neka
vrsta krianca izmeu objekta Tekst i objekta Pravokutnik; on jest u isto vrijeme i
tekst i pravokutnik. Na njega ima smisla primijeniti i operacije koje se primjenjuju na
tekst, ali i operacije koje se primjenjuju na pravokutnike. Jedno od moguih rjeenja je
iskoristiti mehanizme viestrukog nasljeivanja:
class UokvireniTekst : public Pravokutnik, public Tekst {
// ...
};
384
GrafObjekt
GrafObjekt
GrafObjekt
Pravokutnik
Poligon
GrafObjekt
Pravokutnik
Tekst
Tekst
UokvireniTekst
UokvireniTekst
nije nimalo jasniji: klasa GrafObjekt postoji u klasi UokvireniTekst dva puta pa je
navedeni pristup podjednako nejasan. Za pristup tom lanu je potrebno tono navesti
kojem se od tih dvaju lanova pristupa: onom naslijeenom od klase Pravokutnik ili
onom naslijeenom od klase Tekst:
ut.Pravokutnik::PostaviBoju(BIJELA);
ut.Tekst::PostaviBoju(ZELENA);
U nekim primjenama pojavljivanje osnovne klase vie puta u toku nasljeivanja moe
biti poeljno. U ovom sluaju to je oito pogreno. Klasa GrafObjekt predstavlja
osnovnu klasu za sve grafike objekte u hijerarhiji. Kako je UokvireniTekst u cjelini
samo grafiki objekt, bilo bi logino da ta klasa ima klasu GrafObjekt proslijeenu
samo jednom. Tako oblikovano hijerarhijsko stablo izgledalo bi kao na slici 11.10.
Mehanizam virtualnih osnovnih klasa (engl. virtual base classes) omoguava
definiranje osnovnih klasa koje se dijele izmeu vie izvedenih klase te se prilikom
nasljeivanja izvedenoj klasi prosljeuju samo jednom.
11.12.1. Deklaracija virtualnih osnovnih klasa
Neka osnovna klasa moe se uiniti osnovnom virtualnom klasom tako da se prilikom
nasljeivanja u listi nasljeivanja ispred naziva klase umetne kljuna rije virtual.
Redoslijed kljunih rijei public, private, protected i virtual pritom nije bitan.
Klasa se ne definira virtualnom prilikom njene deklaracije, nego prilikom nasljeivanja.
385
GrafObjekt
Poligon
Tekst
Pravokutnik
UokvireniTekst
To znai da se deklaracija klase GrafObjekt ne mijenja. tovie, ona moe jednom biti
upotrijebljena kao virtualna, a drugi put kao nevirtualna:
class Poligon : public virtual GrafObjekt {
// ...
};
class Pravokutnik : public Poligon {
// ...
};
class Tekst : public virtual GrafObjekt {
// ...
};
class UokvireniTekst : public Pravokutnik, public Tekst {
// ...
};
Kako su klase Poligon (a preko nje i klasa Pravokutnik) i Tekst definirale klasu
GrafObjekt kao virtualnu osnovnu klasu, klasa UokvireniTekst e sada zaista
imati samo jedan podobjekt klase GrafObjekt.
Mehanizam virtualnih klasa omoguava nam da rijeimo na problem na
zadovoljavajui nain, a to je da definiramo jednu klasu zajedniku svim preostalim
klasama. No takvo nasljeivanje uvodi novi niz pravila koja se moraju potivati da bi se
mehanizam mogao uspjeno primjenjivati.
11.12.2. Pristup lanovima virtualnih osnovnih klasa
386
klase koji potiu od osnovnih virtualnih klasa nisu sadrani u objektu izvedene klase.
Oni se uvaju u posebnom bloku memorije, a skriveni pokaziva izvedene klase
pokazuje na podobjekt osnovne virtualne klase, kako je prikazano na slici 11.11.
Prilikom izvoenja oba izvedena objekta e jednostavno pokazivati na isti podobjekt.
Na elemente virtualnih osnovnih klasa primjenjuju se pravila pristupa i tipova
izvoenja kao i za obine nevirtualne klase: lanovi naslijeeni po javnom nainu
nasljeivanja zadravaju svoje originalno pravo pristupa, dok lanovi naslijeeni po
privatnom i zatienom nainu dobivaju privatno ili zatieno pravo pristupa.
No to se dogaa u sluajevima kada je osnovna virtualna klasa naslijeena jednom
kao javna, a jednom kao privatna? Na primjer:
class Poligon : private virtual GrafObjekt {
// ...
};
class Pravokutnik : public Poligon {
// ...
};
class Tekst : public virtual GrafObjekt {
// ...
};
class UokvireniTekst : public Pravokutnik, public Tekst {
// ...
Pravokutnik
GrafObjekt
Tekst
GrafObjekt
Pravokutnik
GrafObjekt
Tekst
UokvireniTekst
387
};
// OK
Sada u klasi UokvireniTekst postoje dva funkcijska lana za postavljanje boje: jedan
iz klase GrafObjekt dobiven preko klase Pravokutnik i drugi definiran u klasi
Tekst. Ako se prilikom izvoenja klase Tekst ne bi koristilo virtualno nasljeivanje,
prevoditelj ne bi mogao razlikovati ta dva lana, odnosno prilikom prevoenja bi
dojavio pogreku. No ako lan koji se poziva pripada virtualnoj osnovnoj klasi i nekoj
klasi koja se nalazi blie u hijerarhijskom lancu, prevoditelj e izabrati ovaj potonji.
Kae se da lanovi iz bliih klasa dominiraju nad lanovima osnovnih virtualnih klasa.
Na primjer:
UokvireniTekst ut;
ut.PostaviBoju(BIJELA); // poziva Tekst::PostaviBoju(int)
388
389
390
Vrlo korisna mogunost jezika C++ jesu predloci. Oni omoguavaju pisanje
openitog kda koji vrijedi jednako za razliite tipove. Time se vrijeme razvoja
sloenih programa moe znaajno skratiti, a olakava se i odravanje programa te
ispravljanje eventualnih pogreaka.
Predloci se dijele u dvije osnovne skupine: predloci funkcija i predloci klasa.
Prvi omoguavaju pisanje opih funkcija koje se zatim primjenjuju za razliite tipove
parametara. Potonji omoguavaju definiranje opih klasa koje se zatim aktualiziraju
tipovima prilikom poziva. U ovom poglavlju bit e iscrpno objanjene obje vrste
predloaka.
391
No dobiveno rjeenje je zapravo dvolini Janus koji svoje pravo lice otkriva tek ako mu
kao parametar proslijedimo izraz. Naime, makro funkcije se prevode tako da se poziv
jednostavno zamijeni tekstom definicije makro funkcije, pri emu se formalni parametri
zamijene stvarnima. Izraz se tako izvodi dva puta, to moe bitno utjecati na rezultat:
int i = 4, j;
j = manji(++i, 10); // suprotno oekivanju j e sadravati
// broj 6
Poveavanje varijable i se obavlja dva puta: jednom prije i jednom poslije usporedbe,
pa se i cjelokupni rezultat dosta razlikuje od oekivanog.
C++ jezik nudi rjeenje na zadane probleme u obliku predloaka (engl. templates).
Oni omoguavaju upravo eljeno svojstvo da se samo jednom navede algoritam koji se
zatim poziva za razliite tipove podataka. Postoje dvije osnovne vrste predloaka:
predloci funkcija i predloci klasa. Prvi omoguavaju definiciju opih funkcija, dok
drugi omoguavaju definiciju opih klasa.
392
Gornja definicija predloka specificira NekiTip kao identifikator tipa koji e se kasnije
navesti prilikom poziva funkcije. Iako je zapis funkcije slian navedenoj makro-naredbi,
ovakva definicija oznaava funkciju, iji e se parametri izraunati prije poziva. Zbog
toga e sada poziv
int j = manji(++i, 10);
// sada je OK
dati oekivani rezultat. Predloak nije makro funkcija prilikom prevoenja predloka
prevoditelj nee parametre funkcije samo ubaciti u definiciju predloka. Ovdje se radi o
definiciji (hipotetski) beskonanog skupa funkcija koje su parametrizirane tipom.
Prilikom prevoenja, predloak e za svaki pojedini tip parametra rezultirati jednom
sasvim konkretnom funkcijom, koja e se pozivati na uobiajeni nain.
Identifikator tipa se u listi formalnih parametara predloka smije pojaviti samo
jednom:
template <class T, class T>
void func(T a) {
// ...
}
// pogreka
393
Svaki formalni parametar mora biti oznaen kljunom rijei class. Sljedea definicija
je neispravna:
// pogreka: mora biti <class Elem, class Zbroji>
template <class Elem, Zbroji>
void Zbroji (Elem *niz, Zbroji zbr);
Formalne parametre NekiTip, Tip i Elem iz gornjih primjera treba shvatiti kao
simbolike oznake za tipove koji e se navesti prilikom poziva funkcije. Na primjer,
ako emo funkciju manji() pozvati za usporeivanje cijelih brojeva, tada e
parametar NekiTip poprimiti vrijednost int, a ako emo usporeivati cipele po
veliini (opisane klasom Cipela) parametar NekiTip e poprimiti vrijednost Cipela.
Iza kljune rijei template i liste formalnih parametara navodi se ili deklaracija ili
definicija funkcije. Prilikom pisanja definicije funkcije vrijede sva dosad navedena
pravila C++ jezika, s tom razlikom to se unutar deklaracije i definicije funkcije za
navoenje tipova koji se obrauju koriste formalni parametri predloka. Na primjer,
umjesto da se navede stvarni tip podataka iji se minimum trai u funkciji manji(),
navest e se tip NekiTip. Formalni parametri se mogu pojaviti na svim mjestima gdje
se moe pojaviti bilo koja regularna oznaka tipa: u listi parametara, za specificiranje
povratne vrijednosti, za deklaraciju varijabli i u izrazima za dodjelu tipa.
Da bismo demonstrirali upotrebu predloaka, realizirat emo funkciju koja odreuje
minimalni lan nekog niza. Funkcija e biti realizirana pomou predloka
parametriziranim tipom elemenata koji ine niz. Funkciji se preko parametara
prosljeuje pokaziva na poetak niza i duljina niza, a ona vraa referencu na objekt
koji je pronaen kao minimalni. Evo realizacije funkcije:
template <class Elem>
Elem &manji_u_nizu(Elem *niz, int brElem) {
Elem *pokNajmanji;
// lokalna varijabla
pokNajmanji = niz;
for (int i = 1; i < brElem; i++)
if (niz[i] < *pokNajmanji) pokNajmanji = &niz[i];
return *pokNajmanji;
}
Formalni parametar predloka je Elem. Pomou njega je parametar niz oznaen kao
pokaziva na tip Elem, kao i lokalna varijabla pokNajmanji. Osim toga, povratna
vrijednost funkcije je Elem &, to zapravo znai da se vraa referenca na tip Elem.
Dozvoljeno je koristiti modifikatore * i & u kombinaciji s identifikatorom
Elem kako bi se oznaili pokazivai i reference na tip.
Prilikom poziva funkcije, prevoditelj e identifikator Elem zamijeniti stvarnim tipom
navedenim u pozivu te e tako odrediti stvarni tip parametara funkcije i lokalne
394
395
Danas to vie nije sluaj: dozvoljeno je prilikom deklaracije predloka navesti parametre
koji se koriste samo u povratnoj vrijednosti funkcije. tovie, mogue je odreeni
parametar uope ne koristiti niti u argumentima niti u povratnoj vrijednosti. Prethodne
su dvije deklaracije stoga danas sasvim ispravne. Tipovi koji nedostaju specificiraju se
prilikom instantacije predloka tako da se navedu u paru znakova < i >. Tako e se
verzija predloka prosjek koja uzima polje float brojeva, a vraa double,
specificirati ovako:
float polje[100];
double pros = prosjek<double, float>(polje, 100);
Prilikom uvoenja predloaka u C++ jezik izabrana je kljuna rije class kao rije
koja specificira da odreeni parametar predloka predstavlja neki tip. Podrazumijevalo
se da taj tip ne mora biti klasa, ve moe biti i ugraeni tip ili pobrojenje. Kako bi se
istakla uloga pojedinog parametra u deklaraciji predloka, uvedena je nova kljuna rije
typename koja se moe koristiti umjesto kljune rijei class. Time se eksplicitnije
naznaava da se odreeni argument predloka zapravo ime tipa, a ne klasa. Kljuna rije
typename ima dodatno znaenje za predloke njome se unutar predloka moe
odrediti da odreeni izraz predstavlja tip, a ne neto drugo. Na primjer:
template <typename T>
void funkcija() {
T::A *pok1;
typename T::A *pok2;
}
// T oznaava tip
// ove dvije deklaracije imaju
// razliito znaenje
U gornjem primjeru naoko bezopasna deklaracija T::A ima razliito znaenje ovisno o
tome stavi li se ispred nje typename ili ne. U naem sluaju T je neki tip, unutar kojeg
je mogue definirati druge tipove. Pretpostavimo da, zbog potreba problema koji
rjeavamo, svaki tip kojim emo instancirati predloak mora imati u sebi definiran tip A
(na primjer, ugnijeenu klasu koja se zove A). Programer bi zasigurno elio pristupiti
tom tipu unutar funkcije, no pitanje je kako. Prva deklaracija ne ini ono to elimo
396
Kljuna rije typename, ba kao i eksplicitno navoenje tipa u pozivu predloka nisu
podrani od mnogih C++ prevoditelja pa ih koristite s oprezom.
Parametar predloka funkcije moe biti i neka konstanta kojom se dodatno upravlja
radom funkcije. Na primjer, mogue je predloak funkcije za uitavanje polja podataka
proizvoljnog tipa s analogno/digitalnog pretvaraa parametrizirati cjelobrojnom
konstantom:
template <class TipPolja, int brElem>
void ADCUcitajPolje(TipPolja *polje) {
TipPolja pomocnoPolje[brElem];
// ...
}
397
koristimo predloak kojemu poseban tip specificira preciznost rauna, i taj tip u veini
sluajeva iznosi double, predloak moemo definirati ovako:
template <class T, class Preciznost = double>
void Inverz(T[100][100]);
Definicija predloka daje prevoditelju samo opi algoritam koji se koristi za rjeenje
nekog problema. No prevoditelj ne moe stvoriti kd same funkcije prije nego to
stvarno dozna na koji je nain potrebno zamijeniti formalne parametre stvarnima. Zbog
toga e prevoditelj prilikom prevoenja kda preskoiti definiciju predloka: ona moe
sadravati i grube pogreke, ali je sva prilika da one na tom mjestu uope nee biti
prepoznate.
Podaci o aktualnim tipovima postaju dostupni tek kada se funkcija pozove. Tada se
obavlja postupak instantacije. Prevoditelj provodi zamjenu tipova, prevodi funkciju i
generira njen izvedbeni kd. Na primjer:
template <class Tip>
Tip manji(Tip a, Tip b) {
return a < b ? a : b;
}
int main() {
cout << "Od 5 i 6: " << manji(5, 6) << endl;
cout << "Od 'a' i 'b' je " << manji('a', 'b') << endl;
return 0;
}
U gornjem programu funkcija manji() se poziva dva puta. Prvi put se kao parametri
navode cjelobrojne konstante, dok se drugi put navode znakovne konstante. Za svaki od
ova dva poziva stvara se po jedna verzija funkcije. Prevoditelj koristi tipove argumenata
da bi odredio koji se predloak funkcije koristi. Zbog toga se i svi formalni argumenti
predloka obavezno moraju pojaviti u listi parametara funkcije.
U prvom sluaju se openiti tip Tip zamjenjuje tipom int, a u drugom sluaju s
char. Nakon supstitucije tipova, stvaraju se dvije preoptereene verzije funkcije
manji(), iji bi ekvivalent u izvornom kdu bio:
398
Stvaraju se dvije verzije funkcije, ba kao da smo i sami napisali obje verzije, no posao
generiranja razliitih verzija za iste parametre je prebaen na prevoditelja. Time je,
takoer, uklonjena mogunost pogreke prilikom runog kopiranja kda u razliite
varijante funkcije.
U posljednjoj verziji C++ standarda je parametre predloka mogue eksplicitno
navesti unutar para znakova < i >. Unutar tih znakova mogue je navesti eventualne
konstantne parametre koje predloak moe imati. To svojstvo se najvie koristi prilikom
specificiranja parametara koji odreuju povratni tipova ili koji se uope ne spominju u
deklaraciji funkcije. Na primjer:
template <class lokalniPodaci>
int Usporedi(char *niz1, char *niz2);
399
Pri tome, ako se navode parametri predloka, dozvoljeno je izostaviti samo parametre s
kraja liste nije mogue, primjerice, izostaviti prvi parametar i oekivati da e on biti
zakljuen iz poziva, a drugi specificirati eksplicitno, na primjer:
template <class Tip, class Preciznost>
Preciznost Prosjek(Tip *polje, int brElem);
float polje[100];
double pros = prosjek<double>(polje, 100);
// pogreno
400
Gornji poziv nee biti uspjean jer je potrebno postii potpuno slaganje tipova, a u
konkretnom pozivu je za drugi parametar umjesto podatka tipa int naveden podatak
tipa long. Da bi poziv uspio, potrebno je primijeniti eksplicitnu dodjelu tipa:
m = manji_u_nizu(niz, (int)dulj);
401
Ovakav poziv rezultira pogrekom zato jer prevoditelj prilikom instanciranja funkcije ne
zna kako treba usporediti dva vektora. Ako izmislimo nain usporeivanja vektora i
ugradimo ga u klasu Vektor tako da preopteretimo operator <, gornji poziv postaje
ispravan i on stvarno nalazi najmanji vektor s obzirom na zadani kriterij usporedbe.
Zadatak. Napiite predloak funkcije srednja_vrijednost(). Kao parametar ona e
uzimati polje i cijeli broj koji e pokazivati duljinu polja. Rezultat e biti istog tipa kojeg
su i tipovi elemenata polja. Pri tome se za zbrajanje lanova polja i za dijeljenje lana
polja cijelim brojem koriste operatori + i / definirani u klasi koja opisuje tip elemenata
polja. Pretpostavite da lan polja ne mora imati podrazumijevani konstruktor, ali
sigurno ima ispravan konstruktor kopije.
Zadatak. Napiite predloak funkcije binarno_pretrazi() koja e kao parametre
imati polje, referencu na objekt istog tipa kojeg su i elementi polja te cijeli broj koji
definira duljinu polja. Funkcija pretpostavlja da je ulazni niz sortiran po veliini te e
provesti binarno pretraivanje proslijeenog polja. Kao rezultat e biti cijeli broj koji
e pokazivati indeks proslijeenog objekta u polju ili -1 ako objekt nije pronaen.
Uputa: binarno pretraivanje radi tako da se prvo pogleda element na sredini polja.
Ako je zadani element manji od traenog, pretrauje se preostala gornja polovica polja
(jer je polje sortirano), a u suprotnom donja polovica. Postupak se ponavlja po istom
principu na sve manjem i manjem dijelu polja, dok se traeni objekt ne pronae ili se
interval ne svede na jedan lan. Tada je ili taj lan jednak traenom ili se traeni objekt
ne nalazi u polju. Za usporedbu koristite operatore <, >, <=, >=, == te != definirane u
klasi koja opisuje pojedini lan polja.
402
403
Kao i prilikom implicitne instantacije, pojedini tip se moe izostaviti ako se njegova
vrijednost moe odrediti pomou liste argumenata:
// argument Tip e se postaviti na float:
template Prosjek<double>(float *, int);
Predloak funkcije moe biti preoptereen proizvoljan broj puta pod uvjetom da se
potpisi funkcija razlikuju po tipu i/ili broju argumenata. Na primjer, moemo definirati
funkciju zbroji() koja zbraja objekte:
// zbrajanje dvaju objekata
template <class Tip>
Tip zbroji(Tip a, Tip b);
// zbrajanje triju objekata
template <class Tip>
Tip zbroji(Tip a, Tip b, Tip c);
// zbrajanje dvaju nizova
template <class Tip>
void zbroji(Tip *niza, Tip *nizb, Tip *rez, int brElem);
U gornjem primjeru prvi parametar je tipa int, a drugi je unsigned int. Kako se
konverzije tipova ne provode prilikom poziva predloaka funkcija, gornji poziv ne
uspijeva. Ako bismo eljeli zbrajati razliite tipove preoptereenom verzijom funkcije
zbroji(), morali bismo dodati novu verziju predloka:
template <class Tip1, class Tip2>
Tip1 zbroji(Tip1 a, Tip2 b);
404
405
Postoje situacije u kojima openiti algoritam predloka moe biti neodgovarajui ili
neefikasan za pojedine tipove podataka. Na primjer, naa funkcija manji() nee
ispravno usporediti dva znakovna niza, zato jer se operator < ne interpretira kao
usporedba nizova, nego kao usporedba pokazivaa. U takvim sluajevima programer
moe napisati specijalizaciju predloka funkcije (engl. function template specialization)
koja se koristi samo za taj tip podataka.
Specijalizacija se definira tako da se bez koritenja kljune rijei template
jednostavno napie eljena funkcija. Na primjeru funkcije manji() to izgleda ovako:
#include <string.h>
// predloak za openite tipove
template <class Tip>
Tip manji(Tip a, Tip b) {
return a < b ? a : b;
}
// specijalizacija za char sluaj
char *manji(char *a, char *b) {
return strcmp(a, b) < 0 ? a : b;
}
U posljednjoj verziji C++ jezika mogue je prilikom specijalizacije unutar znakova < i
> navesti parametre za koje se definira specijalizacija. Na primjer:
char *manji<char *>(char *a, char *b);
Lista parametara predloka moe biti i prazna, ako se oni mogu zakljuiti iz liste
argumenta funkcije:
char *manji<>(char *a, char *b);
406
// djelomina specijalizacija
U gornjem primjeru prva deklaracija definira opi predloak parametriziran s dva tipa.
Druga deklaracija je djelomina specijalizacija: ona je parametrizirana jednim tipom, no
drugi argument je char. Ta specijalizacija e se koristiti za sve oblike funkcije kod
char
kao
drugi
argument:
f<long, char>(),
kojih
navedemo
f<Kompleksni, char>(), f(int (*)(int, Kompleksni &), char>() itd.
Postupak odreivanja odgovarajue funkcije prilikom poziva izvodi se po
sljedeem algoritmu:
1. Prvo se pretrauju sve specijalizacije predloka te se trai tono podudaranje
argumenata. Ako se nae vie funkcija koje odgovaraju, dojavljuje se pogreka
prilikom prevoenja. Ako se nae tono jedna odgovarajua funkcija, postupak
zavrava. U protivnom se prelazi na sljedei korak.
2. Pretrauju se svi predloci funkcije te se trai tono podudaranje parametara. Ako se
pronae vie od jednog odgovarajueg predloka, javlja se pogreka prilikom
prevoenja. Ako se pronae tono jedan predloak, funkcija je odreena te se
provjerava je li ve generirana varijanta funkcije za zadane parametre. Ako nije,
prevoditelj prevodi predloak te generira traenu funkciju. Ako nije pronaen
odgovarajui predloak, prelazi se na sljedei korak.
3. Sve specijalizacije se promatraju kao skup preoptereenih funkcija te se pokuava
odrediti odgovarajua funkcija pravilima za poziv preoptereene funkcije, to
ukljuuje i mogue konverzije tipa.
Zadatak. Napiite specijalizaciju funkcije binarno_pretrazi() iz zadatka na stranici
401 tako da ona ispravno radi za znakovne nizove.
12.2.7. Primjer predloka funkcije za bubble sort
407
prvi prolaz
drugi prolaz
trei prolaz
Nakon sortiranja rezultat se moe ispisati funkcijom pisi_niz() koja se takoer moe
realizirati predlokom. Ona moe ispisati bilo koji niz pod uvjetom da je za zadani tip
definiran operator << za ispis na izlazni tok.
#include <iostream.h>
template <class Elem>
void pisi_niz(Elem *niz, int brElem) {
cout << "{ ";
for (int i = 0; i < brElem; i++) {
cout << niz[i];
408
Evo i primjera poziva tih funkcija, jednom za niz cijelih brojeva, a jednom za niz realnih
brojeva:
int main() {
int niz1[] = {4, 5, 2, 7, 1, 0, 9};
double niz2[] = {2.6, 7.9, 1.4, 8.9, 9.9, 3.3};
bubble_sort(niz1, 7);
cout << "Niz cijelih brojeva:" << endl;
pisi_niz(niz1, 7);
bubble_sort(niz2, 6);
cout << "Niz realnih brojeva:" << endl;
pisi_niz(niz2, 6);
return 0;
}
409
radi li se o listi cijelih ili kompleksnih brojeva. Ako bismo primijenili metodu runog
kopiranja i promjene kda klase, tada bi svaka dobivena klasa morala imati svoje
posebno ime: CjelobrLista, KomplLista, ListaNizaZnakova i sl.
12.3.1. Definicija predloka klase
klasa Lista e definirati opa svojstva liste, vodit e rauna o njenom poetnom i
zavrnom lanu te e definirati javno suelje liste i
klasa ElementListe koja e definirati objekt koji se smjeta u listu. Taj objekt e
sadravati pokazivae na prethodni i sljedei element te samu vrijednost objekta.
Definicija predloka klase se ne razlikuje znaajnije od definicije obinih klasa.
Deklaracija se zapoinje tako da se ispred kljune rijei class umetne kljuna rije
template iza koje se unutar znakova < i > (znakovi manje-od i vee-od) navode
argumenti predloka. Na primjer:
template <class Tip>
class Lista;
Argumenti predloka klase se navode slino kao i argumenti predloka funkcije: svaki
argument sastoji se od kljune rijei class i imena parametra. Kljuna rije template
se mora staviti ispred svake deklaracije unaprijed (kao u gornjem primjeru) ili ispred
definicije klase. Lista parametara ne smije biti prazna. Vie parametara se navodi
razdvojeno zarezima:
template <class T1, class T2, class T3>
class NekaKlasa;
Slino predlocima funkcije, parametar predloka klase moe biti i izraz. Taj parametar
se u deklaraciji navodi slino obinim parametrima funkcija: umjesto kljune rijei
class stavlja se naziv tipa iza kojeg se stavlja identifikator. Na primjer, ako bismo
htjeli ograniiti broj elemenata u listi te time sprijeiti preveliko zauzee memorije,
mogli bismo dodati predloku klase cjelobrojni parametar maxElem tipa int:
template <class Tip, int maxElem>
class OgranicenaLista;
410
Klase definirane kao predloci koriste se u programu na isti nain kao i obine klase, s
tom razlikom to se iza naziva klase u kutnim zagradama (< >) obavezno moraju navesti
parametri. Na primjer, lista cijelih brojeva se oznaava kao
Lista<int>
411
412
dio memorije koji uope ne sadrava eljeni objekt. Pokuaj pristupa objektu vjerojatno
e rezultirati pogrekom u radu programa, a takoer moe znatno poremetiti rad
operacijskog sustava.
Zbog opasnosti od viseih referenci valja biti vrlo oprezan prilikom
koritenja kontejnera koji uvaju pokazivae na lanove.
Kao to se vidi, nema ope preporuke koju je mogue bezuvjetno slijediti. Programer
mora prilikom razvoja sustava odvagnuti razloge za i protiv te se odluiti za pristup koji
najbolje odgovara danom podruju primjene.
I na kraju, promotrit emo jo nain na koji je izveden konstruktor klase. Kao prvo,
konstruktoru se kao parametar prosljeuje referenca na objekt. U suprotnom bi se
prilikom poziva konstruktora objekt nepotrebno kopirao u lokalni objekt ime bi se
usporilo izvoenje kda. Takoer, lokalni objekt vrij se inicijalizira u inicijalizacijskoj
listi konstruktora. Time se izbjegava nepotrebna inicijalizacija tog objekta
podrazumijevanim konstruktorom i naknadno kopiranje parametra u taj objekt.
U posljednjoj verziji C++ jezika mogue je koristiti podrazumijevane vrijednosti
parametara. Na primjer, zamislimo predloak klase NizZnakova koji moe biti
parametriziran tipom znaka koji se koristi (char ili wcahr_t, ovisno o situaciji).
Gotovo sigurno emo u 99 % sluajeva koristiti char, pa je praktino char postaviti
kao podrazumijevani parametar:
template <class Tip = char>
class NizZnakova;
// OK
// pogreno
Predloak klase se instancira tako da se iza naziva klase unutar znakova < i > navedu svi
stvarni parametri predloka. Na primjer:
ElementListe<int> eli(5);
ElementListe<Kompleksni> elk(Kompleksni());
ElementListe<char *> elz("Listam");
413
Gornje definicije treba promatrati na sljedei nain: ime ElementListe oznaava sve
mogue elemente liste te ne opisuje eljeni objekt dovoljno precizno. Zbog toga nije
mogue napraviti objekt klase navodei samo naziv klase, ve je potrebno stvoriti
konkretnu realizaciju predloka. Tako je ElementListe<int> zapravo potpuni naziv
jedne konkretne klase koja opisuje element liste cijelih brojeva. Ako se prihvati da je to
puno ime jedne klase (koja je dobivena preko predloaka, no sa stanovita njenog
koritenja nema nikakve razlike s ostalim klasama koje nisu definirane preko
predloaka), onda je odmah jasno kako se takve klase mogu koristiti. Objekti klasa
predloaka deklariraju se isto kao i objekti obinih klasa, s time da se mora navesti puni
naziv klase.
Puni naziv klase stvorene na osnovu predloka ukljuuje parametre koji se
navode unutar znakova < i >. Za stvaranje objekata klase potrebno je koristiti
njen puni naziv.
Nakon prethodnih deklaracija prevoditelj e zapravo stvoriti tri klase. To e biti tri
nezavisne
klase
iji
e
puni
nazivi
glasiti
ElementListe<int>,
ElementListe<Kompleksni> i ElementListe<char *>. Moemo si zamisliti kao
da e prevoditelj u sutini provesti pretrai-i-zamijeni operaciju tri puta. Svaki put e
formalni argument Tip zamijeniti stvarnim tipom i dobiveni kd ponovo prevesti. Tek
tada e se provjeriti sintaktika ispravnost dobivenog kda. Tako se esto moe
dogoditi da predloak ispravno radi s jednim tipom kao parametrom, a ne radi s nekim
drugim. U naem sluaju, za ispravan rad predloka, klasa koja se prosljeuje kao
parametar mora imati definiran konstruktor kopije (ili podrazumijevani konstruktor
kopije mora odgovarati). U suprotnom moe doi do problema prilikom kopiranja
proslijeenog objekta u lokalni objekt vrij. Ovo je primjer kada se predloak moe
prevesti, ali ne funkcionira ispravno. No postoje i sluajevi kada instanciranje jednim
tipom prolazi bez problema, dok instanciranje drugim tipom prouzrokuje pogreke
prilikom prevoenja. To se deava kada primjerice neki od funkcijskih lanova klase
koristi operator usporedbe za tipove kojima je predloak parametriziran. Da bi se takva
klasa uspjeno instancirala, tip koji joj se prosljeuje kao parametar mora imati
definirane operatore usporedbe.
Sve uobiajene konvencije koje vrijede za obine klase vrijede i za predloke klasa.
Tako je mogue neke objekte definirati vanjskima pomou kljune rijei extern ili
statikima pomou kljune rijei static. Mogue je definirati polja objekata,
pokazivae i reference na njih, te dodavati kvalifikatore volatile i const:
extern ElementListe<int> vanjski;
ElementListe<Kompleksni> poljeZ[20];
const ElementListe<int> postojaniKositreniInt(0);
ElementListe<Kompleksni> *pok = poljeZ, &ref = poljeZ[0];
Iako bi to iz dosadanjeg izlaganja trebalo biti samo po sebi jasno, klase istog predloka
razliitih parametara predstavljaju potpuno razliite tipove i nije ih mogue mijeati. Na
primjer, nije ih mogue usporeivati, pridruivati ili prosljeivati kroz parametre (osim
414
// pogreka
nije sintaktiki ispravna i nema smisla. Ona pokuava meusobno pridruiti dva
potpuno razliita tipa. Uostalom, ako se malo bolje pogleda, gornje pridruivanje niti
nema nekog logikog smisla.
Pojedine instance neke klase definirane predlokom predstavljaju razliite
tipove te ih nije dozvoljeno mijeati.
Moe se dogoditi da se predloak treba instancirati unutar definicije drugog predloka.
Tada se parametar predloka koji se definira moe koristi kao stvarni parametar
predloka kojeg se instancira. Pretpostavimo da svakoj varijanti klase ElementListe
elimo pridruiti funkciju Ispisi() koja e ispisati sadraj elementa na ekran. Takva
funkcija e se morati definirati predlokom:
template <class Tip>
void Ispisi(ElementListe<Tip> &Elem) {
ElementListe<Tip> *pok = &Elem;
// ...
}
415
Izmeu gornja dva rjeenja postoji sutinska razlika. Da bi se ona razumjela, potrebno je
sjetiti se da se predloci instanciraju tako da se identifikatori parametara zamijene
stvarnim parametrima po pretrai-i-zamijeni principu te svaki put dobivamo zasebnu
klasu. To znai da e instantacije
OgranicenaLista<int, 5> ol5;
OgranicenaLista<int, 6> ol6;
definirati dvije odvojene klase. Svaka od tih klasa imat e svoj skup funkcijskih
lanova. Dobiveni izvrni program bit e stoga dulji jer e sadravati verziju
funkcijskih lanova za 5 i za 6 elemenata. Ako bi se u programu koristile jo liste drugih
duljina, dobiveni izvedbeni kd bi znatno rastao, pogotovo ako je klasa opirna i s
mnogo funkcijskih lanova. Takoer, parametar maxElem mora biti konstantan izraz,
odnosno mora biti poznat prilikom prevoenja.
Drugo rjeenje nema taj nedostatak: postoji jedna klasa koja se jednom instancira za
odreeni tip. Duljina liste se odreuje prilikom poziva konstruktora. Tim vie, duljina ne
mora biti konstantan izraz, nego se moe izraunati prije poziva konstruktora.
Postavlja se pitanje zato bi onda netko uope htio koristiti izraze kao parametre
predloku. Razlog lei u brzini izvoenja. Naime, kod prvog rjeenja se alokacija
memorije potrebne za spremanje liste obavlja vrlo brzo jer se alocira jedno polje
objekata poznate duljine. U drugom sluaju koristi se dinamika dodjela memorije, koja
radi dosta sporije. Alokacija preko polja je mogua zato jer je parametar maxElem
poznat prilikom prevoenja. U drugom sluaju parametar konstruktoru se odreuje
prilikom izvoenja, pa nije mogue alokaciju obaviti na tako elegantan nain.
Ako se neki funkcijski lan klase ne koristi u programu za odreeni skup
argumenata, prevoditelj ga nee generirati (osim u sluaju eksplicitne instantacije
pogledajte sljedei odsjeak). To omoguava da neke operacije budu definirane samo za
neke tipove. Primjerice, klasa Lista moe sadravati funkcijski lan za sortiranje
lanova u rastui poredak. Pri tome e se za usporedbu koristiti operator < koji mora biti
preoptereen za zadani tip. No mnogi tipovi, primjerice Kompleksni, nemaju
definirane operatore usporedbe, jer se za njih usporedba ne moe definirati na neki
razuman nain. Ako bi prevoditelj prilikom instantacije generirao sve lanove predloka
klase, tada ne bismo mogli koristiti Lista<Kompleksni> jer lan za sortiranje ne bi
proao prevoenje.
416
U odsjeku 12.2.4 naveli smo razloge zbog kojih je esto potrebno eksplicitno
instancirati neki predloak funkcije Slino vrijedi i za predloke klasa: mogue je dati
prevoditelju zahtjev da instancira odreeni predloak za zadani skup parametara.
Cijela klasa se instancira tako da se iza kljunih rijei template class navede
naziv klase s parametrima:
template class Lista<Kompleksni>;
Ovime e se instancirati svi funkcijski lanovi klase Lista s
tipom Kompleksni kao parametrom. Osim instanciranja cijele klase,
mogue je instancirati samo eljeni funkcijski lan klase:
template void Lista<Vektor>::UgurajClan(Vektor, int);
Poneki funkcijski lanovi predloka klase mogu biti neadekvatni za neke konkretne
tipove koji se mogu proslijediti predloku kao parametar. U tom sluaju mogue je
definirati specijalizaciju funkcijskog lana koja e precizno definirati nain na koji
dotini lan mora biti obraen.
Na primjer, ako bismo klasu ElementListe parametrizirali tipom char *,
konstruktor klase ne bi djelovao ispravno. Konstruktor izgleda ovako:
template <class Tip>
inline ElementListe<Tip>::ElementListe(const Tip &elem) :
vrij(elem), prethodni(NULL), sljedeci(NULL) {}
417
U gornjem primjeru parametar konstruktoru je char * const &. Nije sasvim oito, no
radi se o referenci na konstantan pokaziva na znak. Nije bilo dovoljno navesti char *,
const char * niti char const *. Evo i zato: konstruktor deklariran u klasi kao
parametar ima referencu na konstantan tip. Tip specijaliziranog konstruktora mora tome
odgovarati zato nije dovoljno samo prenijeti pokaziva na znak, nego referencu na
pokaziva. Uz ovakvu dopunu, sada e se za sve elemente liste koji sadravaju
pokazivae na znak koristiti dani konstruktor.
Ponekad implementacija definirana predlokom nije dovoljno efikasna za neke
tipove. Takoer, mogue je da za neki tip klasa treba dodatak javnom suelju kako bi
funkcionirala ispravno. Tada moemo definirati specijalizaciju cijele klase za pojedini
tip. Specijalizacija se definira tako da se nakon naziva klase unutar znakova <> navede
tip za koji se specijalizacija definira. Ona se ne mora poklapati u sadranim funkcijskim
lanovima s predlokom klase. Specijalizacija predstavlja jednu zasebnu klasu koja
moe biti potpuno drukija od preostalih klasa dobivenih iz predloka. No sa
specijalizacijama ne treba pretjerivati.
Ako dotina klasa uistinu predstavlja objekt koji nema slinosti s
predlokom, onda je ispravnije ne definirati ga kao specijalizaciju predloka
nego kao neku drugu klasu zasebnog imena.
U naem sluaju, ako klasa ElementListe sadrava znakovne nizove, ona iziskuje
dodatni funkcijski lan destruktor. Predloak klase ne definira destruktor jer e svi
podatkovni lanovi objekta biti automatski uniteni. Objekt koji se uva u listi bit e
automatski uniten pomou podrazumijevanog destruktora klase. No u sluaju
znakovnih nizova podrazumijevani destruktor nee osloboditi zauzetu memoriju. Zbog
toga emo definirati specijalizaciju klase za tip char *:
class ElementListe<char *> {
private:
char *vrij;
ElementListe *sljedeci, *prethodni;
public:
ElementListe(char *elem);
~ElementListe();
char *DajVrijednost() { return vrij; }
418
Dodani destruktor e sada ispravno unititi objekt jer e i osloboditi zauzetu memoriju.
Po istom principu smo dodali funkcijski lan Kopiraj(). On kopira znakovni niz u
memorijski spremnik te je svojstven samo implementaciji za znakovne nizove. Takoer,
kako specijalizirana klasa moe definirati sasvim drukije lanove, vie nije potrebno
patiti se s udnim konstruktorima konstruktor je sada deklariran prirodno (lat.
declaratio naturalis).
Specijalizacija predloka se moe navesti samo nakon navoenja opeg
predloka. Takoer, ako se specijalizirani primjerak klase instancira,
potrebno je definirati sve funkcijske lanove eksplicitno.
To znai da nije mogue u gornjem primjeru izostaviti definiciju funkcijskog lana
DajVrijednost() i oekivati da e prevoditelj primijeniti opu varijantu funkcije.
// opi predloak
// parcijalna specijalizacija
419
Prilikom definiranja predloka klase mogue je navesti statike lanove. U tom sluaju
svaka instanca navedenog predloka e imati svoj zasebni skup statikih lanova. Svaki
od tih lanova se mora inicijalizirati zasebno.
Uzmimo na primjer da elimo brojati ukupan broj listi koje imamo u memoriji. To
se jednostavno moe uiniti tako da klasi Lista dodamo statiki lan. U konstruktoru
klase potrebno je poveati lan za jedan, a u destruktoru klase smanjiti za jedan. Statiki
lan se i prilikom koritenja predloaka klasa moe definirati na isti nain kao i kod
obinih klasa:
template <class T>
class Lista {
private:
ElementListe<T> *glava, *rep;
public:
static int brojLista;
Lista();
~Lista();
void Dodaj(T *elt);
void Brisi(T *elt);
ElementListe<T> *Pocetak() { return glava; }
ElementListe<T> *Kraj() { return rep; }
};
420
sve e se liste poeti brojati od nule. Ako bismo htjeli poeti brojati liste realnih brojeva
od broja 5 (ne ulazei u smisao takvog brojanja), to bismo mogli uiniti na sljedei
nain:
int Lista<float>::brojLista = 5;
Prilikom izvoenja, u prvom retku e se ispisati broj 2 jer u memoriji postoje dvije
cjelobrojne liste, dok e se u drugom retku ispisati broj 1 jer postoji samo jedna lista
nizova znakova. Pristup
cout << Lista::brojLista << endl;
// pogreka
Formalni parametar predloka, osim tipa moe biti i konstantni izraz. Na primjer,
mogue je definirati klasu Spremnik koja definira memorijski meuspremnik za
uvanje podataka prilikom komunikacije s ureajima u raunalu, kao to su disk ili
421
Parametar duljina mora biti poznat prilikom prevoenja. To znai da on mora biti
sastavljen od samih konstanti. Prilikom prevoenja sve pojave identifikatora duljina
se zamjenjuju vrijednou navedenog izraza. Prilikom instantacije klase nije dozvoljeno
parametre definirati varijablama, jer je njihova vrijednost poznata tek prilikom
izvoenja. Na primjer:
Spremnik<20> s1;
Spremnik<10 + 10> s2;
Spremnik<10 * 2> s3;
Spremnik<30> s4;
// pogreka
Tip izraza koji je stavljen na mjesto parametra mora biti potpuno jednak tipu
navedenom u deklaraciji predloka nema konverzije:
Spremnik<6.7 * 6.4> bez_konverzije;
// pogreka
422
423
Ako su ugnijeeni tipovi javni, moe im se pristupati i iz okolnog programa. Pri tome
je potrebno potpuno navesti put do klase kojoj se eli pristupiti. Svaka varijanta klase
Lista definira jednu klasu ElementListe. Tako je mogue koristiti tipove
Lista<int>::ElementListe
Lista<char *>::ElementListe
Predloak klase takoer moe imati ugnijeena pobrojenja. Njima se isto tako moe
pristupiti iz okolnog podruja pod uvjetom da se navede puna staza do tipa. Na primjer,
klasa Spremnik moe definirati pobrojenja koja opisuju redni broj zadnjeg elementa u
spremniku te kritinu veliinu spremnika koja je jednaka 75% ukupne veliine:
template <int duljina>
class Spremnik {
private:
char podrucje[duljina];
public:
enum {kriticna = (int)(0.75 * duljina),
zadnji = duljina - 1, maksimum = 1000};
void Puni(char *pok, int dulj);
void Citaj(char *pok, int dulji);
};
Punu stazu je potrebno navesti i prilikom pristupa identifikatoru maksimum, bez obzira
na to to se vrijednost tog identifikatora ne mijenja u razliitim instantacijama klase:
cout << Spremnik::maksimum << endl;
// pogreka
cout << Spremnik<20>::maksimum << endl; // ispravno
424
};
};
Neka sada imamo funkciju sqrt() koja rauna korijen iz kompleksnog broja. Ona sada
mora biti parametrizirana odreenom instancom predloka uzet emo da kao
parametar uzima Kompleksni<double> zato jer se time postie najvea preciznost:
Kompleksni<double> sqrt(Kompleksni<double>);
425
// neispravno
// neispravno
426
Postoje tri naina na koji jedan predloak klase moemo uiniti prijateljem nekog
drugog predloka.
1. Predloak klase se deklarira kao prijatelj neke druge funkcije ili klase (ne predloka
funkcije ili klase). Takvom deklaracijom funkcija ili klasa postaje prijateljem svih
moguih klasa koje se dobiju iz predloka:
class A {
void FClan();
};
class B {
public:
void FClan();
};
void Funkcija();
template <class Tip>
class C {
friend class A;
friend void Funkcija();
friend void B::FClan();
// ...
};
427
428
koja nam daje mehanizam za odravanje skupa. Klasa SkupRijeci e samo dodati
nove funkcijske lanove za pretraivanje, sortiranje i slino. Takvo nasljeivanje e se
postii na sljedei nain:
class SkupRijeci : Lista<char *> {
// ...
};
Druga mogunost jest da se predloak klase izvodi iz neke obine klase. Na primjer,
mogue je definirati apstraktnu baznu klasu Kontejner koja e definirati opa
svojstva objekta koji sadrava druge objekte, na primjer sposobnost odreivanja broja
elemenata u kontejneru, pranjenje kontejnera i slino. Klasa Lista e zatim naslijediti
tu klasu te e definirati navedene operacije u skladu sa svojom implementacijom. No
kako klasa Lista moe sadravati objekte razliitog tipa, ona mora biti definirana
predlokom. Takvo izvoenje se moe provesti ovako:
class Kontejner {
public:
virtual int BrojElemenata()=0;
virtual void Prazni()=0;
};
template <class Tip>
class Lista : public Kontejner {
// ...
};
Nakon ovakvog nasljeivanja sve varijante klase Lista e imati po jedan podobjekt
klase Kontejner koji nije parametriziran predlokom.
Trea mogunost nasljeivanja je nasljeivanje u kojem se predloak klase izvodi
iz nekog drugog predloka. Na primjer, klasa Lista definira opa svojstva objekta koji
uva druge objekte. No moe nam zatrebati i klasa Stog koja e biti slina klasi Lista,
s tom razlikom to e se dodavanje i uklanjanje elemenata uvijek obavljati na poetku
liste. Ta klasa takoer mora biti realizirana kao predloak, jer e taj stog uvati elemente
razliitih tipova. U tom sluaju predloak klase Lista moe posluiti za izvoenje
predloka klase Stog, to e se obaviti ovako:
template <class Tip>
class Stog : public Lista<Tip> {
// ...
};
Iz svake varijante klase Lista izvodi se po jedna varijanta klase Stog. Prilikom
koritenja osnovnih klasa definiranih predlokom valja se voditi sljedeim pravilom:
ako se kao osnovna klasa koristi predloak klase, on mora imati listu parametara.
Naprotiv, izvedena klasa nikada nema listu parametara; ona se specificira koritenjem
429
Ovaj sluaj je identian prvom, s tom razlikom to se kao osnovna klasa koristi jedna
varijanta predloka.
Zadatak. Realizirajte klasu Stog pomou klase Tablica. Stog je podatkovna struktura
kod koje je lanove mogue dodavati i itati samo na vrhu stoga. Uputa: klasa Stog e
biti predloak te e nasljeivati predloak klase Tablica.
ex =
i =0
xi
x1 x 2 x 3
= 1+
+
+
+
i!
1! 2! 3!
Iako razlog za to moda nije odmah oit, funkcija je realizirana predlokom argument
predloka odreuje tip za koji se eksponencijalna funkcija rauna. Tako se jednim
potezom moe dobiti vie funkcija za raunanje eksponencijalne funkcije realnog broja,
ali i kompleksnog broja, pa ak i matrice (u naprednoj analizi mogue je definirati
eksponencijalnu funkciju za matrice, i to upravo preko gore napisanog beskonanog
reda). Kako ipak nemamo cijelu vjenost na raspolaganju da bismo pozbrajali svih
beskonano lanova, dodatni parametar e nam biti cijeli broj koji e odreivati broj
iteracija:
double faktorijel(double);
double NaPotenciju(double baza, int eksponent);
430
No prevoditelj prilikom prevoenja predloka uope ne zna za koji e tip predloak biti
iskoriten. Jedino to se zna jest da se tip mora moi inicijalizirati nulom, mora se moi
dijeliti faktorijelama te meusobno zbrajati. Takoer, mora postojati funkcija
NaPotenciju() za taj tip.
Oito se povezivanje funkcije NaPotenciju() te aritmetikih operatora mora
odgoditi do mjesta na kojemu se predloak instancira, tek kada se definira tip T. No to
je primjerice, s funkcijom faktorijel()? Mogli bismo rei da emo sve identifikatore
povezati na onom mjestu na kojemu se predloak instancira. To moe ponekad biti
pogubno, ako bismo u nastavku definirati i long varijantu funkcije faktorijel() (za
neupuene, faktorijele od realnih brojeva se definiraju preko gama-funkcija):
// Ovaj kd slijedi iza prethodne deklaracije
long NaPotenciju(int, int);
long faktorijel(int);
int main() {
double e = OpciExp(1, 20);
cout << e << endl;
// mogli bismo se iznenaditi
return 0;
}
431
432
433
pok->Sljedeci()->StaviPrethodni(pok->Prethodni());
else
rep = pok->Prethodni();
if (pok->Prethodni() != NULL)
pok->Prethodni()->StaviSljedeci(pok->Sljedeci());
else
glava = pok->Sljedeci();
delete pok;
}
template <class Tip>
Tip &Lista<Tip>::operator [](int ind) {
ElementListe *pok = glava;
ind--;
while (ind) {
pok = pok->Sljedeci();
ind--;
}
return pok->DajVrijednost();
}
434
13. Imenici
Slava i nitavilo imena.
Lord Byron (1788-1824)
U ovom poglavlju e biti objanjen vaan C++ novitet imenici (engl. namespaces).
Oni su u standard uvedeni razmjerno kasno, a slue prvenstveno kao pomo
programerima prilikom razvoja sloenih programa. Pomou njih je mogue
identifikatore pojedinih klasa, funkcija i objekata upakirati u jednu cjelinu koja se zatim
na proizvoljnom mjestu u programu moe ukljuiti i iskoristiti.
bibl2.h
void Read();
glavni.cpp
#include <bibl1.h>
#include <bibl2.h>
435
Zbog gore navedenog razloga mnoge su programerske kue nazivima klasa i funkcija u
biblioteci davali vrlo duga imena kako bi se smanjila vjerojatnost da neka druga
biblioteka koristi isto ime. No za klase koje se vrlo esto koriste nije praktino prilikom
svakog koritenja utipkavati dugako ime.
Gore opisani problem se esto u literaturi naziva zagaenjem globalnog podruja
(engl. global scope pollution). Njegovo rjeenje je u C++ jezik uvedeno u obliku
imenika, ime je uinjen kompromis izmeu oprenih zahtjeva koji se postavljaju na
podruje imena.
436
class RadiSDiskom::Podaci {
// definicija klase
};
Za razliku od ostalih elemenata C++ jezika koji ne mogu biti definirani na vie mjesta,
imenici se mogu definirati u vie navrata. Ako, primjerice, kasnije u programu
primijetimo da je imeniku RadiSDiskom potrebno dodati jo i klasu Direktorij, to se
moe uiniti tako da se jednostavno ponovi deklaracija imenika u kojoj su navedeni
novi lanovi:
namespace RadiSDiskom {
class Direktorij {
// ...
};
}
Imenici se mogu pojaviti samo u globalnom podruju imena ili unutar nekog
drugog imenika. Naziv imenika mora biti jedinstven u podruju u kojem se
pojavljuje.
Evo primjera u kojem je unutar imenika ugnijeen drugi imenik:
namespace A {
int i;
namespace B {
int j;
}
}
Objekti koji su ugnijeeni u vie imenika imaju puni naziv koji se dobije tako da se
operatorom :: razdvoje nazivi svih imenika na koje se nailazi na putu do objekta.
Mogue je definirati alternativno ime za neki imenik, tako da se iza kljune rijei
namespace navede alternativno ime, stavi znak = te se navede ime originalnog
imenika. U nastavku je deklariran imenik RSD koji je alternativno ime za imenik
RadiSDiskom:
437
Gornji oblik uvoenja alternativnog imena je vrlo koristan. Naime, ako koristimo
imenike, tada nazivi pojedinih klasa i funkcija vie ne predstavljaju problem. No i dalje
ostaje problem jedinstvenosti naziva imenika. Zbog toga e pojedini imenici opet imati
dugake nazive. Koritenje takvih naziva je vrlo naporno, no zato je mogue definirati
alternativno kratko ime za svaki pojedini imenik iz biblioteke.
Posebna vrsta imenika je bezimeni imenik (engl. nameless namespace). On se
deklarira tako da se u deklaraciji izostavi naziv:
namespace {
int i;
class X;
}
Pojedinom elementu imenika se moe pristupiti samo nakon njegove deklaracije unutar
imenika. Na primjer:
namespace A {
int i;
}
438
void f() {
A::j = 9;
}
namespace A {
int j;
}
void g() {
A::j = 10;
}
lanovima imenika bez naziva se pristupa bez eksplicitnog navoenja imenika. Kako
bezimeni imenici nemaju ime, nije mogue eksplicitno navesti lan iz takvog imenika:
int i;
namespace {
int i;
int j;
}
void f() {
j = 0;
i = 0;
//
//
//
::i = 0;// OK:
Ovakav pristup elementima imenika ima tu manu to je esto potrebno navoditi naziv
imenika. Kako bi se to izbjeglo, u C++ jezik je uvedena kljuna rije using koja
omoguava uvoenje identifikatora iz imenika u drugo podruje imena. Ona dolazi u
dvije varijante: deklaracija using i direktiva using.
439
Identifikator iz nekog imenika je mogue uvesti u neko podruje imena tako da se iza
kljune rijei using navede puni naziv identifikatora. Na primjer, ovako emo u
funkciji UcitajDatoteku() iskoristiti funkciju Citaj() iz imenika RadiSDiskom:
void UcitajDatoteku() {
using RadiSDiskom::Citaj;
char buf[50];
if (Citaj(buf, 50)) // radi neto korisno, npr. okopaj vrt
}
// sinonim za globalni i
// sinonim za globalni i
// sinonim za j iz imenika X
int main() {
using Y::j;
cout << Y::i << endl;
cout << j << endl;
return 0;
}
// ispisuje 0
// ispisuje 9
440
void Ispisi(char);
void Ispisi(char *);
void Ispisi(Kompleksni &);
}
int main() {
using Ispis::Ispisi;
Ispisi(5);
Ispisi(Kompleksni(5.0, 6.0));
return 0;
}
Pri tome vrijedi imati na umu da using deklaracija uvodi u neko podruje samo
identifikatore koji su deklarirani u imeniku do tog mjesta. Ako se u nastavku imenik
proiruje jo dodatnim preoptereenim funkcijama, one e biti ukljuene u imenik tek
nakon deklaracije:
namespace Ispis {
// isto kao i gore
}
int main() {
using Ispis::Ispisi;
Ispisi(Vektor(6., 7.)); // pogreka: Ispisi(Vektor&)
// na ovom mjestu jo nije lan
// imenika
return 0;
}
namespace Ispis {
void Ispisi(Vektor &);
441
namespace Ispis {
void Ispisi(char *s) {
cout << "char *: " << s << endl;
}
}
int main() {
Ispisi(5);
Ispisi("C++");
//
//
//
//
//
return 0;
}
public A {
B::k;
A::i;
A::j;
//
//
//
//
442
// ispisuje 10
Deklaracija using unutar klase posebno je korisna ako se prisjetimo pravila koje kae
da nasljeivanje nije isto to i preoptereenje, te da funkcijski lan nekog naziva u
izvedenoj klasi skriva istoimeni lan drukijeg potpisa u osnovnoj klasi:
class Osnovna {
public:
void func(int a) { cout << a << endl; }
};
class Izvedena : public Osnovna {
public:
void func(char *a) { cout << a << endl; }
};
int main() {
Izvedena obj;
obj.func(5);
return 0;
}
// pogreka
443
Sada bi se prethodna funkcija main() uspjeno prevela i pozvala lan iz osnovne klase.
Pri tome e funkcijski lanovi osnovne klase zaobii (engl. override) virtualne
funkcijske lanove osnovne klase. To znai da e, iako se u podruje izvedene klase
uvodi virtualan lan osnovne klase, u podruju izvedene klase vrijediti lan naveden u
toj klasi:
class B {
public:
virtual void f(int);
virtual void f(char);
void g(int);
void h(int);
};
class D : public B {
public:
using B::f;
void f(int);// OK: D::f(int) e zaobii B::f(int)
using B::g;
void g(char);
// OK: ne postoji B::g(char)
using B::h;
void h(int);// pogreka: D::h(int) je u konfliktu s
// B::h(int) jer B::h(int) nije virtualan
};
void f(D* pd) {
pd->f(1);
pd->f('a');
pd->g(1);
pd->g('a');
}
//
//
//
//
poziva
poziva
poziva
poziva
D::f(int)
B::f(char)
B::g(int)
D::g(char)
Vidjeli smo kako se pomou using deklaracije moe neki identifikator iz nekog
imenika uvesti u podruje imena. No ako neki imenik ima mnogo lanova koji se esto
ponavljaju, bit e vrlo zamorno navoditi posebice svako ime kada ga elimo koristiti.
Jednim potezom mogue je kompletan imenik uvesti u podruje imena, tako da se
nakon kljunih rijei using namespace navede naziv imenika:
void UcitajDatoteku() {
using namespace RadiSDiskom;
Datoteka dat;
// poziv klase iz imenika
char buf[50];
if (Citaj(buf, 50))
// poziv funkcije iz imenika
// ...
}
444
// OK
// pogreka: lan jo nije
// ubaen u imenik
}
namespace Ispis {
void Ispisi(Vektor &);
}
int main() {
Ispisi(Vektor(5., 6.));
return 0;
}
U gornjem primjeru, iako na mjestu direktive using imenik nije sadravao funkciju
Ispisi(Vektor &), im je funkcija dodana ona je automatski dostupna bez ponovnog
navoenja direktive using. To je zato jer direktiva using ne deklarira lanove imenika
na mjestu gdje je navedena, ve samo upuuje prevoditelja da, ako neki identifikator ne
moe raspoznati, neka dodatno pretrai i podruje navedenog imenika. Takvo ponaanje
je u suprotnosti s deklaracijom using iz prethodnog odjeljka, no razlog tome je to
deklaracija using deklarira lan, a direktiva using ne.
Direktiva using moe se navesti i u sklopu deklaracije imenika, ime se svi
identifikatori iz jednog imenika ukljuuju u drugi imenik. Pri tome valja biti oprezan, jer
ako dva imenika sadre lanove istog naziva, lanovi se nee moi jednoznano
razluiti:
namespace X {
int i = 0;
int j = 9;
}
namespace Y {
using namespace X;
445
int i = 2;
}
int main() {
using namespace Y;
cout << i << endl;
cout << j << endl;
return 0;
}
// pogreka: koji i?
// OK: ispisuje 9
U gornjem primjeru nije jasno kojem i se eli pristupiti: onom iz imenika X ili iz
imenika Y. U tom sluaju je potrebno simbol jednoznano odrediti navodei puno ime:
int main() {
using namespace Y;
cout << X::i << endl;
cout << Y::i << endl;
cout << j << endl;
return 0;
}
// ispisuje 0
// ispisuje 2
// ispisuje 9
446
14.1. to su iznimke?
Prilikom izvoenja programa esto moe doi do raznih odstupanja od predvienog
tijeka instrukcija. Na primjer, potrebno je alocirati memoriju za neko polje cijelih
brojeva koje se kasnije obrauje u matematikom dijelu programa. No prilikom pisanja
programa ne moemo sa sigurnou ustvrditi da e se dodjela memorije uvijek
uspjeno izvesti. Naime, ovisno o koliini memorije u raunalu, broju aktivnih programa
i slinih okolnosti moe se dogoditi da jednostavno dodjela nije mogua jer nema
slobodne memorije. Programer mora zbog toga provjeriti rezultat dodjele prije nego to
nastavi s izvoenjem kako bi osigurao ispravno funkcioniranje programa. U suprotnom,
program bi vjerojatno izazvao probleme u radu raunala, te moda ak i sruio
raunalo.
Nadalje, esto se mogu pojaviti problemi s neispravnim pristupom korisnikim
podacima. Na primjer, C++ jezik ne obavlja provjeru indeksa prilikom pristupa polju.
Zbog toga, e se nie navedeni kd sasvim uspjeno prevesti, ali e vrlo vjerojatno
prouzroiti probleme prilikom izvoenja:
int main() {
int mat[10];
mat[130] = 0;
447
return 0;
}
Da bismo sprijeili takve pogreke, mogue je uvesti klasu Niz i njome opisati polje
koji provodi provjeru granica:
class Niz {
private:
int duljina;
int *pokNiz;
int pogreska;
public:
Niz(int d) : duljina(d), pokNiz(new int[d]) {}
int JeLiPogreska() { return pogreska; }
int& operator[] (int indeks);
};
inline int& Niz::operator[] (int indeks) {
pogreska = 0;
if (0 <= indeks && indeks <= duljina)
return pokNiz[indeks];
// vraanje reference
else {
// Kako vratiti neku suvislu vrijednost?
pogreska = 1;
return pokNiz[0];
// Nije dobro rjeenje!
}
}
448
if (a.JeLiPogreska())
cout << "Pogreka u pristupu nizu a." << endl;
449
U ovom sluaju iznimka je tipa const char *. No esto se definira poseban korisniki
objekt koji se baca u trenutku podizanja iznimke:
class NizoveIznimke {
private:
char *pogreska;
public:
NizoveIznimke(char *np) :
pogreska(new char[strlen(np)+1]) {
strcpy(pogreska, np); }
NizoveIznimke(NizoveIznimke &ref) :
pogreska(new char[strlen(ref.pogreska)+1]) {
strcpy(pogreska, ref.pogreska); }
~NizoveIznimke() { delete [] pogreska; }
char *Pogreska() { return pogreska; }
};
450
U oba sluaja iznimka zapravo sadrava znakovni niz koji opisuje pogreku do koje je
dolo, no prvi primjer pomou zasebnog objekta ima veliku prednost. Naime, u jednom
programu mogue je koristiti mnogo razliitih objekata odjednom. Ako primjerice
istodobno koristimo i skupove, razumno je uvesti klasu Skup koja e opisivati tu
strukturu podataka. Prilikom obrade skupova takoer su mogue iznimke. Ako bi i
klasa Skup i klasa Niz podizale iznimke tipa const char *, ne bi bilo mogue
razlikovati te dvije vrste iznimaka. Naprotiv, ako za iznimke koje baca klasa Skup
uvedemo novu klasu SkupoveIznimke, mogue je zasebno napisati rutinu za obradu
jednih te rutinu za obradu drugih iznimaka.
Nakon svakog bloka pokuaja mora slijediti barem jedan blok za obradu iznimaka.
Taj blok se specificira kljunom rijei catch. Kae se da on hvata (engl. catch)
odgovarajue iznimke iz bloka pokuaja. Nakon kljune rijei catch u okruglim
zagradama navodi se tono jedan formalni parametar (isto kao prilikom deklaracije
funkcije) koji oznaava tip iznimke koji se hvata tim blokom. Nakon toga se u
vitiastim zagradama navodi tijelo rutine:
Niz a(10);
try {
a[130] = 0;
}
catch (NizoveIznimke &pogr) {
cerr << pogr.Pogreska() << endl;
}
U gornjem sluaju rutina za obradu pogreaka jednostavno hvata sve pogreke tipa
NizoveIznimke & (s time da vrijede pravila standardne konverzije!) te ispisuje opis
pogreke. Iza pojedinog bloka pokuaja mogue je navesti i vie blokova hvatanja
iznimaka. Svaki od njih hvatat e iznimke samo odreenog tipa:
451
try {
// problematian dio koda
}
catch (NizoveIznimke &pogr) {
cerr << pogr.Pogreska() << endl;
}
catch (SkupoveIznimke &pogr) {
// obrada pogreaka koje je izazvala klasa skup
}
{
<< endl;
endl;
452
void Poziv() {
Niz b(6);
try {
Niz a(11);
cout << "..." << endl;
Obracunaj(a);
cout << "..." << endl;
Obracunaj(b);
}
catch (NizovePogreske &pogr) {
cerr << pogr.Pogreska() << endl;
}
cout << "Kraj programa." << endl;
}
OK!
Gore opisani postupak za vraanje poziva funkcija unatrag te unitavanja svih lokalnih
objekata se zove odmatanje stoga (engl. stack unwinding) te funkcionira tako da
programer moe biti siguran da e se do mjesta obrade iznimke unititi svi lokalni i
privremeni objekti koji su se mogli stvoriti do mjesta na kojem je iznimka podignuta.
453
Pri tome oznaka ... u parametrima kljune rijei catch oznaava hvatanje svih
iznimaka (bit e kasnije detaljnije objanjeno).
Nakon bacanja iznimke provodi se postupak odmatanja stoga koji e unititi
sve lokalne objekte stvorene unutar pokuajnog bloka. Time sustav obrade
iznimaka osigurava integritet memorije.
454
Ispravno bi bilo zamijeniti redoslijed posljednja dva bloka tada e iznimke tipa
char * zavriti u odgovarajuem bloku, dok e sve ostale iznimke koje su tipa
pokazivaa biti svedene na void * i proslijeene drugom bloku.
Ponekad je potrebno napisati blok hvatanja koji e preuzimati sve iznimke. To se
moe uiniti tako da se kao parametar catch kljunoj rijei stavi znak ... , slino kao i
prilikom deklaracije funkcije s neodreenim parametrima. Na primjer:
try {
// ...
}
catch (...) {
// ovdje e zavriti sve iznimke
}
Kako nije dano niti ime niti tip parametru, nije mogue pristupiti vrijednosti objekta
koji je baen. Jasno je da ako postoji vie blokova za hvatanje, ovakav blok mora doi
kao posljednji. Kako u njemu zavravaju sve iznimke, eventualni naknadni blokovi
hvatanja nikada nee biti iskoriteni.
Obrada iznimke se esto mora obaviti u nekoliko koraka. Na primjer, zamislimo da
je do iznimke dolo u funkciji koja je pozvana kroz etiri prethodne funkcije. Pri tome
neka je svaka funkcija zauzela odreenu koliinu dinamiki dodijeljene memorije koju
je u sluaju iznimke potrebno osloboditi. U tom sluaju svaka funkcija mora uhvatiti
iznimku, osloboditi memoriju i proslijediti iznimku nadreenoj funkciji kako bi ona
ispravno oslobodila svoje resurse. Iako je mogue iznimku proslijediti nadreenom
bloku tako da se ponovo baci iznimka istog tipa, jednostavnije je navesti kljunu rije
throw bez parametra. Obraivana iznimka e biti ponovno baena i proslijeena
nadreenom bloku. Vano je primijetiti da je takva sintaksa doputena samo unutar
bloka hvatanja izvan njega kljuna rije throw bez parametara prouzroit e pogreku
prilikom prevoenja. Na primjer:
455
void Problematicna() {
throw 10;
}
void Func1() {
char *mem = new char[100];
try {
Problematicna();
}
catch (...) {
delete [] mem; // ienje zauzetih resursa
throw;
// prosljeivanje iznimke
}
delete [] mem;
}
void Func2() {
int *pok = new int[50];
try {
Func1();
}
catch (int) {
delete [] pok;
throw;
}
delete [] pok;
}
Potrebno je obratiti panju na razliku izmeu bloka hvatanja u funkciji Func1() i bloka
hvatanja u funkciji Func2(). U oba nije mogue pristupiti vrijednosti baenog objekta
prilikom specifikacije drugog bloka izostavljen je naziv parametra. No prvi blok
hvatanja hvata sve, a drugi samo cjelobrojne iznimke. Takoer, iako se u prvom bloku
hvatanja ne moe identificirati objekt koji je baen, mogue je proslijediti iznimku
nadreenom bloku pomou kljune rijei throw bez parametara.
Posebnu panju je potrebno posvetiti obraivanju iznimaka u sluajevima kada se
koristi dinamika dodjela memorije.
Dinamika memorija se ne oslobaa automatski nakon bacanja iznimke. To
znai da e memorija zauzeta u bloku pokuaja ostati u memoriji ako ju ne
obriemo u bloku hvatanja.
Drugim rijeima, ovakva funkcija nee ispravno raditi:
void Func1() {
char *mem = new char[100];
try {
Problematicna();
}
456
catch (...) {
throw;
}
delete [] mem;
// prosljeivanje iznimke
457
void Func1() {
PokZnak mem = new char[100];
try {
Problematicna();
}
catch (...) {
throw;
// prosljeivanje iznimke
}
}
458
void Func3() {
try {
Func2();
}
catch (Izn iznim) {
// sljedea naredba e ispisati opet 20
cout << "U Func3: " << iznim.izn << endl;
}
}
U prvom sluaju parametar bloku hvatanja je definiran kao referenca na objekt klase
Izn. To znai da e blok hvatanja baratati zapravo s baenim objektom, a ne s
njegovom kopijom. To, nadalje, znai da e svaka promjena na objektu biti upisana u
originalni objekt. Ako se taj objekt proslijedi nadreenom bloku, na njemu e biti
upisane sve izmjene.
U drugom sluaju, parametar bloku hvatanja je sam objekt. Vrijednost baenog
objekta e se prekopirati u lokalni objekt koji e se proslijediti bloku hvatanja na
obradu. Za to kopiranje koristi se konstruktor kopije klase. Kako sada blok hvatanja
barata s kopijom, promjena u vrijednosti objekta nee biti proslijeena nadreenom
bloku. Kopija e se unititi prilikom izlaska iz bloka hvatanja (pomou destruktora, ako
postoji), a proslijedit e se originalni objekt.
Kada je kao parametar bloku hvatanja navedena referenca, blok hvatanja
moe promijeniti proslijeeni mu objekt. Ako on dalje proslijedi objekt,
promjena e biti proslijeena narednim blokovima hvatanja.
Iz gore navedenog je vidljivo da deklaracija objekta kao parametra moe rezultirati
sporijom obradom iznimke (zbog toga to je potrebno kopiranje i unitavanje objekta).
Zbog toga se esto kao parametar navodi referenca na objekt, iako se ne namjerava
mijenjati sama vrijednost objekta.
459
U prvom sluaju specificirana je funkcija koja moe baciti iznimku cjelobrojnog tipa te
znakovni niz. Ako sluajno neka iznimka drukijeg tipa osim navedenih ipak promakne
obradi te bude baena iz funkcije, pozvat e se predefinirana funkcija unexpected()
koja e tipino izazvati zavretak programa. U drugom sluaju se specificira funkcija
koja ne smije baciti nikakvu iznimku.
Vano je uoiti da lista moguih iznimaka ne definira iznimke koje se smiju
pojaviti unutar funkcije, nego iznimke koje mogu biti proslijeene iz funkcije, bilo da su
baene u samoj funkciji ili su bile baene u nekoj funkciji pozvanoj iz funkcije. To znai
da e se sljedei kod ispravno izvesti:
void Func() throw(int) {
try {
// ...
throw "Iznimka";
// ...
}
catch (char *) {
throw 0;
}
}
Iako se iznimka tipa const char * pojavljuje unutar same funkcije, ona se tamo i
obrauje. Izvan funkcije se baca samo iznimka cjelobrojnog tipa, to udovoljava
specifikaciji liste moguih iznimaka.
Naposljetku, potrebno je uoiti da lista moguih iznimaka nije dio potpisa funkcije,
to jest da deklaracije
void Fn() throw();
void Fn() throw(int);
void Fn();
460
461
462
return *this;
}
ZnakovniNiz operator +(ZnakovniNiz &a, ZnakovniNiz &b) {
char *pok = new char[strlen(a.pokNiz) +
strlen(b.pokNiz) +1];
strcpy(pok, a.pokNiz);
strcat(pok, b.pokNiz);
ZnakovniNiz rez(pok);
delete [] pok;
return rez;
}
U naem sluaju, iznimka koja se podie je tipa const char *, jer nismo htjeli
komplicirati primjer uvoenjem posebne klase koja e opisivati tip iznimke. U
stvarnosti bi se taj problem rijeio uvoenjem posebne klase, primjerice,
ZnakovnePogreske, koja e signalizirati sve pogreke prouzroene akcijama u klasi
ZnakovniNiz.
Ako sada elimo sa sigurnou koristiti znakovne nizove, sve naredbe koje ih
stvaraju, prosljeuju funkcijama ili na bilo koji drugi nain njima manipuliraju moraju
biti smjetene u blok pokuaja. Blok hvatanja mora biti podeen da hvata iznimke tipa
const char *:
int main() {
try {
ZnakovniNiz ime("Salvador ");
ZnakovniNiz prezime("Dal");
ZnakovniNiz slikar;
slikar = ime + prezime;
cout << (const char *)slikar << endl;
}
catch (const char *pogr) {
cout << pogr << endl;
return 1;
}
return 0;
}
463
464
// ...
};
U gornjem primjeru klasa Linija definira liniju na zaslonu raunala. Tu klasu smo
naslijedili i iz nje izveli klasu LinijaSaStrelicama koja opisuje liniju zakljuenu
strelicama na obje strane. Svaka od tih dviju klasa ima funkcijski lan Crtaj() koji
obavlja operaciju crtanja objekta na ekranu. No taj lan je definiran kao virtualan, to
znai da se njegov poziv obavlja dinamiki analizira se tip objekta za koji se trai
poziv, te se poziva ispravan lan:
Linija *ln = new Linija;
ln->Crtaj();
// pozvat e Linija::Crtaj i iscrtat e
// obinu liniju na ekranu
delete ln;
ln = new LinijaSaStrelicama;
ln->Crtaj();
// pozvat e LinijaSaStrelicama::Crtaj
// i iscrtat e liniju sa strelicama
delete ln;
1
2
class Linija {
// ...
public:
virtual int Tip() { return CLS_LINIJA; }
// ...
};
465
466
class type_info {
public:
virtual ~type_info();
bool operator==(const type_info&) const;
bool operator!=(const type_info&) const;
bool before(const type_info&) const;
const char* name() const;
private:
type_info(const type_info&);
type_info& operator=(const type_info&);
// podatkovni lanovi
};
Naziv klase se moe dobiti pomou poziva funkcijskog lana name(). Ponekad je
potrebno leksiki usporeivati type_info objekte. Tome slui funkcijski lan
before() ako je objekt na koji pokazuje parametar leksiki ispred objekta, onda se
vraa logika istina. To moe biti korisno ako bismo podatke o tipovima smjestili u
tablicu koju, radi breg pristupa elimo sortirati i primijeniti, primjerice, binarno
pretraivanje.
Operator typeid se moe primijeniti na proizvoljan izraz. Rezultat operatora
opisuje tip koji ima rezultirajua vrijednost izraza. Takoer, mogue je operator
primijeniti i na sam identifikator tipa, te se tada vraa objekt koji opisuje navedeni tip.
Na problem crtanja samo objekata klase Linija sada bi se mogao rijeiti na ovakav
nain:
// poetak programa je identian
for (int i = 0; i < duljNiza; i++)
if (typeid(niz[i]) == typeid(Linija))
niz[i]->Crtaj();
Vie nije potreban virtualni funkcijski lan Tip koji e jednoznano opisivati tip
objekta tipovi se usporeuju direktno. Operator typeid se moe primijeniti i na
ugraene tipove. Vano je primijetiti da operator ne razlikuje konstantne i nekonstantne
tipove. Na primjer:
Linija ln1;
const Linija ln2;
typeid(ln1) == typeid(ln2);
typeid(ln1) == typeid(const Linija);
// uvijek je istina
// takoer je
// uvijek istina
467
468
velikih problema ako taj lan postoji samo u izvedenoj klasi. Na primjer, ako klasa
LinijaSaStrelicama sadri cjelobrojni lan tipStrelice koji oznaava tip
strelice, tada e sljedei programski kd prouzroiti velike probleme:
Linija *pokLinija = new Linija;
LinijaSaStrelicama *lss = (LinijaSaStrelicama *)pokLinija;
pokLinija->tipStrelice = 10;
// opasno (po ivot)!
gdje izr predstavlja izraz iju je vrijednost potrebno pretvoriti, a T predstavlja eljeni
tip. Kae se da je pretvorba dinamika zato jer se ne obavlja prilikom prevoenja, nego
prilikom izvoenja programa.
Pretvorba e se provesti samo ako je na jednoznaan nain mogue od tipa izraza
izr doi do tipa T. Ovaj operator e obaviti i obinu pretvorbu u osnovnu klasu. Ako je
T pokaziva na osnovnu klasu objekta na koji pokazuje izr, tada je konverziju mogue
provesti statiki prilikom prevoenja.
Promotrimo sluaj kada je T izvedena klasa u odnosu na klasu na koju pokazuje
izr. Napominjemo da je postupak isti bez obzira radi li se o pokazivau ili referenci na
objekt. Najprije e se odrediti tip cijelog objekta na koji izr pokazuje. Ako se za taj tip
moe pronai podobjekt T kao jednoznana javna osnovna klasa, onda e rezultat
pretvorbe biti pokaziva (ili referenca) koja pokazuje na taj podobjekt. Jednostavnije
reeno, provjerit e se je li konverzija u izvedenu klasu dozvoljena s obzirom na stvarni
objekt koji se pretvara. Nul-pokaziva e se pretvoriti u samoga sebe.
469
470
jednostavno nisu u duhu C++ jezika. Mnoge stvari za koje bi mogli doi u napast te
primijeniti dinamike pretvorbe, mogu se kvalitetnije rijeiti pomou predloaka.
Intenzivno koritenje dinamikih pretvorbi u veini sluajeva signalizira da korisnik
pokuava simulirati pristup programiranju koji se koristi u drugim objektno
orijentiranim jezicima, kao to je SmallTalk.
Objasnimo to na primjeru kontejnerskih klasa. U jezicima koji se oslanjaju na
dinamiku provjeru tipa kontejnerski objekt se najee rjeava tako da se stvori klasa
koju svi objekti koje elimo smjestiti u kontejner mora naslijediti. Na primjer, to smo i
mi uinili u poglavlju o nasljeivanju: lanove koje smo htjeli smjetati u listu morali
smo naslijediti od klase Atom. Kontejnerska klasa Lista manipulira s objektima klase
Atom, a podatak o tipu stvarnog objekta je izgubljen prilikom smjetanja objekta u
kontejner.
Kako klasa Lista ne zna nita o tipu podataka, prilikom itanja podataka iz
kontejnera potrebno je pomou operatora za dodjelu tipa pretvoriti vraeni pokaziva iz
pokazivaa Atom * u pokaziva na stvarni objekt. U poglavlju 9 o nasljeivanju jo
nismo prezentirali dinamiku dodjelu tipa, pa smo bili prisiljeni koristiti statiku dodjelu
tipa. Iz tamo navedenog primjera se vidi da je kd dosta nerazumljiv i podloan
pogrekama. Bolje je rjeenje pomou predloaka: za svaki mogui tip koji se smjeta u
kontejner generira se posebna klasa koja je u stanju vratiti ispravan pokaziva. Time se
smanjuje zamor programera te se uklanjaju mogui izvori pogreaka. Primjetite da u
SmallTalku to nije problem: tip objekta se ne provjerava prilikom prevoenja, nego
prilikom izvoenja. Ako programer pozove neku funkciju na objektu krivog tipa,
program e se jednostavno prekinuti uz poruku o pogreki. No za takav luksuz cijena je
velika: SmallTalk programi se izvode znatno sporije od C++ ekvivalenata te se mnoge
pogreke uoavaju tek kada se program izvede.
Sigurno si postavljate pitanje zato smo uope naveli takav primjer. Odgovor je u
tome to je opisani nain bio dugo vremena i jedini: mnogi prevoditelji nisu podravali
predloke te su korisnici jednostavno kopirali pristup iz drugih programskih jezika.
Moda tovani itatelj ima upravo takav prevoditelj. ak da to i nije sluaj, osobno
smatramo da je korisno upoznati razliite stilove programiranja, s njihovim prednostima
i manama. Niti jedno znanje nije suvino!
Odmah emo to i dokazati: to ako na kontejner mora uvati objekte razliitog
tipa? U tom sluaju izneseni pristup je i jedini. Zamislimo da korisnimo biblioteku
grafikih elemenata iz poglavlja 9 o nasljeivanju te elimo dodati novi objekt za
crtanje Bezierovih krivulja Bezier. Objekti tog tipa e sigurno imati sasvim razliite
lanove za postavljanje pozicije od klase Linija. Javno suelje tih klasa nikako ne
moemo objediniti virtualnim funkcijama u korijenu hijerarhije, pa da bi nam bilo
svejedno s kojim pokazivaem radimo. tovie, nekada niti ne moemo promijeniti
korijen hijerarhije, jer se nalazi u biblioteci koju ne moemo mijenjati. U tom sluaju
jedino rjeenje je nakon dobavljanja objekta iz kontejnera iskoristiti dinamiku
pretvorbu nanie te pristupiti specifinim lanovima:
Lista lst;
// lista se puni s objektima iz hijerarhije grafikih objekata
471
Ovdje vrijedi isto to smo rekli i za typeid: ako kasnije dodamo novi objekt u
hijerarhiju, morat emo promijeniti program tako da na adekvatan nain podrava nove
klase.
Jezik C++ raspolae kljunom rijei const kojom se prevoditelju moe dati na znanje
da mora osigurati nepromjenjivost nekog objekta. Ipak, ponekad se moe ukazati
potreba da se privremeno konstantnost objekta ukloni, te da mu se promijeni neki
podatkovni lan. Tada je mogue iskoristiti operator const_cast. On je opeg oblika
const_cast<T>(izr)
Pri tome izraz izr i tip T moraju biti jednaki (oba moraju biti pokazivai ili reference na
objekt), s time da se mogu razlikovati u kvalifikatoru const i volatile. U tom sluaju
e se pretvorba provesti te e se novonastali objekt tretirati kao da mu je uklonjen (ili
dodan) const odnosno volatile kvalifikator. Na primjer:
const Vektor v(0, 50);
const_cast<Vektor&>(v).PostaviXY(10, 20);
472
static_cast<T>(izr)
reinterpret_cast<T>(izr)
Tip T oznaava ciljni tip dodjele te mora biti pokaziva, referenca, aritmetiki tip ili
pobrojenje. Za veinu tipova gornji operatori e dati isti rezultat kao i obian operator
dodjele tipa
(T)izr
Pretvorba e se obaviti na isti nain. Postavlja se pitanje emu su onda uope uvedeni
ovi novi operatori, te u emu je njihova razlika.
Osnovna razlika je nain na koji se tretiraju klase prilikom nasljeivanja. Naime, tip
T kod operatora static_cast mora u potpunosti biti poznat prilikom prevoenja. To
znai da klasa u koju se pretvara mora biti u cijelosti definirana (a ne samo deklarirana
unaprijed). Pogledajmo to se dogaa prilikom koritenja tog operatora i viestrukog
nasljeivanja:
class Osnovna1 {
public:
int osn1;
};
class Osnovna2 {
public:
int osn2;
};
class Izvedena : public Osnovna1, public Osnovna2 {
public:
int izv;
};
int main() {
Izvedena i;
i.osn1 = i.osn2 = i.izv = 0;
Osnovna2 *pok = static_cast<Osnovna2 *>(&i);
pok->osn2 = 30;
cout << i.osn1 << endl << i.osn2 << endl;
return 0;
}
473
osn1
osn2
izv
&i
pok
474
#if
#ifdef
#ifndef
#elif
#else
#endif
#include
#define
#undef
#line
#error
#pragma
475
// iz tekueg imenika
// iz standardnog imenika
Postoji sutinska razlika izmeu ova dva naina omeivanja imena datoteke. Kada je
ime datoteke navedeno unutar znakova < i >, ona se trai u standardnom imeniku za
476
postaviti vrijednost prvog niza (makro ime NEKAJ) na vrijednost navedenu u nastavku
naredbe (NETO DRUGO UMJESTO NEKAJ). Naie li pretprocesor na simbol NEKAJ u
djelu programa koji slijedi, svaki put e
ga zamijeniti nizom
NETO DRUGO UMJESTO NEKAJ.
Iako nije nuno, uobiajena je programerska praksa da se, zbog lake
uoljivosti u izvornom kdu, makro imena piu velikim slovima.
Radi boljeg razumijevanja prouit emo kako bi gornja naredba #define djelovala na
nekoliko razliitih pojava niza NEKAJ (bez obzira na smisao konanog kda).
Pretpostavimo da gornjoj naredbi slijede naredbe:
a = NEKAJ;
char *b = "NEKAJ";
c = PONEKAJ;
// nadomjestit e
// nee nadomjestiti
// nee nadomjestiti
477
to neete moi napraviti tako da napiete PI, jer on ne postoji kao podatak u programu.
Umjesto toga morat ete napisati broj 3.14159. A tko vam pritom jami da je PI na tom
mjestu zaista nadomjeten tim brojem? Ne postoji nain da to izravno provjerite, osim
posredno, preko tonosti konanog rezultata.
U naredbi #define mogue je navesti samo makro ime u tom sluaju se ono
definira na neku neodreenu vrijednost:
#define SOLO
478
// Dobar dan!
// Hvala, tebi...
<< endl;
_ _TIME_ _
_ _STDC_ _
_ _cplusplus
Meutim, veina prevoditelja definira jo i neka dodatna imena, iji se opis moe nai
u pripadajuim uputama.
479
Vrijednosti svih makro imena ostaju nepromijenjena kroz cijelu datoteku izvornog
kda, s izuzetkom _ _LINE_ _ i _ _FILE_ _ . Vrijednost makro imena _ _LINE_ _ se
automatski mijenja tijekom obrade izvornog kda, ali se moe promijeniti i
pretprocesorskom naredbom #line (vidi odjeljak 16.5). Vrijednost makro imena
_ _FILE_ _ se takoer moe promijeniti naredbom #line.
Rezervirana makro imena prvenstveno se koriste tijekom razvoja programa, za
otkrivanje i lociranje pogreaka u kdu. elimo li da se prilikom izvoenja izvedbenog
kda nastalog prevoenjem neke naredbe ispie redni broj retka te naredbe u datoteci
izvornog kda, ime datoteke izvornog kda, te datum i vrijeme prevoenja (koliko god
to bilo besmisleno), iza te naredbe dodat emo naredbu:
cout <<
<<
<<
<<
// izvorni kd
// nakon pretprocesora
Meutim, makro funkcije znaju vrlo malo o sintaksi C++ jezika, ve vrte svoj film.
Stoga e sljedei poziv makro funkcije:
d = KVADRAT(++i);
pretprocesor razviti u kd
// neto tu zaudara...
480
d = ((++i) * (++i));
// nakon pretprocesora
Kao to vidimo, argument e se uveati dva puta, a rezultat pouzdano nee biti kvadrat
(ak ni tako uveanog) broja. Konani ishod e se oito razlikovati od onoga to bi
naivni korisnik u prvi mah oekivao.
itatelj je zacijelo primijetio mnotvo zagrada u definiciji makro funkcije. One su
neophodne da bi se sauvala hijerarhija operacija u sluaju da se kao argument makro
funkcije navede neki sloeni izraz. Da smo izostavili zagrade u definiciji, na primjer:
#define KVADRAT_UPITNI(x) x * x
U ovako razvijenom kdu, zbog razliitog prioriteta operacija, rezultat nee odgovarati
oekivanom.
Kao to vidimo, makro funkcije nemaju uvida u sintaksu jezika C++, pa tako ne
znaju nita o pravilima konverzije tipova, dozvoljenim operatorima, hijerarhiji
operacija. Budui da prevoditelj ne obrauje izvornu datoteku, ve datoteku koju je
generirao pretprocesor (a korisnik u normalnim okolnostima nema uvid u tu
meudatoteku), vrlo je teko ui u trag pogrekama koje nastaju zbog nepravilno
definiranih makro funkcija. Uostalom, programski jezik C++ prua pogodnosti poput
predloaka funkcija, preoptereenja funkcija i umetnutih funkcija, koje ine makro
funkcije potpuno nepotrebnima. Primjena makro funkcija, naprotiv, jest opravdana za
traenje pogreaka u programu, o emu e biti rijei kasnije u ovom poglavlju.
U C++ programima valja izbjegavati koritenje makro funkcija. Primjena
makro funkcija odraava nedosljednost programskog jezika, programa ili
programera [Stroustrup91].
Pri pisanju makro funkcija ili makro definicija, korisniku stoje na raspolaganju dva
operatora koji omoguavaju rukovanje simbolikim imenima (engl. token) iz programa:
operator # za pretvorbu simbolikog imena u znakovni niz, te operator ## za stapanje
simbolikih imena.
Operatorom # je mogue simboliko ime pretvoriti u znakovni niz. Ilustrirajmo to
sljedeim primjerom: prilikom razvoja programa elimo provjeriti vrijednosti pojedinih
481
na svakom eljenom mjestu u kdu. Naravno, gore navedeni naziv varijable varijabla
je proizvoljan i njega treba varirati shodno imenu varijable koju elimo ispisati.
Postupak moemo djelomino pojednostavniti tako da definiramo makro funkciju
koja e kao argument prihvaati ime varijable koju elimo ispisati:
#define PROVJERA(varijabla) \
cout << #varijabla " = " << varijabla << endl
poziv u naredbi
int NESTO_SASVIM_NOVO(x, 6);
482
#endif naredbom. Unutar tog bloka moe postojati vie neovisnih grana, odvojenih s
jednom ili vie naredbi #elif ili (maksimalno) jednom naredbom #else. Kao to
vidimo, blokovi za uvjetno ukljuivanje kda podsjeaju strukturom na if-blokove u
Svaki puta kada elimo generirati inaicu programa za drugi jezik, dovoljno je
promijeniti definiciju niza JEZIK i ponovo prevesti i povezati program prevoditelj e
sam ukljuiti poruke na odgovarajuem jeziku.
Kada ne bismo koristili gornji pristup, trebali bismo generirati zasebne datoteke
izvornog kda za svaki jezik ili bismo, prilikom promjene jezika, trebali prepisivati
sadraje svih znakovnih nizova za taj jezik. Osim toga je i proirenje za nove jezike
(posebice ako se tekstovi svih poruka grupiraju) vrlo jednostavno.
U ispitivanjima uvjeta mogu se koristiti svi logiki i poredbeni operatori koji su
dozvoljeni i u jeziku C++ (!, &&, ||, ==, <, >, !=).
Pretprocesorskom naredbom #ifdef ispituje se je li neko makro ime definirano.
Ako jest, naredbe koje slijede se prevode; u protivnom se kd do pripadajue #elif,
#else ili #endif naredbe ne ukljuuje u prevoenje.
Pretpostavimo da razvijamo neki program za koji elimo prirediti i pokaznu (demo)
inaicu, koja e se od radne razlikovati po tome to nee omoguavati ispis rezultata na
pisa. Pomou pretprocesorskih naredbi moemo to rijeiti na sljedei nain:
#define DEMO
//...
#ifdef DEMO
cout << "Ovo je pokazna inaica mog programa za rjeavanje "
"Besselove diferencijalne jednadbe koja ne "
483
484
gdje je poruka tekst koji elimo ispisati. Ona e prilikom prevoenja prouzroiti ispis
poruke oblika:
Error: ImeDatoteke BrLinije : Error directive: poruka
Ova naredba se uglavnom koristi za prekid prevoenja ako se u nekom ispitivanju utvrdi
da nisu zadovoljeni svi neophodni uvjeti. Naredbu treba u tom sluaju umetnuti unutar
bloka za uvjetno prevoenje koji ispituje traene uvjete.
Za ilustraciju, osvrnimo se na primjer naeg multi-jezinog programa na stranici
482. elimo li se osigurati da makro ime JEZIK uvijek bude definirano, ubacit emo
sljedee naredbe:
#ifndef JEZIK
#error Makro ime JEZIK mora biti definirano.
#endif
485
486
Gotovo svaki ozbiljniji program se sastoji od nekoliko tisua ili desetaka tisua
redaka izvornog kda. Zbog sloenosti i duljine, takve je programe neophodno pisati u
odvojenim modulima. Pojedini modul se moe razvijati nezavisno, pri emu se
programer koncentrira samo na problem koji taj modul rjeava, a ne brine o sloenosti
cijelog sustava.
Takoer, takve programe nerijetko pie vie programera ili ak cijeli timovi
istovremeno, pa je i zbog efikasnosti posla neophodno rascjepkati kd. U ovom
poglavlju bit e dani upute i pravila kako dobro organizirati izvorni kd u sluaju da ga
je potrebno rasporediti u nekoliko datoteka. Takoer e biti govora o povezivanju kda
pisanog u C++ jeziku s programskim kdom u drugim jezicima.
487
objektnom kdu. Ako je broj tih naziva prevelik, memorijski prostor namijenjen za
njihovo pohranjivanje e se prepuniti, to e onemoguiti daljnje prevoenje kda.
Istina, veliina tog prostora moe se poveati, ali to rjeenje nije vjeno.
Trei argument za rasporeivanje izvornog kda u vie datoteka jest preglednost.
Navedu li se definicije svih klasa u jednoj datoteci kd e biti nepregledan puno je
praktinije ako se definicije pojedinih klasa strpaju u zasebne datoteke. Time ete si
kasnije znaajno olakati moebitno ukljuivanje pojedinih klasa u neki drugi program.
etvrti argument je vezan uz timski rad: ako program ne pie jedan programer ve
itava ekipa, tada je daleko praktinije ako svaki programer pie kd u vlastitoj datoteci.
Uostalom, i programeri znaju biti sitne due koje ne vole da im se brlja po njihovim
umotvorinama.
Poneki itatelj e kao argument protiv navesti injenicu da je daleko tee
kontrolirati vei broj datoteka. Sreom, svi dananji prevoditelji/povezivai
omoguavaju objedinjavanje vie datoteka u cjeline projekte. Takvi projekti
dozvoljavaju ak da su datoteke izvornog kda razbacane na vie mjesta (u razliitim
imenicima, pa i na razliitim diskovima).
Oito je da e prije ili kasnije svakom programeru datotena koa postati
pretijesna i da e, unato svim pokuajima odgaanja, u sudbonosnom trenutku u
afektu otvoriti novu datoteku izvornog kda. Kako se datoteke izvornog koda prevode
zasebno, svakoj datoteci moraju biti dostupne deklaracije objekata i funkcija koje se u
njoj koriste. Sutinski gledano, izvorni kd mora biti jednako konzistentan kao kada je
cijeli program napisan u jednoj datoteci. Ako se ta konzistentnost izgubi, poveziva e
prijaviti pogreku prilikom povezivanja.
Budui da postupak naknadnog razbijanja kda moe biti vrlo mukotrpan, od
presudnog je znaaja pravovremeno sagledati kompleksnost nekog problema i od
poetka krenuti s pravilnim pristupom.
17.2. Povezivanje
U poglavlju 1 opisano je kako se prevoenje izvornog kda u izvedbeni sastoji od dva
koraka: prevoenja i povezivanja. U prvi mah poetniku nije jasna razlika izmeu ta dva
koraka. tovie, za programe iji je izvorni kd u cijelosti smjeten u jednu datoteku
nema svrhe razluivati ta dva koraka slika postaje jasnija u sluaju kada je program
razbijen u vie modula.
Tijekom prevoenja provodi se sintaksna i semantika provjera, pri emu se meu
ostalim provjerava odgovaraju li pozivi funkcija njihovim deklaracijama. Budui da se
prevoenje provodi za svaki imbeni modul zasebno, svaki od njih mora sadravati
deklaracije funkcija koje se unutar tog modula pozivaju. Na primjer, u datoteci
poziv.cpp poziva se funkcija cijaFunk(), koja je definirana u datoteci
definic.cpp. Da bi prevoditelj mogao pravilno prevesti kd datoteke poziv.cpp,
njemu prije poziva treba na neki nain predoiti deklaraciju te funkcije:
488
poziv.cpp
void cijaFunk(int dajStoDas);
// ...
cijaFunk(0);
// deklaracija
// poziv
// definicija
// promijenjena definicija
Sada se deklaracija i definicija funkcije meusobno slau u potpisu (tj. broju i tipovima
argumenata funkcije), ali se razlikuju u tipu povratne vrijednosti. Poziv funkcije u
datoteci poziv.cpp bit e preveden u skladu s deklaracijom funkcije u toj datoteci, tj.
kao void cijaFunk(int), dok e definicija funkcije u datoteci definic.cpp biti
prevedena kao int cijaFunk(int). Funkcije su jednake po potpisu i poveziva nee
uoiti da se deklaracija i definicija funkcije razlikuju po tipu povratne vrijednosti.
Posljedica toga e biti pogreka u izvoenju programa, pri pokuaju povratka iz
Moete varati sve ljude neko vrijeme, moete ak varati neke ljude cijelo vrijeme, ali ne
moete varati sve ljude cijelo vrijeme Abraham Lincoln (1809-1865)
489
funkcije. Naime, generirani kd funkcije e pri izlasku iz nje na stog staviti cjelobrojni
povratni broj kojeg, meutim, pozivajui kd nee pokupiti budui da on za dotinu
funkcija oekuje da je tipa void.
Ako deklaracija funkcije u jednoj datoteci i definicija istoimene funkcije u
drugoj datoteci imaju jednake potpise, poveziva e shvatiti da se radi o
istoj funkciji. Ako su se one razlikuju po tipu povratnih vrijednosti, nastupit
e pogreka pri izvoenju.
Iz dosadanjih razmatranja moemo naslutiti da je za pravilnu organizaciju kda
neophodno razluiti doseg imena pojedinih identifikatora. Ve smo nekoliko puta
naglaavali da su objekti deklarirani unutar blokova vidljivi samo unutar tog bloka. To
se odnosi kako na objekte deklarirane unutar funkcija, tako i na ugnijeene klase.
Identifikatori koji su deklarirani izvan funkcija i klasa uglavnom su (uz asne izuzetke
koje emo navesti) vidljivi u cijelom kdu, bez obzira to se taj kd moe rasprostirati
kroz vie datoteka. Za identifikatore (tj. objekte i funkcije) koji su prilikom povezivanja
vidljivi i u drugim modulima kae se da imaju vanjsko povezivanje (engl. external
linkage). Zbog toga u cijelom programu smije postojati samo jedan objekt, tip ili
funkcija s tim imenom.
S druge strane, postoje objekti i funkcije koji su vidljivi samo unutar datoteke. Za
njih se kae da imaju unutarnje povezivanje (engl. internal linkage). Globalne umetnute
(inline) funkcije i simbolike konstante imaju unutarnje povezivanje. Zato e
umetnuta funkcija svojaUsvojoj() i simbolika konstanta lokalnaKonstanta u
kdu
prima.cpp
inline void svojaUsvojoj() {
// tijelo funkcije
}
const int lokalnaKonstanta = 23;
490
// funkcija s vanjskim
// povezivanjem
// ...
}
double lokalnaKonstanta = 210.; // varijabla s vanjskim pov.
druga.cpp
// donja deklaracija moe miroljubivo i aktivno
// koegzistirati s onom u datoteci "prva.cpp":
typedef double Tip;
// ...
491
void funkcija() {
class LokalnaKlasa {
//...
};
}
LokalnaKlasa classaLocale;
// pogreka
Dodavanjem kljune rijei static ispred deklaracije objekta, funkcije ili anonimne
unije, eksplicitno se pridjeljuje interno povezivanje. To omoguava da se u pojedinim
modulima definiraju razliiti globalni objekti ili funkcije s jednakim imenima, a da zbog
toga ne doe do sukoba imena prilikom povezivanja.
U sljedeem primjeru e navedene objektne datoteke nakon povezivanja imati
svaka svoje inaice funkcije f(), varijable broj i anonimne unije:
suveren1.cpp
static int f(int argument) {
//...
}
static float broj;
static union {
int br;
char ch;
};
suveren2.cpp
static void f() {
//...
}
static char broj;
static union {
int br;
char ch;
};
492
// definicija
svecnst2.cpp
// samo deklaracija definicija je negdje drugdje:
extern const float pi;
cout << pi << endl;
// ispisuje 3.14159...
// definicija funkcije
bastian.cpp
extern int bwv;
extern int toccata();
// samo deklaracija
// takoer
float fuga() {
493
bwv = toccata();
}
e postati definicijom globalne varijable bwv. Poput svih globalnih i statikih objekata,
budui da nema eksplicitno pridruene vrijednosti, varijabla bwv e se inicijalizirati na
podrazumijevanu vrijednost 0. Kd u datoteci bastian.cpp e biti preveden bez
pogreke, ali e prilikom povezivanja biti dojavljena pogreka da u programu postoji
jo jedan objekt s istim imenom, onaj definiran u datoteci johann.cpp.
Naglasimo da sama kljuna rije extern nije dovoljni jamac da bi se naredba
interpretirala kao deklaracija. Naime, ako unato kljunoj rijei extern, varijabli
pridruimo vrijednost:
extern int bwv = 538;
494
modul_2.cpp
int crniVrag;
extern double zeleniVrag;
extern float zutiVrag;
495
pripadajue definicije, ali se imenu datoteka zaglavlja umjesto nastavaka .cpp, .c ili
.cp dodaje nastavak .h (ponekad .hpp).
to valja staviti u datoteke zaglavlja? Budui da su objekti i funkcije s vanjskim
povezivanjem vidljivi u svim modulima, ako elimo omoguiti pravilno prevoenje
pojedinih modula, oito je neophodno navesti deklaracije svih objekata i funkcija koji e
biti koriteni izvan modula u kojem su definirani. Kako esto ne moemo unaprijed
znati hoe li neka funkcija ili objekt zatrebati u nekom drugom modulu, nije na odmet u
datotekama zaglavlja navesti deklaracije svih (nestatikih) globalnih funkcija i objekata.
Osim toga, navoenjem svih deklaracija u datoteci zaglavlja kd e biti pregledniji, jer
e sve deklaracije (osim za statike objekte i funkcije) biti na jednom mjestu.
Vjerujemo da je svakom jasno da u datoteke zaglavlja takoer treba staviti
definicije objekata i funkcija s unutarnjim povezivanjem (inline funkcije i simbolike
konstante), elimo li da oni budu dohvatljivi i iz drugih datoteka. Isto tako, u
datotekama zaglavlja treba navesti definicije tipova (deklaracije klasa) i predloke.
Radi preglednosti, nabrojimo po tokama glavne kandidate za navoenje u
datotekama zaglavlja:
**********************************************************/
496
// deklaracija
Deklaracije funkcija:
extern void ReciMiReci(const char *poruka);
Pobrojenja:
enum neprijatelj { unutarnji, vanjski, VanOvan, Feral };
497
Gore navedene toke ine samo prijedlog one nisu odreene pravilima jezika C++, no
do gornjih pravila su doli programeri tokom dugih zimskih veeri provedenih uz
raunalo. Iako u deklaracijama klasa i funkcija u datotekama zaglavlja nije neophodno
navoditi imena argumenata (dovoljno je navesti tipove), zbog preglednosti i
razumljivosti deklaracija one se stavljaju. Neki programeri preferiraju da se u deklaraciji
navede dulje ime da bi se lake shvatilo znaenje pojedinog argumenta, dok u definiciji
koriste skraena imena da bi si skratili muke utipkavanja prilikom pisanja tijela
funkcije.
Sada nakon to znamo to se obino stavlja u datoteku zaglavlja, navedimo i to ne
valja tamo stavljati :
Definicije funkcija:
void IspisStraga(const char *tekst) {
int i = strlen(tekst);
while (i-- > 0) cout << *(tekst + i);
cout << endl;
}
Definicije objekata:
int a;
Ako u datoteku zaglavlja stavimo definiciju objekta, onda emo u svakoj datoteci u
koju ukljuimo datoteku zaglavlja dobiti po jedan globalni simbol navedenog imena
(u gornjem primjeru to je varijabla i). Takav program e prouzroiti pogreke
prilikom prevoenja.
498
499
Prec DajIm();
// kako bi se pokazala organizacija
void PostaviReIm(Prec, Prec);
};
template <class Prec>
ostream &operator <<(ostream &, Kompleksni<Prec> &);
kompl.cpp
#include <iostream.h>
#include "kompl.h"
template <class Prec>
Prec Kompleksni<Prec>::DajRe() {
return re;
}
template <class Prec>
Prec Kompleksni<Prec>::DajIm() {
return im;
}
template <class Prec>
void Kompleksni<Prec>::PostaviReIm(Prec r, Prec i) {
re = r; im = i;
}
template <class Prec>
ostream &operator <<(ostream &os, Kompleksni<Prec> &c) {
os << c.DajRe();
if (c.DajIm() >= 0) os << "+";
os << c.DajIm() << "i";
return os;
}
// sada idu forsirane instance klase i operatora:
static NekoristenaFunkcija() {
Kompleksni<double> c1;
Kompleksni<int> c2;
cout << c1 << c2;
}
500
pozovi.cpp
#include <iostream.h>
#include "kompl.h"
int main() {
Kompleksni<double> c1(5.0, 9.0);
Kompleksni<int> c2(4, 3);
cout << "Prvi broj je: " << c1 << ", a drugi je " << c2;
}
class Kompleksni<double>;
class Kompleksni<int>;
ostream &operator <<(ostream &,Kompleksni<double>);
ostream &operator <<(ostream &,Kompleksni<int>);
501
kompldbl.cpp
#include <iostream.h>
#include "kompl.h"
#include "kompldef.h"
komplint.cpp
#include <iostream.h>
#include "kompl.h"
#include "kompldef.h"
class Tocka {
public:
Tocka(double x = 0, double y = 0);
double x() const { return x_koord; }
double y() const { return y_koord; }
502
tocka.cpp
#include "tocka.h"
Tocka::Tocka(double x, double y) {
x_koord = x;
y_koord = y;
}
503
tocka.h
pravac.h
kruznica.h
#include "tocka.h"
// deklaracija
// klase Tocka
// deklaracija
// klase Pravac
tocka.cpp
#include "tocka.h"
#include "pravac.h"
// deklaracija i
// definicija
// klase Kruznica
pravac.cpp
#include "tocka.h"
#include "pravac.h"
// definicije
// lanova klase
// Tocka
// definicije
// lanova klase
// Pravac
presjek.h
poglavar.cpp
#include
#include
#include
#include
"tocka.h"
"pravac.h"
"kruznica.h"
"presjek.h"
int main() {
//...
}
#include "pravac.h"
#include "kruznica.h"
// deklaracije
// funkcija iz
// presjek.cpp
presjek.cpp
#include "presjek.h"
// definicije
// funkcija
504
#include "tocka.h"
class Pravac {
public:
Pravac(Tocka t, double dx,
double ka() const { return
double kb() const { return
double kc() const { return
private:
double a;
double b;
double c;
};
double dy);
a; }
b; }
c; }
#endif
pravac.cpp
#include "pravac.h"
Pravac::Pravac(Tocka t, double dx, double dy) {
a = dy;
b = -dx;
c = dx * t.y() - dy * t.x();
}
Klasa Kruznica opisuje krunicu pomou sredita krunice i njenog polumjera. Klasa
sadri konstruktor, koji inicijalizira sredite i polumjer, te lanove za dohvaanje
koordinata sredita i polumjera. Kako je klasa Kruznica jednostavna te su se svi
lanovi mogli ostvariti umetnutima, cijela je smjetena u datoteku kruznica.h
uope nema potrebe za posebnom datotekom definicije.
kruznica.h
#ifndef KRUZNICA_H
#define KRUZNICA_H
#include "tocka.h"
#include "pravac.h"
class Kruznica {
public:
Kruznica(Tocka s, double r) : srediste(s), polumjer(r) {}
double r() const { return polumjer; }
double x0() const { return srediste.x(); }
double y0() const { return srediste.y(); }
private:
Tocka srediste;
505
double polumjer;
};
#endif
506
double x1 = -B / 2. / A;
double y1 = -a_b * x1 - c_b;
t1.pomakni(x1, y1);
// obje tocke jednake
t2.pomakni(x1, y1);
return PravacDiraKruznicu;
}
double x1 = (-B + sqrt(diskr)) / 2. / A;
double y1 = -a_b * x1 - c_b;
t1.pomakni(x1, y1);
x1 = (-B - sqrt(diskr)) / 2. / A;
y1 = -a_b * x1 - c_b;
t2.pomakni(x1, y1);
return DvaPresjecista;
}
else {
// kd za vertikalni pravac ete,
// naravno, napisati sami!
}
}
<iostream.h>
"tocka.h"
"pravac.h"
"kruznica.h"
"presjek.h"
507
}
}
int main() {
Tocka t0(1., 1.);
double r = 5.;
Kruznica k(t0, r);
Pravac p(t0, 1., 1.);
//
//
//
//
//
postavljamo toku
polumjer krunice
definiramo krunicu
pravac kroz sredite krunice
pod 45 stupnjeva
PozoviPaIspisi(k, p);
return 0;
}
508
tocka.h
pravac.h
#include "tocka.h"
// deklaracija
// klase Tocka
// deklaracija
// klase Pravac
poglavar.cpp
#include "tocka.h"
#include "pravac.h"
//...
kojom se makro ime TOCKA_H definira (na neku neodreenu vrijednost). Zbog toga e
kod sljedeeg ukljuivanja datoteke tocka.h (na primjer, prilikom ukljuivanja
datoteke pravac.h) makro ime TOCKA_H biti definirano pa e se ponovno prevoenje
deklaracije klase Tocka.
Makro ime moe biti proizvoljno, ali je uobiajeno da se uskladi s imenom
datoteke, osim to se toka ispred nastavka .h ili .hpp zamijeni znakom
podcrtavanja.
Time se znaajno smanjuje mogunost da se neko ime upotrebi u razliitim datotekama
zaglavlja.
Postupak izluivanja datoteka zaglavlja esto zna za poetnika biti mukotrpan
posao. Standard jezika ne specificira niti jedno pravilo glede organizacije kda koje bi
prisiljavalo programera da ga se dri. Stoga taj postupak iziskuje dosta discipline i sva
odgovornost lei na programeru; za one manje marljive, u [Horstmann96] dan je kd
programa za automatsko generiranje datoteka zaglavlja, pa ko voli, nek izvoli.
509
(osim naravno kineskog, kojeg govori treina ovjeanstva). Kako je jezik C++
nadogradnja jezika C (C++: As close as possible to C but no closer), prevoenje
izvornog kda pisanog u C-u na C++ prevoditelju ne bi trebala predstavljati vei
problem. Primijetite naglasak na ne bi trebala: stopostotna kompatibilnost s C-om nije
ostvarena, te su oko toga bile voene une polemike. Na kraju, utvreno je da se ipak
dio kompatibilnosti mora rtvovati kako bi se ostvarile nove mogunosti.
Dio nekompatibilnosti je uzrokovan time to C++ ima nove kljune rijei, iji se
nazivi ne smiju koristiti za nazive identifikatora. Ako C program deklarira, primjerice,
cjelobrojnu varijablu naziva class, takav program se nee moi prevesti C++
prevoditeljem. Nadalje, podruja pojedinih imena se neznatno razlikuju u jezicima C i
C++. Na primjer, u jeziku C se ugnijeene strukture mogu koristiti u globalnom
podruju bez navoenja imena podruja u koje je struktura ugnijeena. Zbog takvih i
slinih primjera, ponekad moe biti jednostavnije ne prevoditi C kd C++
prevoditeljem, nego jednostavno C kd prevesti C prevoditeljem i povezati ga s C++
kdom. Potekoe takoer nastaju kada izvorni kd u C-u nije dostupan, pa ga se ne
moe ponovo prevesti C++ prevoditeljem.
Povezivanje objektnih kdova pisanih u razliitim jezicima nije trivijalno. Naime,
objektni kdovi dobiveni razliitim prevoditeljima, pa ak i objektni kdovi dobiveni
prevoenjem istog jezika, ali na prevoditeljima raznih proizvoaa, meusobno nisu
kompatibilni. Primjerice, redoslijed prenoenja argumenata funkciji ili struktura
pohranjivanja pojedinih tipova podataka se razlikuju za C i Pascal. Standard jezika C++
podrava povezivanje samo s jezikom C, ali prevoditelji pojedinih proizvoaa esto
podravaju povezivanje s drugim jezicima za to morate pogledati upute za
prevoditelj/poveziva koji koristite.
17.6.1. Poziv C funkcija iz C++ kda
Standardom je definiran nain povezivanja C++ i C kda. Jednostavna deklaracija Cfunkcije specifikatorom extern nee puno pomoi, jer se njome samo naznauje da je
funkcija definirana izvan datoteke izvornog kda, ali se podrazumijeva da ta funkcija
odgovara specifikacijama jezika C++. Funkcije u jeziku C++ razlikuju se od C-funkcija
po mnogim svojstvima, a jedno izmeu njih je preoptereenje imena funkcija. Prilikom
prevoenja kda, C++ prevoditelj e pohraniti imena funkcija u objektni kd prema
posebnim pravilima, da bi mogao pratiti njihovo preoptereenje. Taj postupak se naziva
kvarenje imena (engl. name mangling). Da bi poveziva mogao razlikovati razliite
verzije funkcija za razliite parametre, prevoditelj e u objektni kd na naziv funkcije
nalijepiti jo dodatne znakove kojima e oznaiti parametre koje ta funkcije uzima.
Nain na koji to pojedini prevoditelj radi se razlikuje ovisno o proizvoau i nije
definiran standardom.
eli li se u C++ kd ukljuiti C-funkciju, iza kljune rijei extern treba navesti
"C" tip povezivanja, ime se prevoditelju daje na znanje da za tu funkciju treba
obustaviti kvarenje imena:
C++: Blizu jeziku C koliko je god mogue, ali nita blie naslov lanka A. Koeniga i
B. Stroustrupa u asopisu C++ Report.
510
// standardne C-funkcije
// za generiranje
// sluajnih brojeva
511
512
513
Svaki program ima smisla samo ako moe komunicirati s vanjskim svijetom nas ne
zanimaju programi koji hiberniraju sami za sebe (poput Mog prvog C++ programa). U
najjednostavnijem sluaju elimo da program koji pokreemo barem ispie neku poruku
na zaslonu raunala (Dobar dan, gazda. Drago mi je da Vas opet vidim. Kako Vai
bubreni kamenii?).
Ulazni i izlazni tokovi (engl. input and output streams) osiguravaju vezu izmeu
naeg programa i vanjskih jedinica na raunalu: tipkovnice, zaslona, disketne jedinice,
diska, CD-jedinice. U ovom poglavlju upoznat emo s osnovama tokova: upoznat emo
se s upisom podataka preko tipkovnice, ispisom na zaslonu raunala te s itanjem i
pisanjem datoteke.
18.1. to su tokovi
U mnogim dosadanjim primjerima smo u kd ukljuivali iostream.h datoteku
zaglavlja. U njoj su deklarirani ulazni i izlazni tokovi koji su nam omoguavali upis
podataka s tipkovnice ili ispis na zaslonu raunala. Meutim, do sada nismo ulazili u
sutinu ulazno-izlaznih tokova, budui da nam njihovo poznavanje nije bilo neophodno
za razumijevanje jezika.
Sam programski jezik C++ ne definira naredbe za upis i ispis podataka. Naime,
ulazno-izlazne naredbe u pojedinim jezicima podravaju samo ugraene tipove
podataka. Meutim, svaki sloeniji C++ program sadri i sloenije, korisniki definirane
podatke, koji iziskuju njima svojstvenu obradu. Da bi se osigurala fleksibilnost jezika,
izbjegnuta je ugradnja ulazno-izlaznih naredbi u jezik.
Za ostvarenje komunikacije programa s okolinom, programer mora na raspolaganju
imati rutine koje e podatke svakog pojedinog tipa pretvoriti u raunalu u nizove bitova
te ih poslati na vanjsku jedinicu, odnosno obrnuto, nizove bitova s vanjske jedinice
pretvoriti u podatke u raunalu. U jeziku C++ to se ostvaruje pomou ulaznih i izlaznih
tokova. Tokovi su zapravo klase definirane u standardnim bibliotekama koje se
isporuuju s prevoditeljem. Sadraj tih klasa (svi funkcijski i podatkovni lanovi) je
standardiziran i omoguava gotovo sve osnovne ulazno-izlazne operacije. Osim toga,
zahvaljujui svojstvima jezika C++, taj se set operacija po potrebi moe proirivati.
Sam koncept tokova se nadovezuje na princip enkapsulacije nain ispisa podataka
je neovisan o tipu jedinice. Tako e se pojedini podatak na gotovo isti nain ispisati na
514
zaslon ili na disk (slika 18.1). Takoer, unos podataka je praktiki istovjetan radi li se o
unosu s tipkovnice ili diska. Nakon to se stvori odreeni tok, program e komunicirati
preko tokova, njima ostavljajui glavninu prljavog posla.
tipkovnica
monitor
ulazno-izlazni
tok
disk
disk
515
ios
istream
ostream
ifstream
ofstream
516
517
Klasa ios definira i operator !. On vraa vrijednost razliitu od nule ako je tok u stanju
pogreke (failbit, badbit, eofbit), odnosno nula ako je tok u ispravnom stanju
(goodbit). Tako je test toka mogue i ovako obaviti:
if (!cin)
cerr << "Pukla je brana, tok curi.";
Mogue je dohvatiti stanje bilo kojeg izlaznog ili ulaznog toka (pa tako i za unos s
tipkovnice ili ispis na zaslon). Kod ulaznog toka stanje se najee koristi kako bi se
detektiralo da je korisnik upisao podatak koji tipom ne odgovara oekivanom (na
primjer, oekuje se cijeli broj, a korisnik unese znak). Kod tokova vezanih na datoteke
stanja su jo korisnija, jer se njima moe ustanoviti je li pojedina ulazno-izlazna
operacija uspjela. Stanje eofbit se esto ispituje prilikom itanja toka ono signalizira
da se stiglo do kraja datoteke. Stanja badbit, odnosno failbit se obino testira
prilikom upisa kako bi se provjerilo je li upis uspjeno proveden.
<<
<<
<<
<<
9.3 <<
'C' <<
666 <<
"Hvala
endl;
endl;
endl;
bogu, jo uvijek sam ateist";
//
//
//
//
float
char
int
char *
Za ispis pokazivaa (izuzev char * za ispis znakovnih nizova) bit e pozvan operator
ostream::operator<<(const void *), koji e ispisati heksadekadsku adresu
objekta u memoriji. Zato e izvoenje naredbi
518
int i;
cout << &i;
na zaslonu raunala ispisati neto poput 0x7f24. Operator umetanja moe se dodatno
preopteretiti za korisniki definirane tipove podataka, to e biti opisano u sljedeem
odjeljku.
Kao rezultat, operator << vraa referencu na objekt tipa ostream, a kako se
operator izvodi s lijeva na desno, mogue je ulanavanje vie operatora umetanja:
cout << 1001 << " no";
// ispisat e 9
// ispisat e i uvean za 1
Meutim, pri ispisu izraza u kojima se koriste logiki, bitovni i operatori pridruivanja
je potrebno paziti jer oni imaju nii prioritet. Na primjer, izvoenje sljedeih naredbi
int prvi = 5;
int drugi = 7;
cout << "Vei od brojeva " << prvi << " i " << drugi << " je "
// problematian ispis:
cout << (prvi > drugi) ? prvi : drugi;
tj. ispisat e rezultat usporedbe varijabli prvi i drugi, a tek potom e pristupiti
izvoenju uvjetnog operatora ?:. Kako se taj operator zapravo primjenjuje na objekt
tipa ostream &, poziva se operator za konverziju u void *, te se zapravo testira tok.
519
7, j = 0;
i * j;
(i && j);
i && j;
520
Zadatak. Deklarirajte klasu Tocka koja e imati dva privatna podatkovna lana za
pohranjivanje koordinate toke u pravokutnim koordinatama, te javne funkcijske
lanove x() i y() za dohvaanje tih lanova. Preopteretite operator umetanja << tako
da ispisuje koordinate toke u obliku: (x, y).
Zadatak. Program iz prethodnog zadatka proirite deklaracijom klase Krug koja e
imati dva privatna podatkovna lana: objekt srediste klase Tocka, te polumjer
(npr. tipa double). Preopteretite operator umetanja tako da parametre kruga ispisuje u
sljedeem obliku: (r = polumjer, t0(x0, y0)). Iskoristite pritom ve
preoptereeni operator za ispis koordinata toke iz prethodnog zadatka.
18.4.3. Ostali lanovi klase ostream
ili preko manipulatora, koji se pomou operatora umetanja ubacuje u objekt cout:
cout << flush;
521
Klasa ostream sadri jo dva manipulatora koja se vrlo esto koriste: endl i ends.
endl smo ve dosad koristili u naim primjerima; on umee znak za novi redak ('\n')
u tok i prazni tok funkcijskim lanom flush(). Manipulator ends umee zakljuni
nul-znak.
U klasi ostream definirana su i dva funkcijska lana koji slue za kontrolu
poloaja unutar toka. Funkcijski lan tellp() vraa trenutanu poziciju u toku, a
seekp() se postavlja na zadanu poziciju. Ti lanovi se koriste primarno za kontrolu
pristupa datotekama, jer omoguavaju proizvoljno pomicanje unutar datoteke, pa e biti
objanjeni kasnije u ovom poglavlju, u odsjecima posveenim radu s datotekama.
>>
>>
>>
>>
i;
l;
f;
d;
//
//
//
//
int
long int
float
double
522
102345
34.56
3.4567e95
Naravno, da smo u gornjoj petlji uitavali realne brojeve (float ili double), problema
s decimalnom tokom ne bi bilo.
523
gdje realni_dio i imaginarni_dio mogu biti bilo kakvi realni brojevi. Praznine oko
realnog i imaginarnog dijela su proizvoljne jer je ulazni tok postavljen tako da jede
praznine oko uitanih podataka.
Ovo je tek rudimentarna definicija operatorske funkcije, koja slui samo kao
pokazni primjer. Funkcija obavlja samo osnovne provjere ispravnosti ulaznih podataka.
Ako se detektira neispravnost ulaznih podataka (npr. umjesto i, za imaginarnu jedinicu
se upie neki drugi znak), stanje toka se postavlja u failbit, a operacija se prekida.
Takoer, uoimo prvu naredbu u funkciji kojom se provjerava stanje toka prije
524
uitavanja. Ako je, zbog nepravilnih prethodnih operacija uitavanja, tok u stanju
pogreke, operacija uitavanja se odmah prekida. Tako e se, u sluaju ulananog
uitavanja podataka s toka, uitavanje prekinuti im se naie na prvu nepravilnost.
18.5.3. Uitavanje znakovnih nizova
Za upisani tekst
Svakim danom u svakom pogledu napredujemo.
Nakon pokretanja programa, treba utipkati eljeni niz tek po pritisku na tipku za
unos poet e se izvoditi petlja koja e pojedine znakove uitavati u niz. Valja
525
paziti da duljina upisanog teksta mora biti manja od duljine polja niz. Takoer,
uoimo da nakon zavrenog uitavanja znakova, polju niz treba dodati zakljuni nulznak. Ovaj lan ima daleko praktiniju primjenu kod uitavanje podataka iz datoteke.
2. get() bez argumenata koji izluuje znak s ulaznog toka. lan je tipa int, a vraa
kd znaka koji je uitao. Nailaskom na znak EOF funkcijski lan e vratiti vrijednost
-1. Pomou ovog funkcijskog lana petlja u prethodnom primjeru bi se mogla
napisati na sljedei nain:
while ((niz[i] = cin.get()) != -1)
if (niz[i++] == '\n')
break;
niz[i - 1] = '\0';
To znai da ako ulanimo nekoliko operacija uitavanja, drugi niz se nee uspjeti uitati
jer e sljedei get() odmah naii na '\n'. Isto tako e sljedei kd prouzroiti
beskonanu petlju:
while (cin.get(char niz, sizeof(niz)))
//...
526
Prvi i trei oblik funkcijskog lana get() vraaju kao rezultat pripadajui objekt tipa
istream, to omoguava njihovo ulanavanje, jednako kao i kod operatora <<,
odnosno >>.
Funkcijski lan getline() po potpisu je identian treem obliku funkcijskog lana
get(); deklariran je kao
istream &getline(char *znNiz, int duljina,
char granicnik = '\n);
Stoga se taj funkcijski lan poziva jednako kao i trei oblik lana get(), a i djeluje na
slian nain, s jednom znaajnom razlikom getline() uklanja zavrni znak '\n' s
ulaznog toka. Tako je mogue ulanavati operacije uitavanja pomou getline().
lan getline(), za razliku o get(), uklanja znak '\n' s ulaznog toka.
Stoga se operacije uitavanja pomou getline() mogu ulanavati, ali
pomou get() ne.
Funkcijski lan read() takoer uitava niz znakova zadane duljine, ali se njegovo
izvoenje prekida samo u sluaju nailaska na znak EOF. Deklariran je kao:
istream &read(char *znNiz, int duljina);
527
Kada u gornjem kdu ne bi bilo poziva funkcije ignore(), tok bi se nakon prvog
prolaza for-petlje zaustavio na desetom znaku. Ako podaci u prvom retku imaju vie
od deset znakova, u sljedeem prolazu petlje bi se itanje nastavilo od tog znaka pa bi
kao idui podatak bio uitan nastavak prvog retka.
Funkcijski lan peek() oitava sljedei znak, ali pritom taj znak i dalje ostaje na
toku, a lan putback() vraa eljeni znak na tok. Ilustrirajmo njihovu primjenu
jednostavnim primjerom u kojem se uitava tekst utipkan u jednom retku.
Pretpostavimo da e tekst biti proizvoljna kombinacija rijei i brojeva, meusobno
razmaknutih prazninama. Program e te rijei i brojeve ispisati u zasebnim recima. Da
bismo znali koji tip podatka slijedi, trebamo oitati prvi znak niza: ako je slovo ili znak
podcrtavanja, tada slijedi rije; ako je broj, decimalna toka ili predznak, imamo broj.
Poetni znak, meutim, ne smijemo izluiti s toka, jer on mora ui u niz koji tek treba
izluiti. U primjeru emo koristiti funkcije za razluivanje tipa znaka, deklarirane u
standardnoj datoteci zaglavlja ctype.h, tako da to zaglavlje valja ukljuiti na poetku
programa.
char znak;
char rijec[80];
double broj;
while ((znak = cin.peek()) != '\n') {
// preskae praznine:
if (isspace(znak)) cin.get(znak);
// je li prvi znak niza znamenka, predznak ili dec. toka?
else if (isdigit(znak) || znak == '+' || znak == '-' ||
znak == '.') {
int predznak = (znak == '-') ? -1 : +1;
// izluuje predznak s toka
if (znak == '-' || znak == '+') cin >> znak;
if (!(isdigit(znak = cin.peek()) || znak == '.')) {
cout << "Pogreka: iza predznaka mora biti "
"broj!" << endl;
break;
}
if (cin.peek() == '.') {
// izluuje decimalnu toku da bi provjerio
// slijedi li iza nje znamenka
cin >> znak;
if (!isdigit(cin.peek())) {
cout << "Pogreka: iza toke mora biti "
"broj!" << endl;
break;
}
// ako je OK, vraa decimalnu toku na tok
cin.putback(znak);
}
cin >> broj;
cout << (broj * predznak) << endl;
if (!isspace(cin.peek())) {
528
isspace() vraa true ako je znak naveden kao argument funkciji praznina (' '),
tabulator ('\t'), znak za povrat (engl. return, '\r'), za novi redak ('\n') ili pomak
papira ('\f').
isdigit() vraa true ako je znak naveden kao argument funkciji decimalna
znamenka ('0', '1'... '9').
529
isalpha() vraa true ako je znak naveden kao argument funkciji slovo
('a'...'', 'A'...'').
isalnum() vraa true ako je znak naveden kao argument funkciji slovo ili
decimalna znamenka (tj. isalpha() || isdigit())
Jednostavnije je (a u veini sluajeva i daleko pouzdanije) koristiti ove funkcije nego
provoditi ispitivanja oblika:
('a' <= znak && znak <= 'z') || ('A' <= znak && znak <= 'Z')
Vezivanje tokova osigurava da se izlazni tok isprazni prije nego to pone izluivanje s
ulaznog toka (i obrnuto). Ilustrirajmo to sljedeim primjerom [Stroustup91]:
Hoe li i koji nacionalni znakovi biti identificirani kao slova ovisi o parametrima definiranima
u klasi locale.
530
char zaporka[15];
cout << "Zaporka: ";
cin >> zaporka;
esto elimo da podaci koji se ispisuju zauzimaju tono odreenu irinu na zaslonu,
neovisno o broju znamenaka u broju ili znakova u nizu, tako da budu poravnati po
stupcima. irinu polja za ispis ili uitavanje brojeva ili znakovnih nizova moemo
podesiti funkcijskim lanom width(). Cjelobrojni argument odreuje broj znakova
koji e ispis ili upis zauzimati. Na primjer:
cout << '>';
cout.width(6);
cout << 1234 << '<' << endl;
1234<
Podeena irina traje samo jedan ispis nakon toga ona se postavlja na
podrazumijevanu irinu. Zbog toga e izvoenje kda
531
biti ispisan kompletan broj 1234, unato tome to je kao argument funkcijskom lanu
width() navedena irina od 2 znaka:
>1234<
Funkcija width() kao rezultat vraa prethodno zadanu irinu ispisa. Ako se ne navede
argument, funkcija width() vraa trenutno vaeu irinu.
Sva navedena razmatranja vrijede i za znakovne nizove. Funkcijski lan width()
se moe pozvati i za ulazne tokove u tom sluaju se njime ograniava broj uitanih
znakova, slino kao to smo to mogli postii funkcijskim lanom get().
irine ispisa podataka mogu se podeavati i pomou manipulatora setw(), koji e
biti opisan kasnije u ovom poglavlju.
18.6.3. Popunjavanje praznina
Ako je irina polja za ispis zadana width() funkcijom, ira od podatka, nedostajui
znakovi bit e popunjeni bjelinama. elimo li umjesto bjelina ispisati neki drugi znak,
najjednostavnije emo to uraditi funkcijskim lanom fill(). On kao argument
prihvaa znak kojim elimo popuniti praznine, a vraa kd znaka koji je prethodno bio
koriten za popunjavanje.
U sljedeem primjeru funkcijskim lanom fill() se praznine popunjavaju
zvjezdicama:
cout << '>';
cout.width(12);
cout.fill('*');
cout << 1234 << '<' << endl;
532
533
zastavica
grupa
znaenje
left
adjustfield
poravnanje ulijevo
right
adjustfield
poravnanje udesno
internal
adjustfield
dec
basefield
hex
basefield
oct
basefield
fixed
scientific
floatfield
floatfield
showpos
showpoint
showbase
skipws
boolalpha
unitbuf
uppercase
534
cout.width(6);
// ... ispunjeno nulama ...
cout.fill('0');
// ... izmeu 0x i broja
cout.setf(ios::internal, ios::adjustfield);
cout << 15 << endl;
// oktalni ispis
cout.setf(ios::oct);
cout << 16 << endl;
// natrag na dekadski ispis
cout.setf(ios::dec, ios::basefield);
cout << 17. << endl;
// vie ne ispisivati '+'
cout.unsetf(ios::showpos);
cout << 18. << endl;
// natrag na poetno stanje
cout.flags(zastavice);
Prije prvog ispisa postavljena je zastavica za ispis predznaka '+' ispred pozitivnih
brojeva pomou zastavice setpos. Unato tome to je irina polja za ispis postavljena
na 8, prvi broj je ispisan uz predznak, jer je podrazumijevano poravnavanje ispisa
ulijevo. Tek nakon postavljanja zastavice internal (ili eventualno right) broj e biti
pomaknut udesno ove dvije zastavice oito imaju smisla samo u kombinaciji s lanom
width().
Postavljanjem zastavica hex i oct, dobiven je ispis cijelih brojeva u
heksadekadskom, odnosno oktalnom formatu, s time da je zastavicom showbase
ukljueno ispisivanje niza "0x" ispred heksadekadskog, odnosno znaka '0' ispred
oktalnog broja. Za ispis realnih brojeva zastavice hex, oct i showbase nemaju
znaenja.
Na kraju primjera naveden je i jedan poziv funkcije unsetf() kojom se brie
zastavica za ispis '+' ispred pozitivnih brojeva.
535
cout.setf(ios::scientific, ios::floatfield);
// u znanstvenom zapisu
cout << 13e4 << endl;
cout << 134 << endl;
cout.setf(ios::uppercase);
cout << 14. << endl;
536
10
11.0000
12
12000.000000
1234560000000.000000
1.300000e+05
134
1.400000E+01
ispisati
12.34
1.23457e+15
1.2345e-07
Ako su postavljene zastavice fixed ili scientific, tada precision() odreuje broj
znamenki koje e se prikazati desno od decimalne toke. To znai da e izvoenje
naredbi
537
cout.precision(2);
fout.setf(ios::fixed, ios::floatfield);
fout << 12.34 << endl;
fout << 1234567890123456. << endl;
fout << 0.00000012345 << endl;
fout.setf(ios::scientific, ios::floatfield);
fout << 12.34 << endl;
fout << 1234567890123456. << endl;
fout << 0.00000012345 << endl;
18.6.6. Manipulatori
Ovakvim pristupom gubi se nit kontinuiteta izlazne naredbe iako su sve tri naredbe
vezane za ispis jednog podatka, morali smo ih napisati odvojeno.
Uvoenjem manipulatora (engl. manipulators) omoguena je bolja povezanost
kontrole formata i operatora izluivanja, odnosno umetanja. Umjesto da se naredba za
538
formatiranje poziva kao zaseban funkcijski lan, ona se jednostavno umee na tok. Tako
gornje naredbe moemo pomou manipulatora napisati preglednije, na sljedei nain:
cout << setw(12) << setiosflag(ios::showpos) << 1.;
manipulator
zaglavlje
znaenje
setw(int)
setfill(int)
dec
hex
oct
setbase(int)
setprecision(int)
setiosflags(long)
resetiosflag(long)
iomanip.h
iomanip.h
iostream.h
iostream.h
iostream.h
iomanip.h
iomanip.h
iomanip.h
iomanip.h
flush
endl
iostream.h
iostream.h
ends
ws
iostream.h
iostream.h
Napomenimo da manipulatori flush, endl i ends djeluju samo za izlazne tokove, dok
manipulator ws djeluje samo za ulazne tokove.
Zadatak. Napiite programske odsjeke iz prethodna dva odjeljka tako da umjesto
funkcijskih lanova upotrijebite manipulatore.
539
upisuje u izlaznu listu. Radi se, naime, o jednom lukavom triku: svi manipulatori bez
parametra su realizirani kao funkcija koja kao parametar uzima referencu na tok. Tako u
datoteci zaglavlja iostream.h postoji deklaracija te funkcije koja ispisuje znak '\n'.
No kada se u ispisnoj listi navede samo endl bez zagrada, to je zapravo pokaziva na
funkciju. Operator << je preoptereen tako da prihvaa pokaziva na funkciju, te on u
biti poziva funkciju navodei tok kao parametar. Evo kako je taj operator definiran:
typedef ostream &(*Omanip)(ostream &);
ostream &operator<<(ostream &os, Omanip f) {
return f(os);
}
Tip Omanip je pokaziva na funkciju koja kao parametar uzima referencu na ostream i
vraa referencu na ostream. Izraz
cout << endl;
se interpretira kao
operator<<(cout, endl);
Na taj nain mogue je dodavati vlastite manipulatore. Na primjer, mogli bismo dodati
manipulator cajti koji ispisuje vrijeme na tok. Napominjemo da nije potrebno
dodavati novi operator << za ispis; on je ve definiran u iostream.h. Evo kda
manipulatora:
#include <time.h>
#include <iostream.h>
ostream &cajti(ostream &os) {
time_t vrij;
time(&vrij);
tm *v = localtime(&vrij);
os << v->tm_hour << ":" << v->tm_min << ":" << v->tm_sec;
return os;
}
540
541
542
Ime datoteke iji sadraj se eli ispisati navodi se kao parametar pri pokretanju
programa. U sluaju da nije naveden parametar ispisuje se kratka uputa, a ako se navede
previe parametara ispisuje se poruka o pogreki.
Usredotoimo se na nama najvaniji dio programa: inicijalizaciju ulaznog toka i
uitavanje podataka iz datoteke. Ulazni tok je objekt datnica klase ifstream.
Prilikom inicijalizacije kao argument konstruktoru se prenosi ime datoteke. Zatim se
ispituje da je li inicijalizacija uspjela. Ako inicijalizacija nije uspjela (na primjer ako
navedena datoteka nije pronaena ili se ne moe itati), program ispisuje poruku o
pogreki i prekida daljnje izvoenje.
Prilikom inicijalizacije toka valja provjeriti je li inicijalizacija bila uspjeno
provedena, tj. je li datoteka uspjeno otvorena za uitavanje ili upisivanje.
U protivnom, daljnje izvoenje programa nee imati nikakvog efekta.
Ako je inicijalizacija bila uspjena i datoteka otvorena za itanje, u while-petlji se
iitava sadraj datoteke. Za itanje se koristi funkcijski lan get(char &), opisan u
odjeljku 18.5.3. Budui da je klasa ifstream naslijeena iz istream, za uitavanje iz
543
datoteke na raspolaganju su nam svi lanovi klase istream, od kojih smo veinu
upoznali.
Koritenje izlaznih tokova za upis u datoteku predoit emo sljedeim primjerom
programom koji kopira sadraj tekstovne datoteke, izbacujui pritom sve viestruke
praznine i prazne retke. Ime izvorne datoteke neka se navodi kao prvi parametar
prilikom pokretanja programa (slino kao u prethodnom primjeru), a ime preslikane
datoteke neka je drugi parametar. Zbog slinosti s prethodnim primjerom, izostavit
emo provjere parametara i navesti samo efektivni dio kda:
//...
ifstream izvornik(argv[1]);
if (!izvornik) {
cerr << "Ne mogu otvoriti ulaznu datoteku "
<< argv[1] << endl;
exit(1);
}
ofstream ponornik(argv[2]);
if (!ponornik) {
cerr << "Ne mogu otvoriti izlaznu datoteku "
<< argv[2] << endl;
exit(1);
}
char znNiz[80];;
while (izvornik >> znNiz) {
ponornik << znNiz;
if (izvornik.peek() == '\n')
ponornik << endl;
else if (izvornik.peek() != EOF)
ponornik << " ";
}
if (!izvornik.eof() || ponornik.bad())
cerr << "Uuups! Nekaj ne tima" << endl;
//...
U ovom primjeru otvaraju se dva toka: izvornik klase ifstream koji se pridruuje
datoteci iz koje se sadraj iitava, te ponornik klase ofstream koji se pridruuje
datoteci u koju se upisuje novi sadraj. Prilikom inicijalizacije oba toka, konstruktorima
se kao argumenti prenose imena pripadajuih datoteka, a potom se provjerava je li
inicijalizacija uspjeno provedena.
U while-petlji se sadraj izvorne datoteke uitava pomou operatora >> u polje
znakova znNiz. To znai da e se izvorna datoteka itati rije po rije. Budui da
operator >> preskae sve praznine izmeu rijei, u izlazni tok treba umetati po jednu
bjelinu nakon svake rijei. Ako se iza rijei pojavio znak za novi redak on se upisuje
umjesto praznine u protivnom bi cijela izlazna datoteka bila ispisana u jednom retku.
Bjelina se ne smije dodati iza zadnje rijei u datoteci, to se provjerava ispitivanjem
544
if (izvornik.peek() != EOF)
EOF je simboliko ime znaka koji oznaava kraj datoteke (engl. End-Of-File), definirano
u zaglavlju stdio.h.
Na kraju primjera uoimo pozive dva funkcijska lana koji provjeravaju stanje
tokova: eof() i bad(). Tim se ispitivanjem provjerava je li nastupila pogreka u
izlaznom toku prije nego to je dosegnut kraj ulaznog toka.
Zadatak. Napiite program koji kopira sadraj datoteke u drugu datoteku, znak po znak.
18.7.2. Otvaranje i zatvaranje datoteke
Verzije koje smo dosad koristili kao argument prihvaaju ime datoteke na koju se tok
vezuje. Koristi li se konstruktor bez parametara, tada se tok vee na datoteku naknadno,
pomou funkcijskog lana open(). Tako smo u primjeru sa stranice 543 mogli pisati:
ifstream izvornik;
izvornik.open(argv[1]);
ofstream ponornik;
ponornik.open(argv[2]);
545
Uspjeh otvaranja datoteke ispituje se tek nakon poziva funkcijskog lana open() na
potpuno identian nain kao i u primjeru na stranici 543.
Ako datoteke treba zatvoriti prije (implicitnog ili eksplicitnog) poziva destruktora,
to moemo uiniti funkcijskim lanom close():
izvornik.close();
ponornik.close();
To nam omoguava da tok veemo na neku drugu datoteku, iako se to vrlo rijetko
koristi.
Kao to se vidi iz gore navedenih deklaracija konstruktora, verzije konstruktora
klasa ifstream i ofstream koje prihvaaju ime datoteteke, prihvaaju i drugi
argument mod. On ima podrazumijevanu vrijednost za pojedini tip toka, tako da ga
nije neophodno uvijek navoditi. Za ifstream je podrazumijevani mod in, tj. itavanje
iz datoteke, a za ofstream podrazumijevani mod je out, tj. upis u datoteku. Potpuno
isto vrijedi i za funkcijske lanove open() ako se datoteka eli otvoriti u modu
razliitom od podrazumijevanog, uz ime datoteke moe se proslijediti mod.
Modovi za otvaranje datoteka definirani su kao bitovna maska u klasi ios (tablica
18.3). Vie razliitih modova moe se meusobno kombinirati koristei operator |
(bitovni ili). Na primjer, elimo li da se prilikom upisivanja u datoteku novi sadraj
mod
znaenje
app
ate
binary
in
nocreate
noreplace
out
trunc
546
Suprotan efekt ima mod trunc koji kree datoteku prilikom njenog otvaranja.
Budui da je to podrazumijevano ponaanje prilikom otvaranja datoteke za upis, ne
treba ga posebno navoditi. Binarni mod e biti opisan u odjeljku 18.7.4.
Matoviti itatelj bi se mogao dosjetiti i pokuati promijeniti podrazumijevane
modove tokova ifsream i ofstream (in, odnosno out). I to je mogue; prevoditelj
nee prijaviti pogreku napiemo li sljedee naredbe:
ifstream izlazniUlaz("kamo.dat", ios::out);
ofstream ulazniIzlaz("oklen.dat", ios::in);
Meutim, od toga nee biti neke vajde, jer su u klasi ofstream (odnosno u klasi
ostream koju ova nasljeuje) definirani samo funkcijski lanovi i operator << za upis u
datoteku, dok su u klasi ifstream (odnosno njenoj osnovnoj klasi istream) definirani
samo funkcijski lanovi i operator >> za uitavanje. Nije potrebno mudrovati:
jednostavno koristite ulazne tokove za ulazne operacije, a izlazne tokove za izlazne
operacije.
18.7.3. Klasa fstream
Ponekad trebamo iz iste datoteke podatke itati i u nju pohranjivati podatke. Na primjer,
program za evidenciju stanja na tekuem raunu uitavat e iz datoteke zadnje stanje
na raunu, dodati ili oduzeti nove uplate, odnosno izdatke te potom pohraniti novi saldo
u datoteku. Kako klase ifstream i ofstream podravaju komunikaciju samo u
jednom smjeru, njihovo koritenje bi iziskivalo da prvo otvorimo datoteku za
uitavanje, uitamo podatke te zatvorimo datoteku, a tek potom otvorimo istu datoteku
za upis, upiemo novi podatak i na kraju zatvorimo izlazni tok. Takvo programiranje bi
bilo dosta zamorno, a rezultiralo bi i vrlo sporim programom. Postupak pojednostavljuje
klasa fstream izvedena iz klase iostream, koja je pak izvedena iz klasa istream i
ostream ona u sebi ima ukljuene sve operacije za pisanje i itanje jedne te iste
datoteke. Ilustrirajmo primjenu klase fstream primitivnim programom za voenje
evidencije stanja na raunu:
//...
float staroStanje;
fstream strujiStruja("MojaTuga.dat",
ios::in | ios::out | ios::nocreate);
if (!strujiStruja) {
cerr << "Ne postoji datoteka \"MojaTuga.dat\"" << endl
<< "Bit e stvorena nova." << endl;
strujiStruja.open("MojaTuga.dat", ios::out);
staroStanje = 0.;
547
}
else {
do {
strujiStruja >> staroStanje;
} while(strujiStruja);
}
cout << setiosflags(ios::fixed) << setprecision(2)
<< setw(15) << "staro stanje:"
<< setw(8) << staroStanje << endl;
cout << setw(15) << "uplate:";
float uplate;
cin >> uplate;
cout << setw(15) << "rashodi:";
double rashodi;
// za rashode uvijek koristite double!
cin >> rashodi;
// ispisuje 25 znakova '-' za podcrtavanje
cout << setw(25) << setfill('-') << " " << endl
<< setfill(' ');
float novoStanje = staroStanje + uplate - rashodi;
strujiStruja.clear();
strujiStruja << novoStanje << endl;
if (strujiStruja.fail())
cerr << "Nekaj ne valja s upisom" << endl;
else {
cout << setw(15) << "novo stanje:"
<< setw(8) << novoStanje << endl << endl
<< "Novo stanje je s uspjehom pohranjeno" << endl;
}
//...
Ako datoteka postoji, ita se njen sadraj, sve do kraja datoteke. Zadnji podatak u
datoteci je posljednje upisano stanje. Kraj datoteke provjerava se preko pokazivaa koji
vraa strujiStruja sve dok je uitavanje u redu, taj je pokaziva razliit od nule.
Nailaskom na kraj datoteke postavlja se eofbit pa pokaziva postaje jednak nuli. Uvjet
za ponavljanje petlje uitavanja mogli smo napisati i kao:
548
do {
//...
} while(!strujiStruja.eof());
prije upisa novog podatka. Da nema te naredbe, podatak se ne bi mogao upisati! Naime,
prilikom uitavanja, nailazak na kraj datoteke postavio je eofbit sve dok se to stanje
ne obrie, upis u datoteku se ne moe obaviti.
Ovako napisani program ima jedan oiti nedostatak: pri svakom pohranjivanju
novog stanja datoteka se poveava i u njoj su pohranjeni podaci koji korisniku gotovo
sigurno nee vie nikad trebati. Elegantnije rjeenje je da se stari podatak jednostavno
prepie novim podatkom, ali da bismo to mogli napraviti, moramo se nakon uitavanja
starog stanja vratiti na poetak datoteke i tamo upisati novo stanje. Kako to ostvariti bit
e prikazano u sljedeem odjeljku.
18.7.4. Odreivanje i postavljanje poloaja unutar datoteke
Prilikom pisanja i itanja datoteke, poseban pokaziva datoteke (engl. file pointer)
pokazuje na poziciju u datoteci na kojoj se operacija obavlja. U dosadanjim primjerima
podaci su se uzastopno upisivali u datoteke ili iz njih uzastopno uitavali. Pritom se
pokaziva datoteke automatski uveava za broj upisanih/uitanih bajtova. Eksplicitnim
usmjeravanjem tog pokazivaa na eljeno mjesto mogue je itati podatke ili pisati ih
na proizvoljnom mjestu unutar datoteke.
Pokaziva datoteke je potrebno razlikovati od obinih C++ pokazivaa on je
jednostavno cijeli broj koji pokazuje na koliko se bajtova od poetka datoteke upis
obavlja. Tip pokazivaa je definiran tipom streampos. Taj tip je definiran pomou
kljune typedef u datoteci zaglavlja fstream.h te je njegova definicija
implementacijski zavisna. U veini implementacija taj tip je zapravo long int.
U klasi ostream definirana su dva funkcijska lana kojima se dohvaa pokaziva
datoteke, te time kontrolira poloaja unutar izlaznog toka:
1. tellp() koji kao rezultat vraa trenutnu vrijednost pokazivaa datoteke,
2. seekp() kojim se pomie na zadani poloaj unutar datoteke.
Slino su u klasi istream definirana dva funkcijska lana koji omoguavaju kontrolu
poloaja u ulaznom toku:
1. tellg() koji kao rezultat vraa trenutnu vrijednost pokazivaa datoteke,
2. seekg() kojim se pomie na zadani poloaj unutar datoteke.
Zbog slinosti imena, u poetku e korisnik teko razlikovati lanove klase ostream
od lanova klase istream. Radi lakeg pamenja, moemo shvatiti da zadnja slova u
549
nazivima lanova (p, odnosno g) dolaze od naziva funkcijskih lanova put() i get() u
pripadajuim klasama.
Funkcijski lanovi seekp() i seekg() definirani su u po dvije preoptereene
inaice:
ostream &ostream::seekp(streampos &pozicija);
istream &istream::seekg(streampos &pozicija);
ostream &ostream::seekp(streamoff &pomak, ios::seekdir smjer);
istream &istream::seekg(streamoff &pomak, ios::seekdir smjer);
Prve dvije inaice funkcijskih lanova seekp(), odnosno seekg(), prihvaaju kao
argument apsolutnu poziciju na koju se valja postaviti unutar datoteke. Kao stvarni
argument najee se prenosi vrijednost dohvaena funkcijama tellp(), odnosno
tellg().
Druge dvije verzije kao prvi argument prihvaaju pomak u odnosu na poziciju
definiranu drugim argumentom. Tip streamoff je definiran u zaglavlju fstream.h i
slui za specifikaciju pomaka pokazivaa. seekdir je pobrojenje koje oznaava u
odnosu na koje mjesto se pomak rauna, te ima tri mogue vrijednosti:
550
Ako datoteka slova.dat sadri samo niz "abcdefgh", tada e izvoenje gornjih
naredbi na zaslonu ispisati:
haag
Zadatak. Napiite program koji kopira sadraj datoteke, znak po znak, od njena kraja
prema poetku, u novu datoteku.
18.7.5. Binarni zapis i uitavanje
551
biti smjeten na kraj datoteke, ali emo pronai mjesto u listi na koje se podatak dodaje,
te emo aurirati pokazivae. Na taj nain izbjegavamo uitavanje kompletne datoteke u
memoriju, njeno sortiranje i zapisivanje upis e biti bri. tovie, za veliku bazu bi
nam sigurno ponestalo memorije za uitavanje cijele baze, pa operaciju niti ne bismo
mogli obaviti.
Prvi podatak u datoteci bit e glava liste pokaziva na poziciju na koju je zapisan
prvi lan liste. Ako je lista prazna, pokaziva e sadravati nulu. Svaki se lan liste
sastoji od pokazivaa na poloaj sljedeeg lana te od samog sadraja lana. lan koji je
zadnji u listi ima pokaziva jednak nuli. U donjem kdu je rijeeno umetanje novog
lana; itatelju preputamo da rijei problem brisanja lana.
const int duljImena = 50;
pos_type pozSljed = 0;
fstream bazaImena("imenabin.dat", ios::in | ios::out |
ios::nocreate | ios::binary);
if (!bazaImena) {
bazaImena.open("imenabin.dat", ios::in | ios::out |
ios::binary);
bazaImena.write((char *) &pozSljed, sizeof(pos_type));
}
char novoIme[duljImena];
cin.getline(novoIme, duljImena);
// ponavlja unos novih imena sve dok je duljina utipkanog
// imena razliita od nule
while (cin.gcount() > 1) {
pos_type pozPreth = 0;
pos_type pozTemp = 0;
// na poetak datoteke
bazaImena.seekg(0);
// uita poziciju sljedeeg
bazaImena.read((char *) &pozSljed, sizeof(pos_type));
// prolazi datoteku sve dok postoji sljedei
while (pozSljed) {
// pomakne na sljedei zapis
bazaImena.seekg(pozSljed);
char imeNaDisku[duljImena];
// uita pokaziva na sljedei zapis
bazaImena.read((char *) &pozTemp, sizeof(pos_type));
// uita ime u tekuem zapisu
bazaImena.get(imeNaDisku, duljImena, '\0');
if (strcmp(imeNaDisku, novoIme) >= 1)
break;
// ako novo treba doi ispred, prekida
pozPreth = pozSljed;
pozSljed = pozTemp;
}
// prvo dopisuje novo ime na kraj datoteke
bazaImena.seekg(0, ios::end);
pozTemp = bazaImena.tellg();
552
Inicijalizacija toka je identina kao u primjeru sa strane 546, osim to dodatno navodimo
binarni mod. Ako datoteka ne postoji, tada se ona generira i upisuje se glava liste kao
nula. Zatim zapoinje petlja za unos novih podataka.
Podaci se unose u zasebnim recima pritiskom na tipku za unos izvodi se funkcija
getline(), koja oitava utipkani redak teksta; ako se tipka za unos pritisne na samom
poetku retka, petlja e se prekinuti. Zatim se provjerava je li neki podatak uope
unesen. To se moe obaviti pomou funkcije gcount() ona vraa broj uitanih
znakova u prethodnoj get(), getline() ili read() funkciji. Budui da getline()
uitava i znak za novi redak, u sluaju da nije utipkan nikakav tekst, funkcija gcount()
e vratiti broj 1 te je to signal da korisnik nije unio podatak.
Uoimo nain na koji se binarno pohranjuju i uitavaju pokazivai datoteke. Za
upis pokazivaa koriten je funkcijski lan write(). On kao prvi argument prihvaa
pokaziva char * koji pokazuje na niz bajtova koje elimo upisati, a drugi argument je
broj bajtova koje treba upisati. Budui da pokazivae elimo upisati binarno, bajt po
bajt, pokaziva operatorom dodjele tipa (char *) pretvaramo u niz bajtova i kao takav
ga zapisujemo. Za upis teksta takoer koristimo lan write(), jer on omoguava da
jednostavno zapiemo i zakljuni nul-znak.
Za usporedbu znakovnih nizova koritena je funkcija strcmp(), deklarirana u
zaglavlju string.h. Navedimo jo kako bi izgledao kd za ispis lanova liste:
// ispis lanova liste po abecedi
cout << "Dosad upisana imena:" << endl;
bazaImena.seekg(0);
bazaImena.read((char *) &pozSljed, sizeof(pos_type));
while (pozSljed) {
bazaImena.seekg(pozSljed);
bazaImena.read((char *) &pozSljed, sizeof(pos_type));
bazaImena.get(novoIme, duljImena, '\0');
cout << novoIme << endl;
}
cout << "*** Konac popisa ***" << endl;
553
bajtova kamo treba uitane podatke pohraniti, a drugi argument je broj bajtova koje
treba uitati. Tekstovni niz se uitava funkcijskim lanom get() kojemu je kao
graninik, kod kojeg prekida uitavanje, naveden nul-znak.
U svakom sluaju valja uoiti da kod binarno zapisanih podataka, prilikom
uitavanja treba unaprijed znati kakvog je tipa podatak koji se uitava. U binarnom
obliku svi podaci izgledaju jednako, pa program nee razlikovati uitava li se broj ili
znakovni niz.
Zadatak. Razmislite i pokuajte rijeiti gornji zadatak tako da pokazivae zapisujete u
tekstovnom obliku. Usporedite duljine datoteka sa deset jednakih nizova podataka za
obje izvedbe programa.
Zadatak. Napiite program koji pohranjuje i uitava polje cijelih brojeva u binarnom
obliku. Duljinu polja (takoer u binarnom obliku) pohranite kao prvi podatak.
554
555
Objektno orijentirano programiranje nije samo novi nain zapisivanja programa ono
iziskuje potpuno novu koncepciju razmiljanja. Stari proceduralni programi se, dodue,
mogu prepisati u objektnom jeziku, no time se ne iskoritavaju puni potencijal jezika.
C++ nije zamiljen zato da bi se stari programi bolje preveli, ili da bismo dobili bri
izvedbeni kd. Njegova osnovna namjena je pomoi programeru da formulira svoje
apstraktne ideje na nain razumljiv raunalu. Njegova uporabna vrijednost posebice
dolazi do izraaja pri razvoju novih, sloenih programa.
556
je napisati C++ program koji ne deklarira niti jedan objekt te dosljedno prati
proceduralni nain pisanja programa. No ipak, s pojavom novih operacijskih sustava
koji tee objektnom ustroju, objektno programiranje se jednostavno ne smije zanemariti.
Jezik C++ je jezik ope namjene, za poslove od sistemskog programiranja do
razvoja korisnikih suelja te isto apstraktnih primjena kao to su matematiki
prorauni i predstavlja najkoriteniji programski jezik za pisanje komercijalnih
aplikacija. To je mjesto stekao dobrim dijelom zahvaljujui svom prethodniku jeziku
C. Svi C programi se mogu prevesti C++ prevoditeljem potpuno ispravno, te se na ve
postojee projekte lako moe dograditi objektno orijentirani modul. I dok je ouvanje
kompatibilnosti s C-om sigurno jedna od velikih prednosti C++ jezika, to mu je
istodobno i jedan veliki kamen smutnje. Naime, mnogi C-ovski koncepti, kao to su
pokazivai, nizovi te dodjele tipova ugraeni su u C++ te dozvoljavaju zaobilaenje
objektnih svojstava. Tako se C++ nalazi izmeu klasinih proceduralnih jezika, kao to
su C i Pascal, i istih objektnih jezika, kao to je SmallTalk te objedinjava svojstva i
jednih i drugih. Takva dvostruka svojstva ponekad dodaju jeziku nepotrebnu
kompliciranost.
U prethodnim poglavljima iznesena su svojstva jezika s naznakama kako se ta
svojstva mogu iskoristiti u objektno orijentiranim programima. No do istinskog
poznavanja objektno orijentiranog principa programiranja potrebno je prevaliti jo dug
put. Naime, ako programer poznaje kljunu rije virtual, ne znai da e ju ujedno
znati i upotrijebiti na pravom mjestu na pravi nain, po principu Stavi pravi virtual
na pravo mjesto... (uini to esto). Dapae, ima dosta primjera nenamjerne
zloupotrebe slobode koju prua OOP. Rezultat je lo izvorni kd koji ima dosta
pogreaka, neitak je, sporo se izvodi te se dosta teko prilagoava promijenjenim
zahtjevima.
Zbog toga emo u ovom poglavlju pokuati prikazati nain na koji se elementi
jezika upoznati u prethodnim poglavljima mogu uinkovito iskoristiti. Ovo poglavlje
nee uvesti niti jednu novu kljunu rije niti proiriti steeni C++ vokabular. No
ispravna i dosljedna primjena principa programiranja je ak vanija od poznavanja
sintakse i semantike jezika.
Ovdje e se spomenuti samo osnovni principi modeliranja objekata na kraju, u
podruju kao to je C++ ne moe se oekivati da svo znanje bude kondenzirano na
jednom mjestu. Naposljetku, iskustvo je vrlo znaajna dimenzija objektnog
programiranja koja se stie iskljuivo radom. Neke stvari, ma koliko puta ih proitali,
shvatit ete tek nakon tri dana i noi provedenih analizirajui program koji bez pardona
rui sve teorije o pisanju programa bez pogreaka.
557
558
559
biblioteka
jezik
prozor
izbornik
ekran
raunalo
560
Iako biblioteka ima veze s rjeavanjem problema, ona je objekt koji e objedinjavati
sve preostale objekte. Nije potrebno uvoditi posebnu apstrakciju za biblioteku, jer e
sam izvedbeni kd dobiven nakon prevoenja predstavljati biblioteku.
Jezik nema direktne veze s rjeavanjem problema. On je samo sredstvo koje
koristimo za formulaciju rjeenja, a ne dio rjeenja.
Prozor, izbornik i ekran su objekti koji su direktno pogodni za prikazivanje pomou
klase. Na primjer, ekran je fiziki dio raunala koji omoguava prikaz podataka u
vidljivom obliku. Njegova svojstva, kao na primjer razluivost u horizontalnom i
vertikalnom smjeru, broj boja koji se odjednom moe prikazati, frekvencija
osvjeavanja i slino, mogu se vrlo efikasno upakirati u objekt klase Ekran koji e biti
apstrakcija stvarnog ekrana.
Prozor i izbornik su elementi korisnikog suelja koji su ve sami po sebi
apstrakcije. Oni ne postoje u stvarnom svijetu, ve su dio apstrakcije u komunikaciji
izmeu raunala i korisnika.
Iako se moda na prvi pogled ini da je raunalo objekt koji nema veze s rjeenjem
problema, paljivo razmatranje nam moe rei da to nije tako. Raunalo je objekt koji se
sastoji od mnogo razliitih cjelina: tipkovnice, memorije, mia, ekrana, diskova i slino.
Neki od njih slue za interakciju izmeu programa i korisnika, poput mia, tipkovnice i
ekrana. Na slian nain na koji emo u klasu Ekran upakirati svojstva ekrana u
raunalu, mogue je definirati i klase Mis i Tipkovnica koji e predstavljati apstraktni
model tih ureaja. Dakle, nije nam potrebno direktno modelirati raunalo, no modelirat
emo neke njegove dijelove. Modeliranje raunala koje objedinjuje sve druge objekte za
svaki dio raunala bio bi posao operacijskog sustava.
Primjenom gornjeg postupka dobili smo ve nekoliko klasa koje emo primijeniti u
rjeavanju problema. Time popis nije nipoto zakljuen daljnjom analizom dobivenih
objekata vidjet emo da emo morati uvesti nove klase za nove apstrakcije.
561
562
za prikaz slike treba monitor i grafika kartica, pod nazivom ekran podrazumijevat
emo cjelokupni podsustav raunala za prikaz slike.
19.5.2. Definicija prozora
Po gore navedenom principu provest emo definiciju apstrakcije prozora kako bismo
mogli bolje definirati na objektni model:
Prozor je podruje slike na ekranu koje prikazuje stanje izvoenja pojedine
aplikacije u raunalu.
Gornja definicija, iako moda na prvi pogled najoitije prikazuje ulogu prozora, nije
sasvim precizna. Naime, rei da je primarna uloga prozora prikazivanje stanja odreene
aplikacije je vrlo usko odreenje. U stvarnosti prozori se koriste za mnogo raznih drugih
poslova, kao to je prikazivanje dijalokih prozora ili ak modeliranje pojedinih kontrola
unutar prozora. Takoer, operacijski sustav MS Windows implementira padajue
izbornike pomou prozora. Zbog toga treba promijeniti gornju definiciju:
Prozor je pravokutno podruje slike na ekranu za prikaz odreenog skupa
logiki povezanih podataka.
Takva definicija znatno bolje opisuje to je prozor. Pri tome nita nije reeno o svrsi
prozora; on samo prikazuje podatke. Takav prozor moe posluiti za prikaz podataka
aplikacije koja se izvodi na raunalu, ali takoer moe posluiti i za prikaz izbornika.
Nadalje, prozora ima razliitih vrsta: osim prozora koji predstavljaju neko podruje na
ekranu, postoje prozori koji mogu imati okvir, polja za poveavanje, smanjenje i
zatvaranje pa i pridijeljen izbornik. Nazovimo takve prozore uokvirenim prozorima.
19.5.3. Definicija izbornika
Trei vaan element nae biblioteke elemenata grafikog suelja jesu izbornici (meniji).
Pokuajmo izrei kratku definiciju izbornika.
Izbornik je podruje na ekranu koje omoguava izbor neke od ponuenih
opcija.
Ova definicija dosta dobro ocrtava to je izbornik i emu slui, ali ona nita ne kae o
detaljima kako to izbornik radi. Zbog toga je potrebno definiciju proiriti i opisati to su
opcije te navesti nain na koji su opcije prikazane.
Pojedina opcija izbornika je kratak tekst koji ukratko opisuje akciju koju e
program poduzeti nakon njenog izbora. Takoer, opcija izbornika moe voditi
u novi podizbornik.
Sada ve znamo dosta o strukturi izbornika. No jo mnoga pitanja ostaju otvorena: kako
izbornik izgleda kada se prikae na ekranu, kako izgledaju pojedine opcije, kako se
izabire opcija miem i slino. Dapae, razvoj klase izbornika koja e se moi koristiti
je vrlo sloen i obuhvaat e razne parametre. U razvoj takve klase osim inenjera
563
informatike mogu biti ukljueni primjerice i dizajneri koji e odrediti vizualni identitet
izbornika. Zbog toga emo preskoiti daljnju analizu ove klase.
Postoji tehnika koju su razvili ameriki strunjaci K. Beck i W. Cunningham koja
pomae razvoju apstrakcije u velikim sloenim projektima. Ta tehnika koristi takozvane
CRC kartice (kratica od engl. class, responsibility, collaboration klasa, odgovornost,
suradnja). Ona se sastoji u tome da se za svaku klasu koja se pojavljuje u projektu
formira jedna kartonska kartica tono odreene veliine (kako se ne bi pretjerivalo s
glomaznim definicijama) na koju se napie naziv klase i njena glavna svojstva. Ljudi
koji rade na projektu se zatim okupe na sastanku te svaki dobije jednu ili vie kartica.
Oni zatim uzimaju ulogu pojedinih klasa te pokuavaju odigrati vie scenarija
interakcije izmeu objekata. Pri tome esto postaje jasno da pojedine apstrakcije nisu
adekvatne te ih se zatim mijenja u skladu sa zahtjevima. Sve te promjene se unose na
kartice kako bi se na kraju mogla izvui bit svake klase. Takoer, pojedini sudionici
mogu sugerirati da su neke apstrakcije previe komplicirane i zbunjujue te predloiti
nove. Male kartice prisiljavaju sudionike na jednostavne i razumljive apstrakcije.
Pojedine kartice mogu biti smjetene u grupe ime se oznaava pripadnost pojedinoj
funkcionalnoj cjelini.
Iako takav postupak moe ponekad izmaknuti kontroli i pretvoriti se u
poslijepodnevno ludilo isfrustriranih i premalo plaenih informatiara, pokazao se vrlo
korisnim u poetnom postupku razvoja apstrakcija nekog sloenog sustava. Ovakvim
neformalnim sastankom mnogi ljudi esto izraze svoje zamisli koje inae nikada ne bi
ispriali na slinom sastanku formalnog karaktera.
564
LCDEkran
Ekran
Relacija biti oznaava definiciju novog tipa na osnovi ve postojeeg tipa. Njena
najea implementacija u C++ jeziku je pomou javnog nasljeivanja. Prilikom
nasljeivanja objekt klase LCDEkran e sadravati podobjekt klase Ekran i ta se
injenica vrlo esto interpretira na krivi nain pomou relacije posjedovati.
Relacija posjedovati opisuje odnos kada neki objekt kao dio svog internog ustroja
posjeduje ili sadri neki drugi objekt. Na primjer, Ekran e sadravati objekt Paleta
koji e definirati listu boja koje se trenutno mogu prikazati na ekranu. Ekran nije podtip
klase Paleta (niti obrnuto), on jednostavno sadrava paletu. Paleta e biti
implementirana kao podatkovni lan objekta Ekran.
Vidjeli smo da svaki objekt izvedene klase sadrava podobjekt osnovne klase.
Neiskusan korisnik objektno orijentirane tehnologije bi mogao zbog toga zakljuiti da se
Ekran jednostavno moe implementirati tako da se naslijedi klasa Paleta. Iako takvo
rjeenje moe korektno raditi, ono je nekorektno sa stanovita dizajna sustava. Svrha
nasljeivanja je opisati podtipizaciju, a ne sadravanje objekata. Takvu manu u dizajnu
sustava moglo bi se pokuati ispraviti tako da se Ekran izvede zatieno ili privatno, no
niti to nije korektno. Takav program moe raditi ispravno, ali e sigurno biti
nerazumljiv. Programeri koji rade na razvoju takvog sustava bit e zasigurno zbunjeni
navedenim pristupom rjeavanju problema. Zbog toga e biti vrlo jednostavno prilikom
pisanja programa nainiti pogreku, te e dobiveni kd gotovo sigurno biti neispravan.
Takoer, ako se prilikom razvoja sustava ustanovi da dotina apstrakcija nije sasvim
prikladna, bit e teko provesti izmjenu. Pomicanje pojedine klase u hijerarhiji prema
gore ili dolje e takoer biti vrlo teko.
Potrebno je biti oprezan prilikom odreivanja odnosa izmeu pojedinih klasa
te pokuati vrlo precizno odrediti je li neki odnos tipa biti ili posjedovati.
Relacija koristiti je najopenitija ona specificira da jedan objekt u svome radu samo
koristi neki drugi objekt. Pri tome ta dva objekta koji ulaze u interakciju nisu niti na
jedan drugi nain meusobno povezani (primjerice tako da je jedan objekt podatkovni
lan drugoga ili da postoje hijerarhijski odnosi izmeu njih). Ta dva objekta mogu
komunicirati putem njihovog javnog suelja, ili moda ak pomou privatnog suelja u
565
sluaju da je jedan deklariran kao prijatelj drugog. Ovakav odnos se u C++ jeziku
najee implementira pokazivaima i referencama izmeu objekata. U naem primjeru
moemo rei da e svaki Prozor koristiti Ekran za ispisivanje na njemu.
Osim tih osnovnih tipova odnosa izmeu objekata postoji niz podtipova tih odnosa.
Nakon identifikacije pojedinih odnosa moramo se odmah zatim zapitati je li taj odnos
jednosmjeran ili dvosmjeran. Jednosmjerni odnosi se lake implementiraju i zahtijevaju
manje izvornog kda za implementaciju. Zbog toga se oni i izvode znatno bre. No
dvosmjerni odnosi su mnogo fleksibilniji, pa je zato posao korisnika objekata znatno
pojednostavljen.
U naem sluaju odnos izmeu Ekrana i Prozora e gotovo sigurno biti
implementiran kao jednosmjeran Prozor e koristiti Ekran za ispis svojih podataka,
dok Ekran nee imati potrebe koristiti Prozor (barem na sadanjem stupnju razvoja
projekta toga nismo svjesni).
Gornji primjer odmah nas vodi na dodatna etiri podtipa odnosa: jedan na jedan,
jedan na vie, vie na jedan i vie na vie. U naem sluaju odnos izmeu prozora i
ekrana je vie na jedan: u nekom trenutku imat emo vie aktivnih prozora koji e
eljeti crtati na ekranu. Takoer, moemo se zapitati koliko je to vie: je li to neki fiksni
broj (na primjer dva) ili neki promjenjivi broj.
Vaan je i nain na koji se odnosi mijenjaju tijekom vremena. Pojedine veze vrijede
samo dok postoje povezani objekti. Na primjer, prozori se mogu u toku rada raunala
stvarati i unitavati. Veza prozora s ekranom postoji samo dok postoji prozor, kada
prozor biva uniten, veza takoer prestaje. Nadalje, veza prozora s ekranom nije
potrebna cijelo vrijeme; prozor pristupa ekranu dok eli obaviti nekakav ispis. Nakon
toga veza do sljedeeg ispisa nije potrebna. Svaki prozor moe prepustiti ekran za
vrijeme perioda dok ne obavlja ispis nekom drugom prozoru. Na taj nain se moe
odrediti protokol po kojem u svakom trenutku samo jedan prozor ima mogunost ispisa.
Da li e se takva implementacija primijeniti ovisi o nizu imbenika, kao to su fizika
svojstva stvarnog ekrana s kojim raspolaemo, operacijskim sustavom koji se koristi u
raunalu i slino. Radi jednostavnije implementacije neemo uvoditi kontrolu pristupa,
nego emo se zadrati na originalnom rjeenju kod kojeg vie prozora moe pristupati
ekranu odjednom.
19.6.1. Odnosi objekata u korisnikom suelju
U nastavku emo pokuati primijeniti navedena pravila kako bismo precizno odredili
odnose izmeu objekata koje smo izveli u prethodnom poglavlju.
Poet emo s klasom Ekran. Ona opisuje fiziku izlaznu jedinicu koja slui za
komunikaciju s korisnikom. Svakom ekranu je pridruena slika koja se sastoji od niza
toaka. Moemo definirati klasu Slika koja e opisivati sliku na ekranu. Ona e
omoguavati radnje kao to su postavljanje boje odreene toke na ekranu, itanje boje
neke toke i slino. Takoer e sadravati mehanizme za podravanje razliitih
razluivosti.
566
567
568
Prozor
Prozor
Prozor
S_PromjenomVelicine
S_Izbornikom
S_Zatvaranjem
MojProzor
S_PromjenomVelicine
S_Izbornikom
S_Zatvaranjem
MojProzor
Gornje rjeenje je korektno, ali jo uvijek uvodi dodatne komplikacije u hijerarhiju. Kao
i uvijek prilikom viestrukog nasljeivanja, tri klase koje uvode elemente za kontrolu
prozora mogu definirati lan istog naziva. Zbog toga e za pristup pojedinim lanovima
klase MojProzor biti neophodno navesti osnovnu klasu u kojoj je lan definiran.
Osnovnu dizajnersku pogreku smo napravili ve pri izradi apstraktnog modela.
Prozor s elementom za zatvaranje ili za promjenu veliine nije sutinski razliit od bilo
kojeg drugog prozora. Element za zatvaranje se moe promatrati kao objekt koji prozor
posjeduje, ili jednostavno kao modifikator koji mijenja nain na koji prozor funkcionira.
U gornjem rjeenju smo relaciju posjedovati pokuali realizirati pomou relacije biti.
Prvo bi rjeenje bilo generirati dodatne klase izvan hijerarhije prozora. Zatim bi
klasa UokvireniProzor morala uvesti metode za manipulaciju pojedinih elemenata
(dodavanje, uklanjanje, promjenu poloaja i slino) te za komunikaciju izmeu njih.
Takvo rjeenje je znatno korektnije od simulacije posjedovanja pomou nasljeivanja.
569
570
571
udioR; }
udioG; }
udioB; }
R, unsigned short G,
572
unsigned short B) {
udioR = R; udioG = G; udioB = B;
}
// suelje za HSB sustav
unsigned short dajH();
unsigned short dajS();
unsigned short dajBr();
//
//
//
void postaviHSB(unsigned short
unsigned short
dajB je ve deklariran,
pa moramo koristiti naziv
dajBr
H, unsigned short S,
B);
573
Ovo moemo protumaiti kao da je Boja(10, 250, 70) konstanta koja se nalazi s
desne strane operatora pridruivanja, te se pridruuje nekom elementu niza nizBoja.
Valja uoiti da zbog implementacije klase nije potrebno uvoditi posebne operatore
pridruivanja ili konstruktore kopije.
Prilikom definicije suelja vrlo je vano ispravno odrediti atribute pojedinih
elemenata suelja. U C++ jeziku pod atributima se primarno misli na virtualnost,
statinost, konstantnost i javnost. Pozabavimo se malo pravilima kojima se odreuju ti
atributi.
Vrlo je vano ispravno odrediti je li neki funkcijski lan klase virtualan ili ne.
Pravilo od palca (engl. rule of the thumb) koje se nalazi u veini knjiga o objektnom
programiranju kae da sve funkcijske lanove, za koje se oekuje da e biti promijenjeni
prilikom nasljeivanja, treba obavezno uiniti virtualnima. Lijepo reeno, no ne ba
previe korisno! Naime, sada je odgovor prebaen na drugo pitanje: A koji e
funkcijski lanovi biti promijenjeni prilikom nasljeivanja?!
Odgovor na gornje pitanje moe se dobiti ako se shvati bit virtualnosti. Virtualan
funkcijski lan specificira akciju koja je vezana uz tip podataka na kojemu se obavlja.
Ako se akcija za razliite tipove obavlja na isti nain, tada ona nije vezana uz tip te ju
nije potrebno uiniti virtualnom. tovie, preporuka je da se virtualni funkcijski lanovi
koriste samo kada je to nuno.
Poziv virtualnog lana najee se realizira pomou indirekcije (poziva
preko pokazivaa) koji se izvodi sporije od direktnog poziva. Virtualan lan
se ne moe uiniti umetnutim, ime se jo vie degradira uinkovitost
programa.
Vratimo se na primjer klase Boja i promotrimo jesmo li ispravno odredili tip
funkcijskih lanova. Prvo je pitanje da li uope oekujemo da e ikad biti potrebno
naslijediti klasu Boja. Ta je klasa razvijena iskljuivo kao pomona klasa za klasu
Paleta. Teko je zamisliti koji bi bio smisao u nasljeivanju klase Boja. Zbog toga
nema niti smisla govoriti o virtualnim funkcijskim lanovima njima bismo samo
znatno usporili izvoenje programa.
Razmotrimo suelje klase Paleta. Velika je slinost izmeu palete i niza paleta
zapravo i jest niz boja. Zbog toga su operacije nad paletom sline operacijama s
nizovima: potrebno je definirati veliinu palete te osigurati pristup pojedinim
lanovima.
Veliina palete se moe definirati ve prilikom njenog stvaranja, dakle u
konstruktoru. Takoer, korisnik prilikom rada s raunalom moe htjeti promijeniti broj
574
boja, pa je veliinu palete potrebno mijenjati i u toku rada. Uvest emo stoga funkcijski
lan BrojBoja() koji e podesiti veliinu palete na eljeni broj boja. lan
DajBrojBoja() e omoguiti itanje broja boja u paleti. Pristup pojedinoj boji moe
se realizirati na razne naine. Jedan od vrlo prikladnih je adresiranje palete pomou
operatora [] time se naglaava itatelju programskog kda da je paleta neka vrsta
niza. Napisat emo kako bi izgledala deklaracija klase Paleta:
class Paleta {
public:
Paleta(int brBoja);
void BrojBoja(int brBoja);
int DajBrojBoja();
?? operator [](int indeks);
// nismo jo odredili
// povratni tip
};
Razmotrimo koji bi tip bilo najprikladnije vratiti iz operatora []. Jedno od moguih
rjeenja koje se namee samo po sebi ve je opisano u poglavlju o preoptereenju
operatora kod definicije klase Matrica, a to je vratiti referencu na objekt Boja:
class Paleta {
// ...
Boja &operator [](int indeks);
// ...
};
No takvo rjeenje ne mora uvijek odgovarati. Ono zapravo pretpostavlja tono odreenu
implementaciju koja ne mora uvijek biti ispravna. Naime, u gornjem sluaju
najjednostavnija implementacije bi bila kada bi klasa Paleta sadravala niz objekata
klase Boja. Operator [] jednostavno vraa referencu na odreeni element niza. Gornje
pridruivanje se tada provodi tako da se pozove operator = za klasu Boja. Klasa
Paleta vie nema nikakvog nadzora nad pridruivanjem svojim lanovima nakon
pridruivanja kontrola se ne vraa vie u klasu pa dodatna obrada podataka nije
mogua.
Meutim, u naem bi sluaju upravo to bilo potrebno: nakon dodjele odreenom
elementu palete potrebno je uskladiti sistemske registre na grafikoj kartici s RGB
575
vrijednostima dodane boje. tovie, paleta uope ne mora biti realizirana tako da ona
sadri niz objekata Boja boje u tekuoj paleti se nalaze zapisane u registrima grafike
kartice. Implementacija palete moe prilikom dohvaanja pojedine boje jednostavno
proitati boju iz registra te vratiti privremeni objekt klase Boja koji e sadravati
proitane vrijednosti. Klasa Paleta zapravo slui kao omota (engl. wrapper) oko
sistemskih poziva grafike kartice te omoguava koritenje jednostavnog objektnog
suelja umjesto sloenih sistemskih poziva.
Zbog svega gore navedenoga, operator [] e vraati privremeni objekt klase Boja.
Postavljanje boje e se obavljati pomou funkcijskog lana PostaviBoju() koji kao
parametre ima cijeli broj za identifikaciju mjesta u paleti na koje se boja postavlja, te
referencu na objekt klase Boja. Konana deklaracija klase Paleta izgledat e ovako:
class Paleta {
public:
Paleta(int brBoja);
void BrojBoja(int brBoja);
int DajBrojBoja();
Boja operator [](int indeks);
void PostaviBoju(int indeks, Boja &novaBoja);
};
Sljedei zadatak koji nas eka jest definiranje suelja klase Slika. Ta klasa mora
uvesti mehanizme kojima se moe crtati na izlaznoj jedinici raunala. Slika se sastoji iz
niza toaka poredanih okomito i vodoravno. Klasa Tocka opisuje svaku toku na slici.
Osnovno svojstvo toke je boja (tonije reeno, indeks boje u paleti), pa e ta klasa
sadravati funkcijske lanove za postavljanje i itanje boje. Takoer, kako je indeks u
biti cijeli broj, uvest emo i operator konverzije klase Tocka u cijeli broj, te konverziju
cijelog broja u objekt klase Tocka. Evo mogue deklaracije suelja:
class Tocka {
public:
Tocka(int ind = 0); // konstruktor i konverzija
// int Tocka
operator int();
// konverzija Tocka int
int DajIndeks();
void PostaviIndeks(int ind);
};
Budui da nije za oekivati da e klasa Tocka ikad biti naslijeena, niti jedan
funkcijski lan nije virtualan.
576
577
Slika(int pocetniNacin);
virtual ~Slika();
virtual
virtual
virtual
virtual
int DajXRazlucivost();
int DajYRazlucivost();
int DajBrojBoja();
void PostaviNacinPrikaza(int nacin);
Gornja deklaracija je samo primjer koji pokazuje kako bi se deklaracija mogla obaviti
to nipoto nije potpuna deklaracija koja bi obuhvatila svu potrebnu funkcionalnost klase
Slika. Objasnit emo ukratko pojedine elemente suelja.
Konstruktor klase Slika ima jedan cjelobrojni parametar kojim se identificira
poetni nain prikaza. Neemo ulaziti dublje u problematiku to sainjava pojedini
nain prikaza te kako se oni numeriraju to ovisi o konkretnom raunalu na kojemu
radimo. Recimo samo da se pojedini nain prikaza identificira cijelim brojem kojim se
odreuje razluivost u x i y smjeru te broj raspoloivih boja.
Destruktor ima ulogu oslobaanja svih resursa koje je objekt zauzeo, a definiran je
virtualnim kako bi se omoguilo nasljeivanje klase. Tako sve izvedene klase mogu
definirati svoje destruktore, a virtualni mehanizam e omoguiti ispravno unitavanje
objekata.
U zatienom dijelu suelja naveden je lan PostaviPaletu(). Njegova je uloga
uspostavljanje veze izmeu palete i slike. lan je potrebno pozvati prije nego to se
Slika pone koristiti i kao parametar mu proslijediti pokaziva na objekt klase
Paleta. Klasa Slika e se time konfigurirati tako da e koristiti proslijeeni objekt za
identifikaciju pojedinih boja. Klasa Ekran je uinjena prijateljem klase Slika primarno
zato da bi se omoguio pristup tom zatienom funkcijskom lanu.
Slijedi niz lanova: DajXRazlucivost(), DajYRazlucivost() te
DajBrojBoja() koji omoguavaju itanje podataka o trenutno postavljenom nainu
prikaza. I oni su uinjeni virtualnima kako bi se osiguralo da se za svaku posebnu
grafiku
karticu
ispravno
odredi
traeni
podatak.
Funkcijski
lan
PostaviNacinPrikaza() omoguava naknadnu promjenu naina prikaza tako da mu
se kao parametar proslijedi identifikator naina prikaza.
Slijede lanovi za crtanje. lan CrtajTocku() e nacrtati toku na zadanom
mjestu na ekranu tako da mu se kao parametar proslijede koordinate toke i sam objekt
klase Tocka koji e identificirati toku. lan CitajTocku() radi upravo obrnuto: uz
zadane koordinate toke on e vratiti objekt klase Tocka koji e identificirati toku na
578
zadanom mjestu. lan Linija iscrtava liniju tako da mu se zadaju koordinate poetne i
zavrne toke i indeks boje iz palete. lan Kvadrat() e nacrtati kvadrat tako da mu se
zadaju koordinate gornjeg lijevog i donjeg desnog kuta te indeks boje iz palete.
Klasa Ekran slui objedinjavanju rada slike i palete. Ona e sadravati konstruktor
u kojemu e se stvoriti po jedan objekt klase Slika i jedan klase Paleta. Javno suelje
klase e sadravati samo dva funkcijska lana pomou kojih e se moi dobiti referenca
na objekt Slika i na objekt Paleta, tako da se moe pristupati njihovim funkcijskim
lanovima. Evo deklaracije:
class Ekran {
public:
Ekran();
virtual ~Ekran();
Slika &DajSliku();
Paleta &DajPaletu();
};
Pomone klase Mis i Tipkovnica takoer slue tome da se sklop raunala uini
apstraktnim te prikae pomou objekta. Klasa Mis mora sadravati funkcijske lanove
za inicijalizaciju i itanje poloaja mia. Klasa Tipkovnica e sadravati funkcijske
lanove za itanje kdova tipki koje su pritisnute. Kao i prilikom razvoja klase Slika,
moemo ustanoviti da postoje razne vrste mieva i tipkovnica, pa emo funkcijske
lanove definirati virtualnima, a u izvedenim klasama navesti kd za rad s konkretnim
sklopom. Tono i precizno formiranje suelja tih klasa previe bi zadiralo u podruje
sistemskog programiranja, pa emo njihove deklaracije izostaviti i zadovoljiti se
gornjim opisom.
Na slian nain bi se definirala suelja i preostalih klasa Prozor,
UokvireniProzor, Izbornik i Prometnik. Definirati suelje tih klasa ne bi bilo vrlo
jednostavno iz razloga to uope nismo dovoljno precizno definirali svojstva tih klasa.
Koje su sve operacije dozvoljene na prozoru? Kako on odgovara na pojedinu operaciju?
Kako izgleda prozor na ekranu? Kakav operacijski sustav se koristi na naem raunalu?
Sve su to stvari o kojima treba voditi rauna prilikom izrade biblioteke grafikog
suelja. Zbog toga emo ovdje zavriti s definicijom suelja, a daljnju razradu problema
prepustiti nadobudnim itateljima.
579
580
Prilog
581
571
A. Standardna biblioteka
Doite i odaberite neto iz moje biblioteke,
odagnajte tako svoju tugu.
William Shakespeare, Titus Andronicus (1590)
ANSI standard jezika C++ prvenstveno opisuje sintaksu naredbi jezika. Meutim, zbog
to bolje prenosivosti kda, ANSI komitet za standardizaciju je odluio u standard
ukljuiti i definicije elemenata standardne biblioteke. Osnovna namjena standardne
biblioteke jest pruiti efikasne i pouzdane predloke, klase i funkcije te time oslobodi
pisca kda od napornog pisanja trivijalnih struktura podataka i algoritama. Standardna
C++ biblioteka sadri definicije:
makro funkcija i makro imena,
simbolikih konstanti (vrijednosti),
tipova,
predloaka,
klasa,
funkcija,
objekata.
Deklaracije i definicije elemenata standardne C++ biblioteke raspodijeljene su kroz
trideset dvije datoteke zaglavlja, navedene u tablici A.1.
Tablica A.1. Zaglavlja standardne C++ biblioteke
<algorithm>
<bitset>
<complex>
<deque>
<exception>
<fstream>
<functional>
<iomanip>
<ios>
<iosfwd>
<iostream>
<istream>
<iterator>
<limits>
<list>
<locale>
<map>
<memory>
<new>
<numeric>
<ostream>
<queue>
<set>
<sstream>
<stack>
<stdexcept>
<streambuf>
<string>
<typeinfo>
<utility>
<valarray>
<vector>
572
<climits>
<clocale>
<cmath>
<csetjmp>
<csignal>
<cstardg>
<cstddef>
<cstdio>
<cstdlib>
<cstring>
<ctime>
<cwchar>
<cwtype>
Postoji znaajna razlika u odnosu na standard C jezika. Naime, svi nazivi C datoteka
zaglavlja imaju prefiks c, kako bi se naznailo da dotina datoteka pripada C standardu.
Na primjer, sadraj zaglavlja cmath odgovara sadraju C zaglavlja math.h. Takoer,
standard C++ jezika dozvoljava i ukljuivanje starih datoteka zaglavlja koje imaju
nastavak .h. Razlika je u tome to su u datotekama koje poinju sa c svi identifikatori
smjeteni u imenik std, ime su uklonjeni iz globalnog podruja imena. Radi
kompatibilnosti preporuuje se koritenje novih datoteka. Krasno, jo samo da su nam
dostupne...
Prema namjeni pojedinih komponenti, standardna biblioteka podijeljena je u deset
kategorija:
573
574
LC_CTYPE
LC_MONETARY
LC_NUMERIC
LC_TIME
L_tmpnam
MB_CUR_MAX
NULL <cstddef>
NULL <cstdio>
NULL <cstring>
NULL <ctime>
NULL <cwchar>
offsetoff
RAND_MAX
SEEK_CUR
SEEK_END
SEEK_SET
setjmp
SIGABRT
SIGFPE
SIGILL
SIGINT
SIGSEGV
SIGTERM
SIG_DFL
SIG_ERR
SIG_IGN
stderr
stdin
stdout
TMP_MAX
va_arg
va_end
va_start
WCHAR_MAX
WCHAR_MIN
WEOF <cwchar>
WEOF <cwctype>
_IOFBF
_IOLBF
_IONBF
Makro funkcija koja testira uvjet test. Ako je rezultat testa nula, prekida se izvoenje
programa pozivom funkcije abort(), a na jedinici za ispis pogreke ispisuje se poruka
s nazivom datoteke izvornog kda i brojem linije u kojoj je pogreka nastupila.
Djelovanje makro funkcije assert()se moe iskljuiti tako da se ispred
pretprocesorske naredbe za ukljuivanje zaglavlja cassert (ili assert.h) definira
makro ime NDEBUG (naredbom #define NDEBUG).
#include <cerrno>
#include <cmath>
EDOM, ERANGE
EDOM
ERANGE
575
#include <cstdio>
EOF
Vrijednost nul-pokazivaa.
#include <cstdlib>
RAND_MAX
Najvea vrijednost koju moe vratiti funkcija rand(). Funkcija rand() slui
generiranju pseudo-sluajnih brojeva.
576
#include <cstdarg>
void va_start(va_list ap, lastfix);
type va_arg(va_list ap, type);
void va_end(va_list ap);
va_arg()
va_end()
FLT_DIG
FLT_EPSILON
FLT_MANT_DIG
FLT_MAX
FLT_MAX_10_EXP
FLT_MAX_EXP
FLT_MIN
FLT_MIN_10_EXP
FLT_MIN_EXP
FLT_RADIX
FLT_ROUNDS
INT_MAX
INT_MIN
LDBL_DIG
LDBL_EPSILON
LDBL_MANT_DIG
LDBL_MAX
LDBL_MAX_10_EXP
LDBL_MAX_EXP
LDBL_MIN
LDBL_MIN_10_EXP
LDBL_MIN_EXP
LONG_MAX
LONG_MIN
MB_LEN_MAX
SCHAR_MAX
SCHAR_MIN
SHRT_MAX
SHRT_MIN
UCHAR_MAX
UINT_MAX
ULONG_MAX
USHRT_MAX
577
ptrdiff_t <cstddef>
sig_atomic_t
size_t <cstddef>
size_t <cstdio>
size_t <cstring>
size_t <ctime>
streambuf
streamoff
streampos
string
stringbuf
terminate_handler
time_t
unexpected_handler
va_list
wctrans_t
wctype_t
wfilebuf
wifstream
wint_t <cwchar>
wint_t <cwctype>
wios
wistream
wistringstream
wofstream
wostream
wostringstream
wstreambuf
wstreampos
wstring
wstringbuf
578
Tablica
Tablica
A.7. Standardne
A.6. Standardna
strukture
biblioteka
definirane
predloaka
predlocima
bidirectional_iterator
allocator
binary_function
auto_ptr
divides
back_insert_iterator
equal_to
basic_filebuf
forward_iterator
basic_ifstream
greater
basic_ios
greater_equal
basic_istream
input_iterator
basic_istringstream
basic_ofstream
basic_ostream
basic_ostringstream
basic_streambuf
basic_string
basic_stringbuf
binary_negate
binder1st
binder2nd
bitset
codecvt
codecvt_byname
collate
collate_byname
complex
ctype
ctype_byname
deque
front_insert_iterator
gslice_array
indirect_array
insert_iterator
istreambuf_iterator
istream_iterator
list
ios_traits
map
negate
less
mask_arraynot_equal_to
less_equal
messages pair
logical_and
messages_byname
plus
logical_not
moneypunctrandom_access_iterator
logical_or
moneypunct_byname
string_char_traits
minus
money_get times
modulus money_put unary_function
multimap
multiset
numeric_limits
numpunct
num_get
num_put
ostreambuf_iterator
ostream_iterator
pointer_to_binary_function
pointer_to_unary_function
priority_queue
queue
raw_storage_iterator
reverse_bidirectional_iterator
reverse_iterator
set
slice_array
stack
time_get
time_get_byname
time_put
time_put_byname
unary_negate
valarray
vector
koji svakom lanu dodjeljuje jedinstveni klju (jedan lan je vezan s kljuem koji je
jedinstven na nivou kontejnera) i osigurava brzo dohvaanje kljua. multiset
dozvoljava jednake kljueve (vie lanova mogu imati isti klju). Nasuprot tome, map i
multimap osiguravaju dohvaanje podataka nekog drugog tipa prema zadanom kljuu.
Klasa basic_string, deklarirana u zaglavlju string, namijenjena je za
rukovanje znakovnim nizovima. Za nju su definirani i neki vaniji operatori, poput +,
!=, ==, << i >>, to moe znaajno pojednostavniti operacije sa znakovnim nizovima.
I na kraju, spomenimo jo i klasu complex (deklariranu u istoimenom zaglavlju
complex), koja pojednostavnjuje raun sa kompleksnim brojevima. Za nju su takoer
definirani svi aritmetiki operatori i operatori za ispis na tok (!=, *, *=, +, +=, -, -=, /,
/=, <<, ==, >>) i funkcije (npr. abs(), acos(), exp(), log()) koji su dozvoljeni s
kompleksnim brojevima.
Osim klasa definiranih predlocima, standardna biblioteka sadri i strukture
definirane predlocima (tablica A.7), standardne klase (tablica A.8) i standardne
strukture (tablica A.9). Standardne klase (npr. bad_alloc, bad_cast,
domain_error) uglavnom su namijenjene za hvatanje iznimki.
579
ctype_byname<char>
domain_error
exception
gslice
invalid_argument
ios_base
length_error
locale
locale::facet
locale::id
logic_error
out_of_range
overflow_error
range_error
runtime_error
slice
type_info
vector<bool,allocator>
bidirectional_iterator_tag
codecvt_base
ctype_base
forward_iterator_tag
input_iterator_tag
ios_traits<char>
ios_traits<wchar_t>
lconv
money_base
money_base::pattern
nothrow
output_iterator
output_iterator_tag
random_access_iterator_tag
string_char_traits<char>
string_char_traits<wchar_t>
time_base
tm <ctime>
580
B. Standardne funkcije
Franklin: Jeste li ikada, gospodine Ravnatelju,
pomislili da su vai standardi moda malo zastarjeli?
Ravnatelj: Naravno da su zastarjeli. Standardi su
uvijek zastarjeli. To je ono to ih ini standardima.
Alen Bennet, engleski glumac i pisac,
Forty Years On (1969)
Standardna C++ biblioteka prua 208 standardnih funkcija iz C biblioteke (tablica B.1).
Veina matematikih funkcija je preoptereena za objekte klase complex. Takoer,
fgetpos
fgets
fgetwc
fgetws
floor
fmod
fopen
fprintf
fputc
fputs
fputwc
fputws
fread
free
freopen
frexp
fscanf
fseek
fsetpos
ftell
fwide
fwprintf
fwrite
fwscanf
getc
getchar
getenv
gets
getwc
getwchar
gmtime
isalnum
isalpha
iscntrl
isdigit
isgraph
islower
isprint
ispunct
isspace
isupper
iswalnum
iswalpha
iswcntrl
iswctype
iswdigit
iswgraph
iswlower
iswprint
iswpunct
iswspace
iswupper
iswxdigit
isxdigit
labs
ldexp
ldiv
localeconv
localtime
log
log10
longjmp
malloc
mblen
mbrlen
mbrtowc
mbsinit
mbsrtowcs
mbstowcs
mbtowc
memchr
memcmp
memcpy
memmove
memset
mktime
modf
perror
pow
printf
putc
puts
putwc
putwchar
qsort
raise
rand
realloc
remove
rename
rewind
scanf
setbuf
setlocale
setvbuf
signal
sin
sinh
sprintf
sqrt
srand
sscanf
strcat
strchr
strcmp
strcoll
strcpy
strcspn
strerror
strftime
strlen
strncat
strncmp
strncpy
stroul
strpbrk
strrchr
strspn
strstr
strtod
strtok
strtol
strxfrm
swprintf
swscanf
system
tan
tanh
time
tmpfile
tmpnam
tolower
toupper
towctrans
towlower
towupper
ungetc
ungetwc
vfwprintf
vprintf
vscanf
vsprintf
vswprintf
vwprintf
wcrtomb
wcscat
wcschr
wcscmp
wcscoll
wcscpy
wscspn
wcsftime
wcslen
wcsncat
wcsncmp
wcsncpy
wcspbrk
wcsrchr
wcsrtomba
wcsspn
wcsstr
wcstod
wcstok
wcstol
wcstomba
wcstoul
wcsxfrm
wctob
wctomb
wctrans
wctype
wmemchr
wmemcmp
wmemcpy
wmemmove
wmemset
wprintf
wscanf
Korak 6: Implementacija
581
C++ standard definira posebne tipove kojima se podrava vrlo brza paralelna obrada
numerikih podataka na jakim strojevima. Za tu svrhu uveden je predloak valarray
koji definira polje matematikih vrijednosti. Parametar predloka je tip vrijednosti, pa
emo tako polje realnih brojeva dobiti kao valarray<float>, a polje kompleksnih
brojeva sa valarray<complex>. Za pojedine funkcije su u matematikim
bibliotekama definirani i predloci funkcija koje barataju poljem valarray. Tako
postoji poseban predloak, primjerice, funkcije sqrt(), koji kao parametar uzima
valarray te rauna korijen iz pojedine vrijednosti. Definirani su i predloci operatora
koji omoguavaju raunanje s poljima vrijednosti istog tipa. Na taj nain C++ standard
omoguava da se za pojedinu raunalnu platformu optimizira raunanje s poljem
podataka na najprikladniji nain (pojedine arhitekture procesora posjeduju zaseban set
instrukcija za obradu polja numerikih podataka), tako da se specijalizira pojedini
predloak za odreeni tip.
Slijedi kratak opis vanijih funkcija, grupiranih prema operacijama koje izvode.
Budui da je veina funkcija preuzeta iz standardne C biblioteke, pri navoenju naredbe
za ukljuivanje, osim imena zaglavlja C++ biblioteka, navedena su i imena zaglavlja iz
C biblioteke. Njih se moe prepoznati po nastavku .h. Ovo je praktino ako su
korisniku na raspolaganju starije biblioteke. Iz opisa su izuzete standardne C funkcije za
ulazno-izlazne tokove, budui da tokovi iz iostream biblioteke (opisane u poglavlju
16) podravaju sve operacije koje su na raspolaganju C programerima u stdio.h
biblioteci. tovie, iostream prua mnotvo dodatnih pogodnosti pa vjerujemo da,
kada se jednom saivi sa iostream bibliotekom, programeru uope nee nedostajati Cfunkcije printf(), scanf() i sline. Isto tako, iskusni C++ programer e vrlo rijetko
posegnuti za C funkcijama za alokaciju i oslobaanje dinamiki alocirane memorije
(malloc(), calloc(), free()), budui da jezik C++ ima za to ugraene operatore
new i delete.
Ove funkcije slue za klasifikaciju znaka. Argument znak je kd znaka kojeg elimo
klasificirati. Funkcije vraaju true ako je zadovoljen uvjet ispitivanja; u protivnom je
582
#include <cctype>
int isalnum(int znak);
int isalpha(int znak);
int iscntrl(int znak);
int isdigit(int znak);
int isgraph(int znak);
int islower(int znak);
int isprint(int znak);
int ispunct(int znak);
int isspace(int znak);
int isupper(int znak);
int isxdigit(int znak);
Korak 6: Implementacija
583
#include <cstring>
// stari naziv: <string.h>
char *strcat(char *pocetak, const char *nastavak);
char *strncat(char *pocetak, const char *nastavak, size_t maxDuljina);
Funkcija u nizu niz trai prvu pojavu znaka znak. Ako znak nije pronaen, kao rezultat
se vraa nul-pokaziva. U podruje pretraivanja ukljuen je i zakljuni nul-znak.
#include <cstring>
// stari naziv: <string.h>
int strcmp(const char *niz1, const char *niz2);
int strncmp(const char *niz1, const char *niz2, size_t maxDuljina);
int strcoll(char char *niz1, char *niz2);
Funkcija strcmp() usporeuje niz1 i niz2, znak po znak, sve dok su odgovarajui
znakovi meusobno jednaki ili dok ne naie na zakljuni nul-znak. Rezultat usporedbe
je:
< 0 ako je niz1 manji od niz2 (svrstano po abecedi niz1 dolazi prije niz2),
0 ako su niz1 i niz2 meusobno jednaki,
> 0 ako je niz1 vei od niz2 (svrstano po abecedi niz1 dolazi iza niz2).
Usporedba se radi po slijedu znakova koji se koristi na dotinom raunalu. Valja uoiti
da u najeem ASCII slijedu sva velika slova prethode malim slovima.
Funkcija strncmp() radi jednaku usporedbu kao i strcmp(), ali usporeuje samo
do najvie maxDuljina znakova.
Funkcija strcoll() radi usporedbu u skladu sa tekuom postavom zemljopisne
regije. Ta postava se moe podesiti funkcijom setlocale(), pri emu se kao
kategorija postavke navodi LC_COLLATE. Za detaljnije objanjenje pogledajte opis
funkcije setlocale().
#include <cstring>
// stari naziv: <string.h>
char *strcpy(char *odrediste, const char *izvornik);
char *strncpy(char *odrediste, const char *izvornik, size_t maxDuljina);
584
poziva funkcije treba alocirati dovoljan prostor za preslikani niz, jer funkcija ne
provjerava je li za preslikani niz na mjestu odrediste odvojeno dovoljno mjesta.
Funkcija strncpy() preslikava do najvie maxDuljina znakova niza izvornik
na mjesto odrediste. Treba uoiti da, ako je maxDuljina manja ili jednaka duljini
niza izvornik, nul-znak nee biti preslikan pa niz odrediste moe ostati
nezakljuen.
#include <cstring>
size_t *strlen(const char *niz);
Funkcija pretrauje niz niz do prve pojave bilo kojeg znaka iz niza znakovi. Kao
rezultat vraa pokaziva na prvu pojavu ili nul-pokaziva ako nijedan znak nije
pronaen.
#include <cstring>
// stari naziv: <string.h>
size_t strspn(const char *niz, const char *podNiz);
size_t strcspn(const char *niz, const char *podNiz);
Funkcija strspn() trai u nizu niz mjesto gdje se spominje prvi znak koji nije
naveden u nizu podNiz. Kao rezultat ona vraa indeks pronaenog znaka. Na primjer,
izvoenjem naredbe
cout << strspn("crnac", "nerc") << endl;
ispisat e se broj 3, jer se tek znak 'a' u prvom nizu ne pojavljuje u drugom nizu, a
nalazi se na poziciji 3.
Funkcija strcspn() radi upravo suprotno: ona e vratiti indeks prvog znaka niza
niz koji je sadran u nizu podNiz. Na primjer, donja naredba e ispisati 2, jer je znak
'n' prvi znak iz drugog niza koji se pojavljuje u prvome te se nalazi na poziciji 2:
cout << strspn("crnac", "antimon");
Korak 6: Implementacija
585
#include <cstring>
// stari naziv: <string.h>
const char *strstr(const char *niz, const char *podNiz);
char *strstr(char *niz, const char *podNiz);
Funkcija pretrauje niz i trai prvu pojavu niza podNiz (bez njegovog zakljunog nulznaka). Rezultat je pokaziva na poetak prve pojave niza podNiz ili nul-pokaziva ako
podNiz nije sadran u niz-u.
#include <cstring>
// stari naziv: <string.h>
const char *strrchr(const char *niz, int znak);
char *strrchr(char *niz, int znak);
Funkcija pretrauje niz i trai zadnju pojavu znaka znak. Funkcija pretrauje niz od
njegova kraja (ukljuujui i zakljuni nul-znak). Ako je znak pronaen, funkcija vraa
pokaziva na zadnju pojavu znaka u nizu; ako nema znak-a u niz-u, tada vraa nulpokaziva.
#include <cstring>
// stari naziv: <string.h>
char *strtok(char *niz, const char *granicnici);
586
cvri
cvrak
#include <locale>
template <class charT> tolower(charT znak, const locale &loc) const;
template <class charT> toupper(charT znak, const locale &loc) const;
#include <cctype>
// stari naziv: <ctype.h>
int tolower(int znak);
int toupper(int znak);
Ako je znak veliko slovo, funkcija tolower() ga pretvara u njegovo malo slovo
('A'...'' u 'a'...''). Funkcija toupper() ini obrnuto: ako je znak malo slovo,
pretvara ga u njegovo veliko slovo. Ostale znakove funkcije ostavljaju
nepromijenjenima. Povratna vrijednost je kd (eventualno) pretvorenog znaka. Funkcije
iz standardne C++ biblioteke deklarirane su predlocima, gdje charT moe biti bilo koji
tip koji se moe svesti na char ili wchar_t. Drugi argument jest referenca na objekt
klase locale koja specificira mjesne (zemljopisne) parametre, kao to su klasifikacija
znakova, formati zapisa brojeva, datuma i vremena i sl. Istoimene funkcije iz standardne
C biblioteke su tipa int.
Pretvara znakovni niz niz u realni broj tipa double. Broju u znakovnom nizu smiju
prethoditi praznine. Broj smije biti zapisan u bilo kom formatu dozvoljenom za realne
brojeve, ukljuujui i znanstveni zapis. Prvi nedozvoljeni znak u nizu prekida
konverziju. Funkcija vraa pretvorenu vrijednost ulaznog niza. Ako nastupi brojani
preljev prilikom pretvorbe (primjerice ako je broj u znakovnom nizu prevelik ili
premali), funkcija vraa HUGE_VAL, te postavlja globalnu varijablu errno na ERANGE
(Range Error).
Funkcija atof() je slina funkciji strtod(), ali osigurava bolje prepoznavanje
pogreaka. Izvoenjem naredbi:
char *niz = " -1.23e-4 take money and run";
double broj = atof(niz);
cout << broj << endl;
587
Korak 6: Implementacija
#include <cstdlib>
int atoi(const char *niz);
long atol(const char *niz);
Funkcija atoi() pretvara znakovni niz niz u cijeli broj tipa int, a funkcija atol() u
cijeli broj tipa long. Broju u znakovnom nizu smiju prethoditi praznine te predznak.
Prvi nedozvoljeni znak prekida konverziju. Funkcije ne provjeravaju pojavu preljeva.
Kao rezultat vraaju pretvoreni cijeli broj, a ako se pretvoreni broj ne moe svesti na
int, odnosno long, vraaju 0. Tako e se izvoenjem primjera
char *niz = "1234.56";
int numera = atoi(niz);
cout << numera << endl;
Funkcija strtod() pretvara znakovni niz u broj tipa double. U znakovnom nizu niz
koji sadri tekstovni prikaz realnog broja, samom broju smiju prethoditi praznine. Broj
smije biti prikazan u bilo kojem obliku dozvoljenom za prikaz realnih brojeva, bez
praznina unutar broja. Ako je pokazKraja prilikom ulaska u funkciju razliit od nulpokazivaa, tada ga funkcija usmjerava na znak koji je prekinuo slijed uitavanja. Slijed
uitavanja se prekida im se naie na neki znak koji ne moe biti sastavni dio broja.
Stoga e se izvoenjem sljedeeg primjera:
char *niz = " -1.23e-1hahaha", *pokazKraja;
double broj = strtod(niz, &pokazKraja);
// radi ispisa odsijeca se niz iza dozvoljenih znakova
*pokazKraja = '\0';
cout << "Broj " << niz << " je pretvoren u " << broj << endl;
Funkcija strtod() slina je funkciji atof(), ali potonja bolje prepoznaje pogreke
prilikom pretvorbe.
#include <cstdlib>
// stari naziv: <stdlib.h>
long strtol(const char *niz, char **pokazKraja, int baza);
Funkcija pretvara znakovni niz u broj tipa long. U znakovnom nizu niz koji sadri
tekstovni prikaz cijelog broja koji se eli pretvoriti, samom broju smiju prethoditi
praznine. Takoer, neposredno ispred broja smiju se nalaziti predznak ('+' ili '-'),
588
'0' (za sluaj da niz sadri oktalni prikaz broja) ili 'x', odnosno 'X' (za sluaj da niz
sadri heksadekadski prikaz broja). Baza moe biti cijeli broj izmeu ukljuivo 2 do
ukljuivo 36. Ako je pokazKraja prilikom ulaska u funkciju razliit od nulpokazivaa, tada ga funkcija usmjerava na znak koji je prekinuo slijed uitavanja. Slijed
uitavanja se prekida im se naie na neki znak koji nije neki od gore spomenutih
znakova ili ne moe biti znamenka u pripadajuoj bazi. Na primjer, za heksadekadske
brojeve (baza 16) dozvoljeni znakovi su svi dekadski brojevi '0'...'9' te slova
'a'...'f', odnosno 'A'...'F'. Tako e u sljedeem primjeru biti uitane samo prve tri
znamenke, jer u oktalnom prikazu znamenka 8 nije dozvoljena:
char *niz = " -01188", *pokazKraja;
int baza = 8;
long broj = strtol(niz, &pokazKraja, baza);
// radi ispisa odsijeca niz iza vaeih oktalnih znamenki
*pokazKraja = '\0';
cout << "Broj " << niz << " u bazi " << baza << " je "
<< broj << endl;
Funkcije pretvaraju vrijeme koje im se prenosi kao argument, u znakovni niz oblika:
Wed Jan 15 01:23:45 1997\n\0
589
Korak 6: Implementacija
Uoimo da funkcija na kraj niza, neposredno ispred zakljunog nul-znaka dodaje znak
za novi redak '\n', tako da za ispis nije koriten manipulator endl.
Budui da je niz koji vraa funkcija asctime(), odnosno ctime() statika
varijabla, on e biti prepisan pri svakom novom pozivu funkcija asctime(), odnosno
ctime() ako ga elimo sauvati, valja ga preslikati u neki drugi znakovni niz.
#include <ctime>
clock_t clock(void);
#include <ctime>
// stari naziv: <time.h>
double difftime(time_t trenutak2, time_t trenutak1);
Funkcija gmtime() pretvara kalendarsko vrijeme u GMT (Greenwich Mean Time), dok
funkcija localtime()pretvara kalendarsko vrijeme u lokalno vrijeme. Argument obje
590
//
//
//
//
//
Ako je tm_isdst pozitivan, tada je aktiviran ljetni pomak vremena, ako je nula tada je
ljetni pomak vremena neaktivan, a ako je negativan tada informacija nije dostupna.
Valja paziti da je struktura koju stvaraju gmtime(), odnosno localtime() statiki
alocirana, te se prepisuje pri svakom novom pozivu funkcije. Stoga, ako elimo sauvati
oitano vrijeme, moramo strukturu preslikati u neki drugi objekt.
Sljedee naredbe e ispisati dananji datum i tono vrijeme:
time_t ura;
tm *cajt;
ura = time(NULL);
cajt = localtime(&ura);
cout << "Danas je "
<< cajt->tm_mday << "."
<< (cajt->tm_mon + 1) << "."
<< (1900 + cajt->tm_year) << "." << endl;
cout << "Tono je "
<< cajt->tm_hour << ":"
<< cajt->tm_min << " sati" << endl;
#include <ctime>
time_t mktime(tm *pokVrijeme);
591
Korak 6: Implementacija
#include <ctime>
time_t time(time_t *vrijeme);
Funkcija time() vraa kalendarsko vrijeme, a ako vrijeme nije dostupno, funkcija
vraa -1. Kao argument funkciji moe se proslijediti nul-pokaziva; ako se proslijedi
pokaziva na neki objekt tipa time_t, tada funkcija pridruuje rezultat tom objektu.
Valja spomenuti da se kalendarsko vrijeme moe razlikovati od lokalnog vremena, na
primjer zbog razlike u vremenskim zonama da bi se dobilo lokalno vrijeme treba
pozvati funkciju localtime() koja e kalendarsko vrijeme pretvoriti u lokalno.
#include <cmath>
float abs(float x);
double abs(double x);
long double abs(long double x);
float fabs(float x);
Funkcija rauna arkus kosinus argumenta x. Argument mora biti unutar intervala od 1
do +1, a funkcija vraa odgovarajuu vrijednost kuta u radijanima, unutar intervala od
0 do (3,1415926535...). Ako je argument izvan dozvoljenog intervala, funkcija
postavlja globalnu varijablu errno na EDOM (Domain Error).
592
#include <cmath>
float asin(float x);
double asin(double x);
long double asin(long double x);
Funkcija rauna arkus sinus argumenta x. Argument mora biti unutar intervala od 1 do
+1, a funkcija vraa odgovarajuu vrijednost kuta u radijanima, unutar intervala od
/2 do +/2. Ako je argument izvan dozvoljenog intervala, funkcija postavlja globalnu
varijablu errno na EDOM (Domain Error).
#include <cmath>
float atan(float x);
double atan(double x);
long double atan(long double x);
Funkcija rauna arkus tangens omjera argumenata y/x. Za razliku od funkcije atan(),
ova funkcija vraa vrijednosti kuta u radijanima, za sva etiri kvadranta, unutar
intervala od do +. Ako su oba argumenta jednaka nuli, tada funkcija postavlja
globalnu varijablu errno na EDOM (Domain Error).
#include <cmath>
float ceil(float x);
double ceil(double x);
long double ceil(long double x);
593
Korak 6: Implementacija
Funkcije izraunavaju kvocijent i ostatak dijeljenja dva cijela broja. Kao rezultat
vraaju standardno definirane tipove div_t, odnosno ldiv_t (definirane pomou
typedef u cstdlib). U sutini su to strukture koje se sastoje od dva cijela broja tipa
int:
typedef struct {
int quot;
// kvocijent
int rem;
// ostatak
} div_t;
odnosno long:
typedef struct {
long quot;
long rem;
} ldiv_t;
// kvocijent
// ostatak
594
#include <cmath>
float exp(float x);
double exp(double x);
long double exp(long double x);
Rauna ostatak dijeljenja dva realna broja. Tako e izvoenje sljedeih naredbi:
double x = 5.0;
double y = 2.2;
cout << fmod(x, y) << endl;
ispisat e:
4 = 0.5 * 2 na 3
595
Korak 6: Implementacija
#include <cmath>
// stari naziv: <math.h>
float ldexp(float x, int eksponent);
double ldexp(double x, int eksponent);
long double ldexp(long double x, int eksponent);
Funkcija log() vraa prirodni logaritam, dok funkcija log10() vraa dekadski
logaritam argumenta x. Ako je argument realan i manji od 0, tada funkcije postavljaju
globalnu varijablu errno na vrijednost EDOM (Domain Error). Ako je argument jednak
0, funkcija vraa vrijednost minus HUGE_VAL te globalnu varijablu errno postavlja na
vrijednost ERANGE (Range Error).
#include <cmath>
// stari naziv: <math.h>
float modf(float x, float *cijeliDio);
double modf(double x, double *cijeliDio);
long double modf(long double x, long double *cijeliDio);
Funkcija rastavlja broj x na njegov cijeli i decimalni dio. Povratna vrijednost funkcije je
decimalni dio, dok se cijeli dio prenosi preko pokazivaa koji se prosljeuje funkciji kao
drugi argument. Tako e izvoenje naredbi:
double broj = 94.3;
double cijeli;
double decimalni = modf(broj, &cijeli);
cout << broj << " = " << decimalni << " + " << cijeli << endl;
ispisati na zaslonu:
94.3 = 0.3 + 94
596
#include <cmath>
// stari naziv: <math.h>
float pow(float baza, float eksponent);
float pow(float baza, int eksponent);
double pow(double baza, double eksponent);
double pow(double baza, int eksponent);
long double pow(long double baza, long double eksponent);
long double pow(long double baza, int eksponent);
Rauna potenciju bazaeksponent. Ako je raun uspjeno proveden, funkcija kao rezultat
vraa izraunatu potenciju. Ako je rezultat izvan opsega moguih vrijednosti za
rezultirajui tip, funkcija vraa kao rezultat HUGE_VAL te postavlja globalnu varijablu
errno na vrijednost ERANGE (Range Error). Ako se funkciji prenese realna baza manja
od 0 ili ako su oba argumenta funkciji jednaki 0, funkcija postavlja vrijednost errno na
x
EDOM (Domain Error). Za raunanje potencija e praktinije je koristiti funkciju exp(),
a za raunanje kvadratnog korijena na raspolaganju je funkcija sqrt().
#include <cstdlib>
int rand();
void srand(unsigned int klica);
uvijek na nekom stroju ispisati potpuno isti niz brojeva (zato se govori o
pseudosluajnim brojevima). Da bi se prilikom svakog pokretanja programa dobili
razliiti nizovi sluajnih brojeva, generator sluajnih brojeva treba inicijalizirati nekim
brojem koji e se mijenjati od izvoenja do izvoenja programa. Najzgodnije je koristiti
sistemsko vrijeme, koje se mijenja dovoljno brzo da korisnik ne moe kontrolirati
njegovu vrijednost.
Funkcija srand() slui za inicijalizaciju generatora sluajnih brojeva. Argument je
klica pomou koje se generira prvi broj u nizu sluajnih brojeva. Ako gornji primjer
modificiramo, dodajui mu izmeu ostalog poziv funkcije srand() ispred for-petlje:
597
Korak 6: Implementacija
#include <stdlib.h>
#include <iostream.h>
#include <time.h>
int main() {
time_t cajt;
srand((unsigned) time(&cajt));
for (int i = 0; i < 10; i++)
cout << (rand() % 6 + 1) << endl;
return 0;
}
Rauna kvadratni korijen argumenta x. Ako je argument pozitivan realni broj, funkcija
vraa pozitivni korijen. Ako je x realan i manji od nule, funkcija postavlja vrijednost
globalne varijable errno na EDOM (Domain Error).
#include <cmath>
float tan(float x);
double tan(double x);
long double tan(long double x);
598
#include <cmath>
float tanh(float x);
double tanh(double x);
long double tanh(long double x);
int main() {
atexit(izlaz1);
atexit(izlaz2);
return 0;
}
na zaslonu ispisati:
Izlaz br. 2
Izlaz br. 1
Korak 6: Implementacija
599
#include <cstdlib>
// stari naziv: <stdlib.h>
void *bsearch(const void *kljuc, const void *baza, size_t brElem,
size_t sirina, int (*usporedba)(const void *, const void *));
Funkcija obavlja binarno pretraivanje sortiranog polja na koje pokazuje baza. Kao
rezultat vraa pokaziva na prvi lan polja koji zadovoljava klju na koji pokazuje
kljuc; ako nije uspjela pronai lan koji zadovoljava klju, kao rezultat funkcija vraa
nul-pokaziva. Broj elemenata u polju definiran je argumentom brElem, a veliina
pojedinog lana u polju argumentom sirina.
Funkcija na koju pokazuje usporedba koristi se za usporedbu lanova polja sa
zadanim kljuem. Poredbena funkcija mora biti oblika:
int ime_funkcije(const void *arg1, const void *arg2);
Ona mora biti definirana tako da vraa sljedee vrijednosti tipa int:
< 0 ako je arg1 manji od (tj. mora u slijedu biti prije) arg2,
0 ako su arg1 i arg2 meusobno jednaki,
> 0 ako je arg1 vei od (tj. mora u slijedu biti iza) arg2
Evo i primjera:
#include <stdlib.h>
#include <iostream.h>
// dodano da bi se prevario prevoditelj, jer funkcija
// bsearch() oekuje pokaziva na funkciju s argumentima
// tipa void *
typedef int (*fpok)(const void *, const void *);
int usporedba(const int *p1, const int *p2) {
return(*p1 - *p2);
}
int main() {
int podaci[] = {123, 234, 345, 456};
int trazeni = 345;
int brelem = sizeof(podaci) / sizeof(podaci[0]);
int *pok = (int *)bsearch (&trazeni, podaci, brelem,
sizeof(podaci[0]), (fpok)usporedba);
if (pok)
cout << "U polju ve postoji taj podatak na mjestu "
<< (pok - podaci) << endl;
else
cout << "Ovakvog podatka jo nije vidjelo ovo polje!"
<< endl;
return 0;
}
600
*grouping;
*int_curr_symbol;
*currency_symbol;
*mon_decimal_point;
char *mon_thousands_sep;
char
char
char
char
*mon_grouping;
*positive_sign;
*negative_sign;
int_frac_digits;
char frac_digits;
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
601
Korak 6: Implementacija
char p_cs_precedes;
char p_sep_by_space;
char n_cs_precedes;
char n_sep_by_space;
char p_sign_posn;
char n_sign_posn;
//
//
//
//
//
//
//
//
//
//
//
//
//
};
Pretrauje niz bajtova duljine duljina, poevi od lokacije na koju pokazuje niz i
trai znak znak. Kao rezultat vraa pokaziva na prvu pojavu znaka, a ako ga ne
pronae, povratna vrijednost je nul-pokaziva.
#include <cstring>
// stari naziv: <string.h>
void *memcpy(void *odrediste, const void *izvornik, size_t duljina);
void *memmove(void *odrediste, const void *izvornik, size_t duljina);
Obje funkcije preslikavaju blok duljine duljina bajtova s mjesta u memoriji na koje
pokazuje pokaziva izvornik, na mjesto na koje pokazuje odrediste. Meutim, ako
se izvorni i odredini blokovi preklapaju, ponaanje funkcije memcpy()je nedefinirano,
dok e funkcija memmove() preslikati blok korektno. Primjerice, provjerite to ete
dobiti ispisom polja abcd[] nakon sljedeih naredbi:
602
#include <cstring>
// stari naziv: <string.h>
int memcmp(const void *niz1, const void *niz2, size_t duljina);
Usporeuje prvih duljina bajtova oba niza, a kao rezultat usporedbe vraa:
< 0 ako je niz1 manji od niz2 (svrstano po abecedi niz1 dolazi prije niz2),
0 ako su niz1 i niz2 meusobno jednaki,
> 0 ako je niz1 vei od niz2 (svrstano po abecedi niz1 dolazi iza niz2).
Usporedba se radi po slijedu znakova koji se koristi na dotinom raunalu.
#include <cstring>
// stari naziv: <string.h>
void *memset(void *niz, int znak, size_t duljina);
Blok duljine duljina bajtova, poevi od lokacije na koju pokazuje niz, funkcija
popunjava znakom znak. Kao rezultat, funkcija vraa pokaziva na poetak niza.
#include <cstdlib>
// stari naziv: <stdlib.h>
void *qsort(void *baza, size_t brElem, size_t sirina,
int (*usporedba)(const void *, const void *));
Ona mora biti definirana tako da vraa sljedee vrijednosti tipa int:
<0
ako je arg1 manji od (tj. mora u slijedu biti prije) arg2,
0 ako su arg1 i arg2 meusobno jednaki,
> 0 ako je arg1 vei od (tj. mora u slijedu biti iza) arg2
Funkcija qsort() primjenjuje quick sort algoritam koji se smatra najbrim za sortiranje
openitih podataka. Sljedei program e sortirati slova u nizu podaci obrnutim
abecednim slijedom:
int usporedba(const void *p1, const void *p2) {
return(*(char*)p2 - *(char*)p1);
}
int main() {
char podaci[] = "adiorwgoerg";
Korak 6: Implementacija
603
Uoimo kako je ovdje poziv funkcije usporedba() drugaije rijeen nego kod
primjera s funkcijom bsearch(): unutar same funkcije usporedba() pokazivaima
na void se dodjeljuje tip char * te se provodi usporedba takvih tipova.
#include <csignal>
// stari naziv: <signal.h>
int raise(int dojava);
void (*signal(int dojava, void (*rukovatelj)(int)))(int);
Funkcijom signal() definira se koja e funkcija biti pozvana ako nastupi neka
izvanredna situacija, na primjer ako se primi signal prekida (interrupt) sa neke vanjske
jedinice ili pogreka tijekom izvoenja programa. Prvi argument je tip dojave na koju se
definicija nove funkcije rukovatelj() odnosi. Standardno su definirani (u zaglavlju
csignal) sljedei tipovi:
SIGABRT nepravilan prekid, primjerice uslijed abort();
SIGFPE
aritmetika pogreka, primjerice zbog dijeljenja s nulom ili preljev;
SIGILL
neispravna instrukcija;
SIGINT
interaktivno upozorenje, primjerice prekid;
SIGSEGV nepravilni pristup memoriji, na primjer pristup nedozvoljenom podruju
memorije;
SIGTERM program je zaprimio zahtjev za zavretak.
Funkcija rukovatelj() je zaduena za obradu dojave. To moe biti funkcija koju
definira korisnik, ili dva predefinirana rukovatelja:
SIG_DFL zakljui izvoenje programa;
SIG_IGN ignorira ovaj tip dojave.
Rukovateljska funkcija prihvaa jedan cjelobrojni argument tip dojave. Ako je poziv
funkcije signal() bio uspjean, funkcija kao rezultat vraa pokaziva na prethodni
rukovatelj navedenog tipa dojave; ako poziv nije bio uspjean, funkcija vraa SIG_ERR.
Valja voditi rauna da se pozivom rukovatelja, brie dojava te ponovno treba instalirati
funkciju za rukovanje.
Funkcija raise() alje dojavu signala dojava programu. Ako je odailjanje bilo
uspjeno, funkcija vraa vrijednost razliitu od nule. U sljedeem primjeru ilustrirana je
primjena obje funkcije:
void hvataljka(int dojava) {
signal(SIGFPE, hvataljka); // ponovno instalira hvataljku
cout << "No, no: zna da nije dozvoljeno dijeljenje s "
"nulom" << endl;
604
return;
}
int main() {
int a = 10;
int b = 0;
if (signal(SIGFPE, hvataljka) == SIG_ERR)
cout << "Hvatanje pogreke nije postavljeno - "
<< "nastavak programa na vlastitu odgovornost."
<< endl;
if (b == 0)
raise(SIGFPE);
else
a = a / b;
return 0;
}
U jeziku C++ funkcije ove funkcije se rijetko koriste, jer se rukovanje izvanrednim
situacijama elegantnije rjeava hvatenjem iznimki. Takoer, signali su ostaci UNIX
biblioteke. U modernim, suvremenim i naprednim sustavima signali imaju bolju i
kvalitetniju implementaciju.
#include <new>
// stari naziv: <new.h>
new_handler set_new_handler(new_handler funkcija);
605
Korak 6: Implementacija
606
anonymous union
data abstraction
abstract class
argument
assembler
automatic storage class
automatic object
throwing an exception
byte
nameless union
nameless namespace
library
try block
integer
integral promotion
pure virtual function member
directory
file scope
file
header file
function definition
forward declaration
destructor
dynamic object
dynamic call
dynamic binding
union discriminant
partial specialization
type cast
dominance
deep copy
encapsulation
formal argument
member functions
global scope
heap
catch
run-time type identification
607
Korak 6: Implementacija
imenik
implementacija objekta
instanciranje predloka
integrirana razvojne okoline
iskljuivi ili
isprazniti
izbornik
izlazni tok
iznimka
izvedbeni program
izvedena klasa
izvorni kd
javni
javna osnovna klasa
javno suelje
kasno povezivanje
klasa
konstantnost
konstruktor
konstruktor kopije
kontejnerska klasa
kurzor
lvrijednost
makro funkcija
makro ime
manipulator
meupohranjivanje
meuspremnik
memorijska napuklina
metoda
mjesto instantacije
najdalje izvedena klasa
nasljeivanje
neimenovani privremeni objekt
nevezano prijateljstvo
nul-pokaziva
nul-znak
objektni kd
objektno orijentirano programiranje
obnavljajue pridruivanje
odbaciti konstantnost
odmatanje stoga
omota
operator izluivanja
namespace
implementation
template instantiation
integrated development environment,
IDE
exclusive or
flush
menu
output stream
exception
executable
derived class
source code
public
public base class
public interface
late binding
class
constness
constructor
copy constructor
container class
cursor
lvalue
macro function
macro name
manipulator
buffering
buffer
memory leak
methods
point of instantiation
most derived class
inheritance
unnamed temporary
unbound template friendship
null-pointer
null-character
object code
object oriented programming
update assignment
cast away constness
stack unwinding
wrapper
extraction operator
608
operator umetanja
operator za indeksiranje
operator za odreivanje podruja
operator za pristup lanu
oporavak od iznimke
osnovna klasa
pametni pokaziva
parametar
plitka kopija
pobrojenje
pobrojani tip
podatkovni segment
podizanje iznimke
podrazumijevana
vrijednost
argumenta
podrazumijevani konstruktor
podruje
pogonitelj
pogonjeno dogaajima
pogreka
pogreka pri izvoenju
pogreka pri povezivanju
pogreka pri prevoenju
pogreno
pokaziva
pokaziva datoteke
pokaziva instrukcija
pokaziva na lan klase
pokaziva stoga
polimorfizam
polje bitova
polje podataka
pomak udesno
pomak ulijevo
ponovnu iskoristivost kda
popratna pojava
posebna sekvenca
potpis funkcije
poveziva
povratna vrijednost
pravila provjere tipa
pravilo od palca
prazan
predloak
predloak funkcije
insertion operator
indexing operator
scope resolution operator
member selection operator
exception recovery
base class
smart pointer
parameter
shallow copy
enumeration
enumerated type
data segment
raising an exception
default argument value
default constructor
scope
driver
event-driven
bug
run-time error
link-time error
compile-time error
false
pointer
file pointer
instruction pointer
class member pointer
stack pointer
polimorphysm
bit-fields
array
shift right
shift left
code reusability
side-effect
escape sequence
function signature
linker
return value
type-checking rules
rule of the thumb
void
template
function template
609
Korak 6: Implementacija
predloak klase
prekid
prekomjerno bujanje kda
preoptereenje funkcije
preoptereenje operatora
pretvorba nanie
pretvorba navie
prevoditelj
prijatelj klase
prijenos po referenci
prijenos po vrijednosti
privatna osnovna klasa
privatni
program za lociranje pogreaka
program za praenje efikasnosti kda
program za ureivanje teksta
programersko suelje
promjenjiv
proirenje imenika
prototip funkcije
razluivanje podruja
realni broj
registar
registarska smjetajna klasa
rekurzija
relacijski operator
rukovanje iznimkama
skrivanje podataka
skupljanje smea
smjetajna klasa
specijalizacija predloka
sredinja procesorska jedinica
standardna biblioteka predloaka
stanje
statiki objekt
statiki poziv
statiko povezivanje
stog
struktura
stvarni argument
tijelo funkcije
tip
tip povezivanja
tono
tok
class template
interrupt
code bloat
function overloading
operator overloading
downcast
upcast
compiler
friend of a class
pass by reference
pass by value
private base class
private
debugger
profiler
text editor
application programming interface
volatile
namespace extension
function prototype
scope resolution
floating-point number
register
register storage class
recursion
relational operator
exception handling
data hiding
garbage collection
storage class
template specialization
central processing unit, CPU
standard template library
state
static object
static call
static binding
stack
structure
actual argument
function body
type
linkage
true
stream
610
ugnijeena klasa
ulazni tok
umetnuta definicija
umetnuta funkcija
unija
unutarnje povezivanje
vanjsko povezivanje
vezana lista
vezano prijateljstvo
virtualna osnovna klasa
virtualni funkcijski lan
virtualni poziv
visea referenca
visei pokaziva
viestruko nasljeivanje
zagaenje globalnog podruja
zaobilaenje prilikom nasljeivanja
zastavica
zatien
zatiena osnovna klasa
znakovni niz
nested class
input stream
inline definition
inline function
union
internal linkage
external linkage
linked list
bound template friendship
virtual base class
virtual function member
virtual call
dangling reference
dangling pointer
multiple inheritance
global scope pollution
overriding
flag
protected
protected base class
character string
611
Korak 6: Implementacija
Englesko-hrvatski rjenik
abstract class
actual argument
anonymous union
application programming interface
argument
array
assembler
automatic object
automatic storage class
base class
bit-fields
bound template friendship
buffer
buffering
bug
byte
cast away constness
catch
central processing unit, CPU
character string
class
class member pointer
class template
code bloat
code reusability
compile-time error
compiler
constness
constructor
container class
copy constructor
cursor
dangling pointer
dangling reference
data abstraction
data hiding
data segment
debugger
deep copy
default argument value
default constructor
apstraktna klasa
stvarni argument
anonimna unija
programersko suelje
argument
polje podataka
asembler
automatski objekt
automatska smjetajna klasa
osnovna klasa
polje bitova
vezano prijateljstvo
meuspremnik
meupohranjivanje
pogreka
bajt
odbaciti konstantnost
hvatati
sredinja procesorska jedinica
znakovni niz
klasa
pokaziva na lan klase
predloak klase
prekomjerno bujanje kda
ponovnu iskoristivost kda
pogreka pri prevoenju
prevoditelj
konstantnost
konstruktor
kontejnerska klasa
konstruktor kopije
kurzor
visei pokaziva
visea referenca
apstrakcija podataka
skrivanje podataka
podatkovni segment
program za lociranje pogreaka
duboka kopija
podrazumijevana
vrijednost
argumenta
podrazumijevani konstruktor
612
derived class
destructor
directory
dominance
downcast
driver
dynamic binding
dynamic call
dynamic object
encapsulation
enumerated type
enumeration
escape sequence
event-driven
exception
exception handling
exception recovery
exclusive or
executable
external linkage
extraction operator
false
file
file pointer
file scope
flag
floating-point number
flush
formal argument
forward declaration
friend of a class
function body
function definition
function overloading
function prototype
function signature
function template
garbage collection
global scope
global scope pollution
header file
heap
implementation
indexing operator
inheritance
izvedena klasa
destruktor
datoteni imenik
dominacija
pretvorba nanie
pogonitelj
dinamiko povezivanje
dinamiki poziv
dinamiki objekt
enkapsulacija
pobrojani tip
pobrojenje
posebna sekvenca
pogonjeno dogaajima
iznimka
rukovanje iznimkama
oporavak od iznimke
iskljuivi ili
izvedbeni program
vanjsko povezivanje
operator izluivanja
pogreno
datoteka
pokaziva datoteke
datoteno podruje
zastavica
realni broj
isprazniti
formalni argument
deklaracija unaprijed
prijatelj klase
tijelo funkcije
definicija funkcije
preoptereenje funkcije
prototip funkcije
potpis funkcije
predloak funkcije
skupljanje smea
globalno podruje
zagaenje globalnog podruja
datoteka zaglavlja
hrpa
implementacija objekta
operator za indeksiranje
nasljeivanje
613
Korak 6: Implementacija
inline definition
inline function
input stream
insertion operator
instruction pointer
integer
integral promotion
integrated development environment,
IDE
internal linkage
interrupt
late binding
library
linkage
linked list
linker
link-time error
lvalue
macro function
macro name
manipulator
member functions
member selection operator
memory leak
menu
methods
most derived class
multiple inheritance
nameless namespace
nameless union
namespace
namespace extension
nested class
null-character
null-pointer
object code
object oriented programming
operator overloading
output stream
overriding
parameter
partial specialization
pass by reference
pass by value
point of instantiation
umetnuta definicija
umetnuta funkcija
ulazni tok
operator umetanja
pokaziva instrukcija
cijeli broj
cjelobrojna promocija
integrirana razvojne okoline
unutarnje povezivanje
prekid
kasno povezivanje
biblioteka
tip povezivanja
vezana lista
poveziva
pogreka pri povezivanju
lvrijednost
makro funkcija
makro ime
manipulator
funkcijski lan
operator za pristup lanu
memorijska napuklina
izbornik
metoda
najdalje izvedena klasa
viestruko nasljeivanje
bezimeni imenik
bezimena unija
imenik
proirenje imenika
ugnijeena klasa
nul-znak
nul-pokaziva
objektni kd
objektno orijentirano programiranje
preoptereenje operatora
izlazni tok
zaobilaenje prilikom nasljeivanja
parametar
djelomina specijalizacija
prijenos po referenci
prijenos po vrijednosti
mjesto instantacije
614
pointer
polymorphism
private
private base class
profiler
protected
protected base class
public
public base class
public interface
pure virtual function member
raising an exception
recursion
register
register storage class
relational operator
return value
rule of the thumb
run-time error
run-time type identification
scope
scope resolution
scope resolution operator
shallow copy
shift left
shift right
side-effect
smart pointer
source code
stack
stack pointer
stack unwinding
standard template library, STL
state
static binding
static call
static object
storage class
stream
structure
template
template instantiation
template specialization
text editor
throwing an exception
pokaziva
polimorfizam
privatni
privatna osnovna klasa
program za praenje efikasnosti kda
zatien
zatiena osnovna klasa
javni
javna osnovna klasa
javno suelje
isti virtualni funkcijski lan
podizanje iznimke
rekurzija
registar
registarska smjetajna klasa
relacijski operator
povratna vrijednost
pravilo od palca
pogreka pri izvoenju
identifikacija tipova tijekom izvoenja
podruje
razluivanje podruja
operator za odreivanje podruja
plitka kopija
pomak ulijevo
pomak udesno
popratna pojava
pametni pokaziva
izvorni kd
stog
pokaziva stoga
odmatanje stoga
standardna biblioteka predloaka
stanje
statiko povezivanje
statiki poziv
statiki objekt
smjetajna klasa
tok
struktura
predloak
instanciranje predloka
specijalizacija predloka
program za ureivanje teksta
bacanje iznimke
615
Korak 6: Implementacija
true
try block
type
type cast
type-checking rules
unbound template friendship
union
union discriminant
unnamed temporary
upcast
update assignment
virtual base class
virtual call
virtual function member
void
volatile
wrapper
tono
blok pokuaja
tip
dodjela tipa
pravila provjere tipa
nevezano prijateljstvo
unija
diskriminanta unije
neimenovani privremeni objekt
pretvorba navie
obnavljajue pridruivanje
virtualna osnovna klasa
virtualni poziv
virtualni funkcijski lan
prazan
promjenjiv
omota
616
Abecedno kazalo
#
!, 57
!=, 59
" (#include), 475
" (znakovni niz), 26, 60, 139
"C", 509
"C++", 511
#, 480
##, 480
%, 46
%:, 70
%:%:, 70
%=, 69
%>, 70
&, 63, 114
&&, 57
&=, 69
', 60
(), 76, 152, 154, 310
preoptereenje, 317
* (mnoenje), 46
* (pokaziva), 114
*=, 69
+, 46
++, 45, 125
preoptereenje, 321
+=, 69
,, 74, 88
-, 46
--, 45, 125
preoptereenje, 321
-=, 69
->, 221, 310
preoptereenje, 318
->*, 274, 278
., 221
.*, 274, 278
... (funkcije)
argumenti funkcije, 176
... (iznimke)
617
Korak 6: Implementacija
??<, 71
??=, 71
??>, 71
[], 104, 310
preoptereenje, 315
\ (nastavak reda), 31, 474
\ (posebna, escape sekvenca), 60
\", 60
\', 60
\0, 60, 140
\?, 60
\\, 60
^, 64
^=, 69
{}, 77, 153
|, 64
|=, 69
||, 57
~, 62, 246
A
\a, 60
abort(), 574, 598
abs(), 591
abstract class. Vidi apstraktna klasa
acos(), 591
adjustfield, 533
algorithm, 573
and, 70
and_eq, 70
anonymous unions. Vidi unija, anonimna
ANSI C++, 18
API, 558
app, 545
application programming interface. Vidi
programersko suelje (API)
apstrakcija
definicija, 560
definicija javnog suelja, 571
implementacija, 578
implementacijski zavisna, 569
ocjenjivanje, 559
odnosi i veze, 563
pronalaenje, 559
apstrakcija podataka, 555
apstraktna klasa, 330, 380
argc, 202
argument
formalni, 154
klasa kao, 363
konstantni, 172
konstruktora, 238, 240
konverzija, 163
neodreeni, 176
podrazumijevani, 173, 192, 201
pokaziva kao, 164
polje kao, 168
predloka funkcije, 211, 392
predloka funkcije, konstantni izraz, 396
predloka klase, 409
predloka klase, konstantni izraz, 420
preoptereene funkcije, 191
prijenos po referenci, 165
prijenos po vrijednosti, 162, 181
privremeni objekt za prijenos po
vrijednosti, 284
redoslijed izraunavanja, 163
referenca kao, 165
stvarni, 154
tono podudaranje, 364
znakovni niz kao, 169
argv, 202
array. Vidi polje
ASCII, 61
asctime(), 588
asembler, 10, 511
ukljuivanje u C++ kd, 511
asin(), 592
asm, 511
assert(), 236, 248, 483, 574
assert.h, 236, 483
atan(), 205, 592
atan2(), 204, 592
ate, 545
atexit(), 598
atof(), 203, 586
atoi(), 587
atol(), 587
auto, 182
automatic storage class. Vidi smjetajna
klasa, automatska
automatski objekti, 128
B
\b, 60
618
bad(), 516
bad_cast, 469
badbit, 516
base class. Vidi osnovna klasa
basefield, 533
basic_string, 572, 578
before(), 466
beg, 549
bezimeni imenik, 437
biblioteka, 22
funkcija, 204, 580. Vidi standardna
funkcija
standardna, 571
binary, 545
bitand, 70
bit-fields. Vidi polje bitova
bitor, 70
bitovni operator
i, 62, 63
ili, 62, 64
iskljuivi ili, 62, 64
komplement, 62
pomak udesno, 65
pomak ulijevo, 65
bits, 573
blok
hvatanja, 448, 452, 453
hvatanja, odreivanje odgovarajueg
bloka, 453
pokuaja, 448, 452
blok naredbi, 38, 77
bool, 57
boolalpha, 533
bound template friendship. Vidi vezano
prijateljstvo
break, 84, 93
broj
cijeli, 40
dekadski, 41
heksadekadski, 41
oktalni, 41
preljev, 46
realni, 40
s pominom decimalnom tokom. Vidi
broj, realni
brojana konstanta, 35, 51
buffer. Vidi meuspremnik
buffering. Vidi meupohranjivanje
C
.c, 22, 495
case, 84
cassert, 572
cast. Vidi dodjela tipa
catch, 448, 450
catching an exception. Vidi iznimka,
hvatanje
cctype, 572
ceil(), 592
cerr, 516
cerrno, 572
char, 44, 60
cijeli broj, 40
dijeljenje, 48
cin, 27, 515, 521
class, 211, 218, 392, 409. Vidi klasa
class member pointer. Vidi pokaziva, na
lan klase
class template. Vidi predloak klase
clear(), 516, 548
clocale, 573
clock(), 589
clock_t, 588, 589
CLOCKS_PER_SEC, 589
clog, 516
close(), 545
cmath, 573
code reusability. Vidi ponovna iskoristivost
compiler. Vidi prevoditelj
compl, 70
complex, 573, 578
complex.h, 205
const, 53, 136, 172, 413. Vidi simbolika
konstanta
argument funkcije, 172
const_cast, 471
funkcijski lan, 253
odbacivanje konstantnosti, 139
podatkovni lan, 244
pokaziva, 137
pokaziva na, 137
pokaziva na const, 138
razlika const i #define, 477
reference na, 145
const_cast, 471
constructor. Vidi konstruktor
container class. Vidi kontejnerska klasa
Korak 6: Implementacija
continue, 94
copy constructor. Vidi konstruktor,
konstruktor kopije
cos(), 593
cosh(), 593
cout, 26, 516, 517
.cp, 22, 495
_ _cplusplus, 478, 510
.cpp, 22, 495
CRC kartica, 563
csetjmp, 572
csignal, 572
cstdarg, 572
cstdio, 573
cstdlib, 572, 573
cstring, 572
ctime, 572, 588
ctime(), 588
ctype.h, 528
cur, 549
cwchar, 572, 573
cwctype, 572
C
lan
funkcijski. Vidi funkcijski lan
imenika, koritenje, 437
iskljuivanje iz nasljeivanja, 353
javni, 226
podatkovni. Vidi podatkovni lan
podrazumijevano pravo pristupa, 227
pokaziva na, 271
polja bitova, 297
pravo pristupa, 226
pristup, 220
privatni, 226
puno ime, 223, 261
razlika statikog povezivanja i statikog
lana, 491
virtualne osnovne klase, 385
zatieni, 226
D
dangling pointer. Vidi pokaziva, visei
dangling reference. Vidi referenca, visea
data abstraction. Vidi apstrakcija podataka
data hiding. Vidi skrivanje podataka
619
620
E
EDOM, 574
#elif, 482
#else, 482
else, 80
encapsulation. Vidi enkapsulacija
end, 549
#endif, 248, 482
endl, 521, 538
Korak 6: Implementacija
F
\f, 60
fabs(), 199, 591
fail(), 516
failbit, 516, 522
false, 57
_ _FILE_ _, 266, 326, 478, 485
FILE, 126
file pointer. Vidi pokaziva datoteke
file scope. Vidi datoteno podruje
fill(), 531
fixed, 533, 535
flag. Vidi zastavica
flags(), 532
float, 44
float.h, 205
floatfield, 533
floating-point. Vidi realni broj
floor(), 592
flush. Vidi meuspremnik, pranjenje
flush, 520, 538
621
flush(), 520
fmod(), 594
fopen(), 126
for, 86
forward declaration. Vidi klasa, deklaracija
unaprijed
free(), 325
frexp(), 594
friend, 230
razlika prijateljstva i nasljeivanja, 350
unutar deklaracije klase, 231
friend of a class. Vidi prijatelj klase
fstream, 515, 541, 546, 573
fstream.h, 91, 205, 541
function overloading. Vidi funkcija,
preoptereenje
function template. Vidi predloak funkcije
function template instantiation. Vidi
predloak funkcije, instantacija
function template specialization. Vidi
predloak funkcije, specijalizacija
funkcija, 150
argc, 202
argument, 150, 161
argv, 202
bez argumenata, 161
datoteka zaglavlja, 155
definicija, 153
deklaracija, 152
formalni argument, 154
inline, 489
konstantni argument, 172
konverzija argumenata, 163
konverzija rezultata, 158
lista moguih iznimaka, 458
main(), 24, 202, 249, 453, 530
mijenjanje pokazivakih argumenata,
166
operatorska, 309
parametar. Vidi argument. Vidi funkcija,
argument
podrazumijevani argument, 173
pokaziva kao argument, 164
pokaziva na, 196
polje kao argument, 168
potpis, 153
povratna vrijednost, 150, 159, 179
poziv, 154
poziv operatorske funkcije, 310
pozivanje C funkcije, 509
622
G
garbage collection. Vidi skupljanje smea
gcount(), 526, 552
get(), 524
getline(), 526
global scope. Vidi globalno podruje
global scope pollution. Vidi zagaenje
globalnog podruja
globano podruje, 262
gmtime(), 589
good(), 516
goodbit, 516
goto, 94
definiranje oznake, 95
grananje toka, 77, 79, 84
H
.h, 495
header file. Vidi zaglavlje
heap. Vidi hrpa
hex, 63, 533, 538
hijerarhija operatora, 74
.hpp, 495
hrpa, 128, 213
HUGE_VAL, 575
I
IDE, 22
identifikacija tipa, 463, 465
before(), 466
dynamic_cast, 468
name(), 466
type_info, 465
typeid, 465
typeinfo.h, 465
usporedba, 465
identifikator, 35
dozvoljeno ime, 35
#if, 481
if, 79
#ifdef, 481
#ifndef, 248, 481
ifstream, 91, 515, 541
ignore(), 526
imenik, 434
Korak 6: Implementacija
623
624
J
javno suelje, 215, 229
definicija, 571
javni pristup lanu, 229
jezik
Actor, 555
Ada, 12
Algol68, 12
asembler, 11, 511
BASIC, 11
BCPL, 11
C, 11, 14
C i C++, usporedba, 14
C s klasama, 11
C++, 555
Clu, 12
COBOL, 11
FORTRAN, 11, 556
PASCAL, 315
Simula, 11
SmallTalk, 470, 555
K
kasno povezivanje, 373
klasa, 37, 215
625
Korak 6: Implementacija
L
labs(), 591
late binding. Vidi povezivanje, kasno
LC_ALL, 601
LC_COLLATE, 601
626
LC_CTYPE, 601
LC_MONETARY, 601
LC_NUMERIC, 601
LC_TIME, 601
lconv, 600
ldexp(), 595
ldiv(), 593
ldiv_t, 593
left, 533
limits.h, 43, 205
#line, 485
_ _LINE_ _, 266, 326, 478, 485
linked list. Vidi vezana lista
linker. Vidi poveziva
list, 573, 577
lista. Vidi vezana lista
locale, 573
locale.h, 205
localeconv(), 600
localtime(), 589
log(), 193, 595
log10(), 595
logiki operator
i, 57
ili, 57
negacija, 57
logiki tip, 57
lokalna varijabla, 78
long, 41, 44
long double, 43, 44
long int, 41, 44
lvrijednost, 39, 127
promjenjiva, 39
M
macro name. Vidi makro ime
main(), 24, 202, 249, 453, 530
makro funkcija, 391, 479
problemi s parametrima, 480
standardna, 573
makro ime, 476
standardno, 573
malloc(), 325
manipulator, 537
dec, 538
dodavanje, 539
endl, 521, 538
ends, 521, 538
627
Korak 6: Implementacija
N
\n, 60
najdalje izvedena klasa, 388
name(), 466
nameless namespace. Vidi imenik,
bezimeni
nameless union. Vidi unija, bezimena
namespace, 435. Vidi imenik
namespace extension. Vidi imenik,
proirivanje
naredba
break, 84, 93
case, 84
continue, 94
default, 84
do-while, 92
else, 80
for, 86
goto, 94
goto, definiranje oznake, 95
if, 79
nastavak u sljedei redak, 31
pisanje, 30
pretprocesorska, 474
return, 153, 158
strukturiranje, 95
switch, 84
throw, 449
throw, bez parametara, 454
while, 90
nasljeivanje, 12, 14, 217, 332
deinicijalizacija izvedene klase, 356
deklaracija izvedene klase, 335
deklaracija using, 441
deklaracija virtualne osnovne klase, 384
i podruje, 360
i predloak klase, 427
i preoptereenje, 363
i pripadnost, 354
i ugnjeeni tipovi, 361
inicijalizacija osnovne klase, 355
instance predloka, 427
iskljuivanje lana, 353
javno, 345
konstruktor izvedene klase, 355
konstruktora konverzije, 370
lista osnovnih klasa, 335
operatora =, 369
O
.o, 22
628
.obj, 22
object. Vidi objekt
object oriented programming. Vidi objektno
orijentirano programiranje
objekt, 37, 215
automatski, 128
deklaracija, 232
deklariran na osnovu strukture, 292
dinamika alokacija, 238
dinamiki, 128
globalni, 182, 185, 248
implementacija, 215
inicijalizacija, 356
kao argument, 363
lokalni, 181, 185
polje objekata, 249
predloka klase, 413
privremeni, 241, 280, 315
privremeni, eksplicitno stvoren, 282
privremeni, prilikom vraanja iz
funkcije, 288
privremeni, za prijenos po vrijednosti,
284
razlika objekta i klase, 215
smjetajne klase globalnog objekta, 185
statiki, 248, 413
statiki, u funkciji, 186
stvaranje, 234
svojstvene operacije, 372
tono podudaranje tipova, 364
trivijalna konverzija, 364
vanjski, 413
objektni kd, 22, 486
objektno orijentirana paradigma, 12, 556
objektno orijentirano programiranje, 215,
555
oct, 533, 538
odmatanje stoga, 452
odnos
biti, 563
jedan na jedan, 565
jedan na vie, 565
korisiti, 564
posjedovati, 564
vie na jedan, 565
vie na vie, 565
ofstream, 515, 541
omota, 575
OOP, 555
open(), 544
operator, 308
!, 57
!=, 59
#, 480
##, 480
%, 46
%:, 70
%:%:, 70
%=, 69
%>, 70
&, 63
& (dohvaanje adrese), 114
&&, 57
&=, 69
(), 154
(), preoptereenje, 317
*, 46
* (deklaracija pokazivaa), 114, 120
* (dereferenciranje), 115
*=, 69
+, 46
++, 45, 125
++, preoptereenje, 321
+=, 69
,, 74, 88
-, 46
--, 45, 125
--, preoptereenje, 321
-=, 69
->, 221
->, preoptereenje, 318
->*, 274, 278
., 221
.*, 274, 278
/, 46
/=, 69
::, 186, 223, 259, 262, 270, 435, 436,
437
::, i izvedena klasa, 342
::, i virtualni lan, 379
:>, 70
<, 59
<%, 70
<:, 70
<<, 26, 28
<< (izlazni operator), 517
<< (pomak ulijevo), 65
Korak 6: Implementacija
<<=, 69
<=, 59
=, 39, 69
= i konstruktor konverzije, 370
=, nasljeivanje, 369
=, preoptereenje, 313
==, 59
>, 59
>=, 59
>>, 27
>>, 28
>> (pomak udesno), 65
>> (ulazni operator), 521
>>=, 69
?:, 83
[], 104
[], preoptereenje, 315
^, 64
^=, 69
|, 64
|=, 69
||, 57
~, 62
alternativne oznake, 35, 70
and, 70
and_eq, 70
aritmetiki, 45
binarni, 45, 308
bitand, 70
bitor, 70
bitovni, 62
compl, 70
const_cast, 471
definicija preoptereenog operatora, 309
dekrement, 45
delete, 129, 247, 381
delete, nasljeivanje, 371
delete, preoptereenje, 323
delete [], 129, 250
delete [], preoptereenje, 324
dodjele tipa, 50, 139, 400
dozvoljeni operatori za preoptereenje,
307
dynamic_cast, 468
hijerarhija, 74
inkrement, 45
izluivanja, 521
konverzije, 302
629
logiki, 57
new, 128
new, nasljeivanje, 371
new, preoptereenje, 323
new [], 129, 134, 250
new [], preoptereenje, 323
not, 70
not_eq, 70
obnavljajueg pridruivanja, 69, 125
operator, 308
or, 70
or_eq, 70
poredbeni, 58, 125
postfiks, 45
poziv operatorske funkcije, 310
prefiks, 45
preoptereenje, 299, 307
preoptereenje i nasljeivanje, 368
pridruivanja, 39
razlika = i ==, 59, 83
razlikovanje << i >>, 28
razluivanje operatora konverzije, 304
redoslijed izvoenja operatora, 74
reinterpret_cast, 473
sizeof, 43, 73
static_cast, 471
typeid, 465
umetanja, 517
unarni, 45, 308
uvjetni, 83
xor, 70
xor_eq, 70
za odreivanje podruja, 186, 270, 342,
379
za pristup lanu, 220
za razluivanje imena, 223
za razluivanje podruja, 435, 436, 437
operator overloading. Vidi operator,
preoptereenje
oporavak od iznimke, 448
or, 70
or_eq, 70
osnovna klasa, 332, 335
deinicijalizacija, 356
dominacija, 387
inicijalizacija, 355
javna, 335, 345, 365
konverzija pokazivaa, 358
630
P
pametni pokaziva, 320
parametar. Vidi argument
partial specialization. Vidi predloak
funkcije, djelomina specijalizacija
peek(), 527
petlja, 77
beskonana, 88
do-while, 92, 93
for, 86, 93
razlika for i while, 91
s uvjetom na kraju, 92
s uvjetom na poetku, 86, 90
ugnjeivanje, 89
while, 90, 93
plitka kopija, 242
pobrojenje, 55
ugnjeeno u predloak, 423
podatkovni lan, 219
inicijalizacija konstantnog lana, 244
inicijalizacija konstruktorom, 237
inicijalizacija reference kao lana, 243
iskljuivanje iz nasljeivanja, 353
javni, 226
mutable, 255
podrazumijevano pravo pristupa, 227
pokaziva na, 272
pravo pristupa, 226
pristup, 220
privatni, 226
puno ime, 223, 261
razlika statikog povezivanja i statikog
lana, 491
Korak 6: Implementacija
631
632
povezivanje kda, 21
pow(), 46, 163, 596
poziv
C funkcije, 509
destruktora, 246
dinamiki, 375
iz destruktora, 379
iz konstruktora, 379
konstruktora, 234, 238, 240
konstruktora kopije, 241
preko objekta, 379
preko pokazivaa, 378
preko reference, 378
razlika statikog poziva i statikog lana,
376
rekurzivan, 195
statiki, 376
statiki, unato virtualnoj deklaraciji, 379
virtualni, 375
virtualni iz lana klase, 378
virtualnog destruktora, 381
poziv funkcije, 154
#pragma, 484
pravo pristupa, 226
definicija unutar deklaracije friend,
231
friend, 230
i deklaracija using, 442
i konstruktor, 245
i virtualna osnovna klasa, 386
javno, 226
lokalne klase, 270
podrazumijevano, 227
prilikom nasljeivanja, 344
privatno, 226
zatieno, 226, 344
praznina, 30, 31
precision(), 535
predloak funkcije, 211, 391
<>, 211, 392, 398
definicija, 392
definicija funkcije, 393
deklaracija funkcije, 393
djelomina specijalizacija, 405
extern, 394
formalni argument, 392, 403
inline, 394
instantacija, 394, 395
instantacija, eksplicitna, 402
instantacija, implicitna, 397
633
Korak 6: Implementacija
#line, 485
#pragma, 484
#undef, 478
\ (nastavak reda), 474
_ _cplusplus, 478, 510
_ _DATE_ _, 478
_ _FILE_ _, 266, 326, 478, 485
_ _LINE_ _, 266, 326, 478, 485
_ _STDC_ _, 478
_ _TIME_ _, 478
<>, 475
makro funkcija, 391, 479
makro ime, 476
naredba, 474
NDEBUG, 483
pretvorba nanie, 467
pretvorba navie, 467
prevoditelj, 21
prevoenje kda, 21
prijatelj klase, 216, 230
i predloci, 426
printf(), 176
private, 226, 335, 347, 384
private base class. Vidi osnovna klasa,
privatna
programersko suelje (API), 558
programiranje
objektno orijentirano, 13
pogonjeno dogaajima, 13
proceduralno, 12
projekt, 487
protected, 226, 335, 344, 349, 384. Vidi
pravo pristupa, zatieno
protected base class. Vidi osnovna klasa,
zatiena
prototip funkcije, 152, 494
public, 221, 226, 335, 345, 384
public base class. Vidi osnovna klasa, javna
public interface. Vidi javno suelje
pure virtual function member. Vidi
funkcijski lan, isti virtualni
put(), 520
putback(), 527
Q
qsort(), 602
queue, 573
634
R
\r, 60
raise(), 603
raising an exception. Vidi iznimka,
podizanje
rand(), 93, 187, 575, 596
RAND_MAX, 93, 575, 596
randomize(), 93
razlika klase i objekta, 215
rdstate(), 516
read(), 526, 552
realni broj, 40. Vidi tip, double
redoslijed izvoenja operatora, 74
referenca, 144
deklaracija, 144
inicijalizacija, 145
kao povratna vrijednost funkcije, 179
na konstantan objekt, 145
na pokaziva, 168
slinost s pokazivaem, 145
standardna konverzija, 347, 348, 350
trivijalna konverzija, 364
virtualni poziv, 378
visea, 411
za hakere, 145
registarska smjetajna klasa. Vidi
smjetajna klasa, registarska
register, 182
reinterpret_cast, 473
rekurzija, 195
relacija
biti, 354, 563
jedan na jedan, 565
jedan na vie, 565
korisiti, 564
posjedovati, 564
sadri, 354
vie na jedan, 565
vie na vie, 565
resetiosflag(), 538
return, 153, 158
reusability. Vidi ponovna iskoristivost
right, 533
rukovanje iznimkama, 448
run-time type identification. Vidi
identifikacija tipa
S
scientific, 533, 535
scope. Vidi podruje
scope resolution operator. Vidi operator, za
odreivanje podruja
seekdir, 549
seekg(), 529, 548
seekp(), 521, 548
set, 573, 577
set_new_handler(), 604
set_terminate(), 605
set_unexpected(), 605
setbase(), 538
setf(), 532
setfill(), 538
setiosflags(), 538
setlocale(), 601
setprecision(), 538
setw(), 90, 538
shallow copy. Vidi plitka kopija
short, 44
short int, 44
showbase, 533
showpoint, 533, 535, 536
showpos, 533
side-effects. Vidi popratne pojave
SIG_DFL, 603
SIG_ERR, 603
SIG_IGN, 603
SIGABRT, 603
SIGFPE, 603
SIGILL, 603
SIGINT, 603
signal(), 603
SIGSEGV, 603
SIGTERM, 603
simbolika konstanta, 52, 489. Vidi const
inicijalizacija, 54
sin(), 199, 597
sinh(), 597
size_t, 74, 323, 371
sizeof, 43, 73
skipws, 533
skrivanje podataka, 12, 557, 571
skupljanje smea, 11, 320
smart pointer. Vidi pametni pokaziva
smjetajna klasa, 181
635
Korak 6: Implementacija
automatska, 182
imenik umjesto static, 437
mutable, 255
registarska, 182
statika, 185
vanjska, 185
source code. Vidi izvorni kd
specijalizacija predloka funkcije, 405
sqrt(), 204, 597
srand(), 596
sstream, 553, 573
stack, 573. Vidi stog
stack pointer. Vidi pokaziva stoga
stack unwinding. Vidi iznimka, odmatanje
stoga
standard C++, 18
makro funkcije, 573
makro imena, 573
standardna biblioteka predloaka, 577
standardne klase, 578
standardne strukture, 578
standardne vrijednosti, 576
standardni tipovi, 576
zaglavlja, 571
Standard Template Library. Vidi standardna
biblioteka predloaka
standardna biblioteka, 571
standardna biblioteka predloaka, 577
standardna funkcija, 580
abort(), 574, 598
abs(), 591
acos(), 591
asctime(), 588
asin(), 592
atan(), 205, 592
atan2(), 204, 592
atexit(), 598
atof(), 203, 586
atoi(), 587
atol(), 587
ceil(), 592
clock(), 589
cos(), 593
cosh(), 593
ctime(), 588
difftime(), 589
div(), 593
exit(), 210, 249, 544, 600
exp(), 193, 594
636
strchr(), 583
strcmp(), 209, 583
strcoll(), 583
strcpy(), 207, 583
strcspn(), 584
strlen(), 169, 207, 584
strlwr(), 209
strncat(), 583
strncmp(), 583
strncpy(), 584
strpbrk(), 584
strrchr(), 585
strspn(), 584
strstr(), 585
strtod(), 587
strtok(), 585
strtol(), 587
system(), 605
tan(), 597
tanh(), 598
terminate(), 453, 604
time(), 591
tolower(), 586
toupper(), 586
unexpected(), 459, 605
standardna klasa, 578
basic_string, 578
complex, 578
valarray, 573
standardna makro funkcija, 573
assert(), 236, 248, 483, 574
va_arg(), 179, 576
va_end(), 179, 576
va_start(), 179, 576
standardna struktura, 578
standardna vrijednost, 576
standardni tip, 576
standardno makro ime, 573
standardno zaglavlje, 571
state. Vidi tok, stanje
static, 185, 186, 258, 394, 413, 491
razlika statikog povezivanja i statikog
lana, 491
static binding. Vidi povezivanje, statiko
static call. Vidi poziv, statiki
static_cast, 471
statiki poziv, 376
637
Korak 6: Implementacija
F, 51
l, 51
L, 51
u, 51
U, 51
switch, 84
system(), 605
T
\t, 60
tablica virtualnih lanova, 375
tan(), 597
tanh(), 598
tellg(), 529, 548
tellp(), 521, 548
template
Vidi predloak funkcije.
Vidi predloak klase.
template, 211, 392, 405, 409
terminate(), 453, 604
this, 224, 261, 277
throw, 449
bez parametara, 454
lista moguih iznimaka, 458
throwing an exception. Vidi iznimka,
bacanje
tie(), 530
_ _TIME_ _, 478
time(), 591
time.h, 205, 588
time_t, 588
tip, 37
bool, 57
broj, 40
char, 44, 60
char *, 139
cjelobrojna promocija, 47
double, 43, 44
float, 42, 44
identifikacija. Vidi identifikacija tipa
int, 41, 44
iznimke, 449
klasa, 215
logiki, 57
long, 41, 44
long double, 43, 44
long int, 41, 44
pobrojani, 55
pokaziva. Vidi pokaziva
pokaziva na lan klase, 271
pokaziva na funkciju, 196
polje, 102
pravila konverzije, 40, 47
pravila provjere tipa, 40
razlika izmeu char i char *, 142
referenca. Vidi referenca
short, 44
short int, 44
sinonim za pokazivaki tip, 148
type_info, 465
typedef, 71, 490
ugnjeen u predloku, 422
ugnjeeni, i nasljeivanje, 361
ugraeni, 40
unsigned, 44
usporedba, 465
void, 159
wchar_t, 62, 515
wchar_t *, 143
znakovni, 60
znakovni niz, 60, 139
tm, 590
tok
bad(), 516
badbit, 516
beg, 549
binarno pisanje i itanje, 550
cerr, 516
cin, 27, 515, 521
clear(), 516, 548
clog, 516
close(), 545
cout, 26, 516, 517
cur, 549
itanje i pisanje u datoteku, 541
end, 549
eof(), 516
eofbit, 516, 548
fail(), 516
failbit, 516, 522
fill(), 531
flags(), 532
flush(), 520
fstream, 515, 541, 546
gcount(), 526, 552
638
get(), 524
getline(), 526
good(), 516
goodbit, 516
ifstream, 91, 515, 541
ifstream, konstruktor, 544
ignore(), 526
ios, 515
iostate, 516
isalnum(), 529
ispis korisnikih tipova, 519
istream, 515, 546
istringstream, 553
istrstream, 553
izlazni, 26, 513
konvezija u void *, 516
main(), 530
manipulator. Vidi manipulator
meupohranjivanje, 514
mod otvaranja, 545. Vidi mod
ofstream, 515, 541
ofstream, konstruktor, 544
open(), 544
operator !, 517
operator izluivanja, 521
operator umetanja, 517
ostream, 515, 546
ostrstream, 553
otvaranje datoteke, 544
peek(), 527
precision(), 535
put(), 520, 554
putback(), 527
rdstate(), 516
read(), 526, 552
seekdir, 549
seekg(), 529, 548
seekp(), 521, 548
setf(), 532
setw(), 90
stablo nasljeivanja, 515
stanje, 516
str(), 554
streambuf, 515, 529
streamoff, 549
streampos, 548
irina ispisa, 530
tellg(), 529, 548
U
ugnjeena klasa. Vidi klasa, ugnjeena
ugnjeivanje imenika. Vidi imenik,
ugnjeivanje
ukljuivanje asemblerskog kda, 511
ulazni tok, 27. Vidi tok, ulazni
639
Korak 6: Implementacija
V
\v, 60
va_arg(), 179, 576
va_end(), 179, 576
va_list, 178, 576
va_start(), 179, 576
valarray, 573
values.h, 43
vanjsko povezivanje, 492
varijabla, 35, 37
deklaracija, 38
lokalna, 78
podruje, 78
pridruivanje vrijednosti, 38
vidljivost unutar bloka, 77
vector, 573, 577
vezana lista, 336
izbacivanje lana, 339
pokaziva glava, 337
pokaziva rep, 337
pomou predloaka, 408
umetanje lana, 337
vezano prijateljstvo, 426
virtual, 375, 381, 384, 556
deklaracija virtualne osnovne klase, 384
virtual base class. Vidi virtualna osnovna
klasa
virtual call. Vidi virtualni poziv
virtual function member. Vidi virtualni,
funkcijski lan
virtualna osnovna klasa, 384
deklaracija, 384
dominacija, 387
i pravo pristupa, 386
inicijalizacija, 387
pristup lanu, 385
virtualni
isti virtualni funkcijski lan, 380
destruktor, 380
funkcijski lan, 343, 374
virtualni poziv, 375
iz destruktora, 379
iz konstruktora, 379
odreivanje lana, 378
preko pokazivaa, 378
preko reference, 378
zaobilaenje, eksplicitno, 379
viestruko nasljeivanje, 335
void, 159
volatile, 54, 139, 413
const_cast, 471
funkcijski lan, 257
vptr, 375
vtable, 375
W
wchar_t, 62, 515
wchar_t *, 143
werr, 516
while, 90
640
width(), 530
wifstream, 515
win, 516
wios, 515
wistream, 515
wlog, 516
wofstream, 515
wostream, 515
wout, 516
wrapper. Vidi omota
write(), 520, 552
ws, 538
wstreambuf, 515
wstring, 572
X
xor, 70
xor_eq, 70
Z
zagaenje globalnog podruja, 435
zaglavlje, 26, 155, 494
algorithm, 573
assert.h, 236, 483
bits, 573
cassert, 572
cctype, 572
cerrno, 572
clocale, 573
cmath, 573
complex, 573, 578
complex.h, 205, 206
csetjmp, 572
csignal, 572
cstdarg, 572
cstdio, 573
cstdlib, 572, 573
cstring, 572
ctime, 572, 588
ctype.h, 528
cwchar, 572, 573
cwctype, 572
deque, 573, 577
exception, 572
float.h, 205
fstream, 573
fstream.h, 91, 206, 541
i statiki objekt, 258
iomanip, 573
iomanip.h, 205, 206, 538
ios, 573
iosfwd, 573
iostream, 573
iostream.h, 26, 205, 206, 513, 515,
517, 521
istream, 573
iterator, 573
limits.h, 43, 205, 206
list, 573, 577
locale, 573
locale.h, 205, 206
map, 573, 577
math.h, 43, 46, 193, 199, 203, 204,
205, 206
memory, 572
new, 572
new.h, 251
numeric, 573
ostream, 573
queue, 573
set, 573, 577
sstream, 553, 573
stack, 573
standardna, 571
standardne datoteke, 205
stdarg.h, 178, 205, 206
stddef.h, 323
stdexcept, 572
stdio.h, 126
stdlib.h, 93, 187, 205, 206, 210, 325
streambuf, 573
string, 572, 578
string.h, 142, 205, 206
strstream.h, 553
to u nj ne staviti, 497
to u nj staviti, 495
time.h, 205, 206, 588
typeinfo, 572
typeinfo.h, 465
utility, 572
valarray, 573
values.h, 43
vector, 573, 577
Korak 6: Implementacija
641