P. 1
04- Funkcije

04- Funkcije

|Views: 121|Likes:
Published by kenanscribd

More info:

Published by: kenanscribd on Sep 17, 2010
Copyright:Attribution Non-commercial

Availability:

Read on Scribd mobile: iPhone, iPad and Android.
download as PDF, TXT or read online from Scribd
See more
See less

09/17/2010

pdf

text

original

5.

Funkcije

Test prvoklasne inteligencije sastoji se " sposobnost i isrovremenog drianja dviju suproinih ideja " glav), a da ,re priton: zadrii sposobnostfunkcioniranja.

ScOII Fitzgerald (/896-/940)

U ovom poglavlj II upoznai cerno se s funkcij ama: I~ ihovi III znacen] ern i namj enom, razlikom izmedu deklaracije i definicije, tiporn funkcija i argurnentima. Proucit cerno sto se moze fu nkcij i prenij eti kao argument, a sto ona mote vratiti kao rezul tal Ie kako funkcije djeluju na objekte kOJ i $U deklar irani izvan njena tijela Upoznat cemo znacenje kljucne rijeci inline, preopterecenja funkcije i predloska funkcije. Na kraju cemo posebnu paznju posvetiti funkciji main I) j standardnim funkcijama.

5.1. Sto su i zasto koristiti fu nkcije

U svakorn slozenijern prograrnu postoje nizovi naredbi koji opisuju neki posiupak zajednick: nizu ulaznih vrijednosti Do sada, ako srno htjeli ponoviti isti postupak s drugim pcdacima, rncrali smo preslikati sve naredbe na sva mjesta gdje se taj postupak koristi, Mane takvog pristupa su ccite: osirn sto programski k6d postaje glornazan, otezava se i ispravak pogresaka - primijcti Ii se pogreska u postupku, ispravak je potrebno unijeti na sva mjesta gdje smo postupak koristili.

Kako bi se takvo ponavljanje izbjeglo, C++ nudi rjesenje tako da se pouavljani postupak smjesti u zasebnu cjelinu - funkciju, te se pozove na mjestima gdje je to potrebno, Fuukcije se pisu kao zasebni blokovi naredbi prije ili poslije glavne funkcije main () _ Poziv funkcijc se obavlja take da se izvodenje glavnog k6da trenutno prekine te se nit izvodenja prenese u funkciju. Nakon SIO se kod u funkciji izvede, glavni program se nasravlja od sljedeee naredbe iza poziva funkcije (slika 5, I). Gledano izvana, funkcije se ponasaju poput zasebnih cjelina tije unutarnje ustrojstvo ne mora biti poznaro korisniku Funke ije. Pozivate Ij mora znati SIO neb fu nkcij acini, no n ije mu b itno kako ona to tini. Funkcije imaju parametre iii argumente (engl. para meters, arguments) koje pozivatelj mora zadati prilikom poziva, tc vracaju povratnu vrijednost (eng]. return value) pozivatelju cirne ga informiraju 0 rezultatu svoga rada.

Funkcije Je moguce pozvati i iz neke druge funkcije (prirnjence poziv funkcije tunkc i j aTreca (I iz funkcije funkci jaDr.uga. () na slici 5 I), pa cak i iz sebe same. Svaka se funkcija moze pozivati neograniceni broj pula.

Razbijanjern koda II funkcije, dopnnosi se modularnosti prograrna - program se cijepa na manJc cjel ine koje je lakse koristiti. Ked ujedno postajc citljivij i i razurnlj ivij i

157

158

5, FunkCije

--- ~ !
1/ " .
.". r e t u rn . . .. _"! !
int main() { , j I
/1 ... j
funkcijaPJCvail; oIIII-i ~
II ' , j
/1 ... j I'e" '''P Cee" " (
funkc~JaDruga(l, ~ ~l~ ~ f unk c i, j ar r e ca i) {
1/ . . I fUnkCl.JdTreCa (I, ~ ~ II ...
/ / ... / / .. , J return; l
f urikc i JilIJcuga (); "<F ~ ~ r e c ur n , =n
I! "
[Unkel]aCetvrta():
// u': cl.lnkcijaCec"·rta (j (
return 1;
i
// ,- ,
) return; l Slika 5.,1. Izvodenje programa s funkcijama

llusuirajmo to vrlo jednostavnim primjerom - prograrnorn za racunanje binornnih koefic ijenata po fo rmul i

(f!) p!

r = rl(p-r)1 .

Bez koristenja funkcija program bi izgledao ovako:

#inelude <iostrearn> using na,"e~pace std;

int m_ain II {

int i" p, r; cout « "p cin» pi cout « "r cin » r;

long brojoik ~ 1;

for (i ~p; i > 1; i--) broj n.i k *~ i;

long oazivnik ft 1;

for {'i ~i; i > 1; i--j nazi vni k ":10:= .i :

for (i - I' - r , i > 1; i --) naz i vru k ,~ i;

II r",euna 1"

II racune r I

/ / rae una (p-r) I

cout « broj~ik j nazivnik « endl; return 0;

UUlllah ,U llutlJ Iv<o m HIli . sucue pcUJC u h.lIJ "ll" ,e IdC<UldJll lah.[uIIJ<::k II til "JUI"-U, odnosno uazivniku binomnog koeficijenia"

Pretpostavimo da imarno na raspolaganju gUl.UYU tuukciju eel' L0L L J'" \ i hUJ" lacuna faktorijelu broja, U tom slucaju gornji kod bismo ruogli napisau ovako:

#include <iostream> using namespace std;

int ma i.n t ) I int p, r; eout « "p c i n »'p; . cout «"r cin » r;·

H;

".

,

cout « fak~or~jel(p) ! faktorijel(r) I faktorijel (p - r ) «'endl;

return 0; .

Ovaj ked j<: vise nego OCiLO preglednijt I. razumlj iviji cd prethodnoga SWVI.Se. runkcije koje napiserno mogu se pohraniti u biblioteke tc se zatim ukljucivati i u programe koje kasnije piserno, bez potrcbe da se kod tih funkcija ponovo pise iii kopira. Nasu funkciju

ra kt o ri jel () rnozerno tako sprerniti u biblioteku mat ern. Pozelimc li kasnije napisati

program koji ireba izracunavau fakiorijele (na primjer za rjesavanje zadataka iz vjerojamosti), jednosravno cerno ukljuciti biblioteku JH8Cem 1I nas projekt. Uz prevoditelje $00 redovito isporucuju biblioteke s nizorn gotovih funkcija koje obavljaju tipicne zadatke kao sto $U racunanje kvadratnog korijcna, pristup datotekama ih manipulacija znakovnirn nizovima. Vecina tih funkcija je standardizirana, te ce neke od njih biti opisane kasnije U ovorn poglavlju,

5.2. Deklaracija i definicija funkcije

Poput varijabli, i funkcije treba prije prvog poziva deklarirau Deklar aciju tunkcije obznanjuje naziv funkcije, broj i tip parametara te tip njene povratne vrijednosti Deklaracija ne sadrzi opis illO i kako funkcija radi, ana daje tek proto tip funkcije (engl. function prototype). Deklaracija funkcije ima oblik:

<povrdtHi_r:ip> ime_fuflkcije ( qip> d,'gl, <tip> ary2 I:

TI]J ispred irnena funkcije odreduje kakvug ce upa biti pcdatak kojeg ce tunkctja vracau pozivatelju kao rezultat svoga izvodenja. Argument! unutar zagrada iza imena funkcije su podaci koji se predaju funkciji prilikom njena poziva. Broj i tip tih argurnenata 1110ze biti proizvoljan, s time da funkcija moze biti i bez argurnenata, a mogu se deklarirati J

r I stina, k6d bi se, kracenjem d ijela ""I.'IVsK~ u brojniku I uazrvn ,I.u, '''''gdU napisau dik~"IIJ~, sa sarno dvije for petlje, ali bi to umanjilo cfekt primjera:

160

5. Funkcije

funkcije s neodredenirn brojem argumcnata. Broj argumenata, nj ihov redoslijed l lip nazivaju se potpisom funkcije (engl . funct iOI1 signature).

Kao jednostavan prirnjer uzrnimo funkciju koja racuna kvadrat broja. Nazovilno tu funkciju kvadr at , Argument te funkcije bit ce broj x koj i treba kvadrirati; uzmimo da je taj argument tipa float. Funkcija ce kao rezulrat vracati kvadrat broja; pretpostavirno da jc [0 broj tipa double. Dekiaracija funkcije kvadrat () izgledat ce u tom slucaju ovako:

double kvadratlfloat xl;

Ovo je sarno prototip funkcije Za sada niSIl10 definirali funkciju - niSIl10 napisali kc)d same funkcije, cdnosno nisrno orlredi li SlO i kako funkcija radio Definicija funkcije (engl. Junction definitioni kvadr e t () izgledala bi:

double kvadr a r (float x I { return x • x;

Naredbe koje se izvode prilikom pOZiV5 funkcije cine tijelo funkcije (engl. [unction body), Tijelo funkcije uvijek pocinje prvom naredbom iza lijeve viticastc zagrade {, a zavrsava pripadajucom desnorn zagradom I U gornjem primjeru tijelo funkcije sastoji se sarno od jedne jediue naredbe koja kvadrira x. Kljucna rijec return upucuje na to da se umnczak x ' X prenosi kao povratna vrijednost u dio programa koji je pozvao funkciju.

Deklaracija i definicija funkcije mogu biti razdvojene i srnjestene u potpuno razlicitim dijelovima izvornog koda. Deklaracija se mora navesti II svakom prograrnskom odsjecku gdje se funkcija poziva, prije prvog poziva. To je potrebno kako bi prevcditelj znao generirati strojni ked za poziv funkcije (taj k6d ovisi 0 potpisu funkcije), Definicija funkcije, se naprotiv, smjesta u sarno jedan dio koda. Svi pozivi te funkcije ce se prilikom povezivanja programa usmjeriti na tu definiciju.

Ako se definicija i deklaracija razlikuju, prevcditelj ce javiti pogresku. Moraju se poklapati tip funkcije te broj, redoslijed i tip argumenata.

lmena argumeuata u deklaraciji i dcfiniciji funkcije mcgu se razlikovati. Siovise, II deklaraciji funkcije imena argumenaia rnogu sc izostaviti, sto znaci cia smo gornju deklaraciju mogii napisati i kao

double kvadra t (float) ;

Nakou sto je funkcija deklarirana (i eventualno definirana), mozerno ju pozivati iz bilo kojeg dijela prograrna tako da navederno naziv funkcije te unutar zagrada (I specificiramo pararnetre - mozerno reci da na naziv funkcije primjenjujemo operator (I

5.2. Deklaraclia I oefirucua funkcije

161

Pcgledajmo kako bi izgledao program za ispis tablicc kvadraia brojeva II cijcm sc glavnorn dijelu poziva funkcija ~vadI a L (i

Hinclude <iosrream> ~include <iomanip> using namespace 'ltd;

double kvadrat(float);

// deklaracija funkcije

inc ma i n II I

f o r t i nt, i 1; i <- 10; iH) COUt « setw(S) « i

« setwllO) « kvadz-a t Li) « endl; return 0;

double kvadrat(float x) { return x ~ x;

// definicija [unkcije

Uocirno u gornjcm kcdu razliku izmedu argumenta u pozivu funkcijc i argumenta u dcflniciji funkcije, Argument x II definicij i je formalni argument (engl. formal argument), simbolicko ime kojim prevoditel_j barata tijekom prevodenja tijela funkcije. Taj identifikator je dohvatljiv same unutar funkcije, dok za kod izvan I1JC nije vidlj iv. Kada sc program izvodi, pri pozivu funkcije se formalni argument inicijalizira stvarnim argumentom (engl. actual argument), 0. konkretnorn vrijednoscu - 1I gornjem prirnjeru Je to vrijednost varijable i , Imena formalnog i stvarnog argurnenta nc moraju biti jednaka. eak, kao sto je i slucaj II primjeru, ne moraju biti jcdnakog tipa - uobicajenim pravilima pretvorbe srvarni argument ce biti sveden na lip formalnog argumenta. Naravno da u pozivu funkcije stvarni argument rnoze biti i brojcana konstanta.

t~ lSJ L

U protivnom ce prevcditelj javiti pogreSku. a prirnjer, na pokusaj poziva gore

de fill irane fu n kc ije k vad r a t I) narcdborn

kvadr e t t e , 3);

1/ pogre§ka: argument viska

prevoditelj ce javiu pogresku: "Suvisni parametar u pozivu funkcije kvadrattfloat) ... " Ovakva Slroga sintaksna provjera poziva funkcija mozc zasrnetati pocetnika. Meduum, cna osigurava ispravno prevodenje i rad programa: prevoditelj ce uociti pogresku u kodu, ispisau poruku 0 pogresriorn pozivu funkcije i prekinuti postupak generiranja izvrsnog prograrna. Ako bi prevoditelj, unatoc pogreski dozvolio generiranje izvrsnog

162

5. Funkciv>

koda, nepravilan poziv funkcije bi izazvao ncpredvidiv prckid prcgraina tijekom njegovog izvodenja,

svodi bro]

Upravo da bi se omoguciia poipuna sintaksna provjcra, deklaracija funkcije mora prethoditi prvorn pozivu funkcije".

Obratimo sroga na trenutak paznju strukturi prethodnog prograrna nakon pretprocesorske naredbc ~ include slijcdi deklaracija funkcije xvadr e t () _ Zatim slijedi glavna (main ()) funkcija unutar koje se poziva funkci]a kvad r a t (), a na kraju se nalazi definicija funkcije kvadr a t (). Ako bi deklaracija funkcije bila izostavljena, prevoditelj bi tij ekom prevodenja izvornog koda II funkcij i rna i n II naletio na nj em u nepoznatu funkciju kvadr e t (), te bi prijavio pogresku.

Druga je mcgucnost da se definicija funkcije kvadr ar I) prebaci na pocetak koda. U definiciji funkcije je implicitno sadrzana i njena deklaracija, te ce ona tada biti deklarirana prije poziva 1I funkcij i main I). Shodno tome, u jednostavni_j im prograrnima je dovoljno sve definicije funkcija staviti ispred funkcije main (), Ij funkciju main () staviti kao zadnju funkciju u kodu. Medutim, situacija postaje zamrsena ako se Iunkcije medusobno pozi vaj u, jedna iz druge.

5.2.1. Deklaracije funkcija u datotekama zaglavlja

U slozenijirn prograrnima se izvorni k6d obicno razbija u vise razlicitih datoreka - modula. Osnovni motiv za to je brze prevodenje i povezivanje programa: promijenimo Ii kod neke funkcije, bit ee potrebno ponovno prevesti same modul u kojem je doticna funkcija definirana. Prevodenjem se stvara novi objektni kod promijenjenog mcdula, koji se potom povezuje s vee postojecim objektnim kodovima ostalih modula .

. Buduci da se u tako rasclanjenim programima lako moze dogoditi da neka funkcija bude pozvana iz razlicitih modula, necphodno je dcklaracije pozivanih funkcija ucinit: dostupnima iz bilo kojeg rnodula. Stcga se deklaracije funkcija stavljaju u zasebne datoteke zoglavl]a (engl. header files), koje se zatim pretprocesorskom naredborn ;include ukljucuju u sve datoteke izvornog koda u kojima se neka od deklariranih funkcija poziva.

Zanirnljivo je spornenuti da je ovaj princip prvo uveden u jezik C++, da bi tek potorn bio prihvaceo u jeziku C i ukljucen LI ANSI C standard [Stroustrup'H].

52. Deklaraciia i defmiciia funkcije

163

Prikazimo ovakvu organizacij u koda na primjeru apsrrakinog programa s dvijc lunkcije (nazvat cerno ih fun kc i j a+rva () i f urikc i j auruqa (I) koje se pozivaju iz glavne (main I) funkcije, a LIZ to se jedna funkcija pozrva iz druge funkcije. Prvo cemo napisati k6c1 kako bi izgledao srnjesten 11 jednu datoteku:

void funkcijaPrva(); void funkcijaDruga();

II deklaracije funkcija

inc ma i n t ) {

II

f unkci j "p rva (J ; I! ... funkcijaDruga();

furikc i j a Pr va Lr !

II ...

/1 definiGija funkcije

funkcijaDrugal) { II ...

tunkc Lj avrva () ; I I .. -

II. definicija funkcije

Sada cemo ta] k6d razmjestiti u tri odvojena modula, koje cerno nazvati poglavni cpp, funkl. cpp i funk2. cpp; u prvi rncdul cemo smjestiti glavnu (main () funkciju, a u potonje module definicije funkcija. Pogledajmo sadrzaje pojedinih modula:

# include "funkl. h" tinclude "funk2.h"

//ukljucLlje deldaracije funkcija

int main() {

II ... funkGijaPrva () i 1-1 funkcijaDruga();

void·funkcijaPrva() [ II ...

II definici j a funkci j e

164

5. Funi<cije

lIinclude "f unx l vr."

void funkcijaDruga(){ / I "funkcijaPrva I); /! .,.

II definicija funkcije

U gornjirn kodovirna vazno je uocu: pretprccesorske naredbe za ukljucivanjc daroreka zaglavlja f un k L. h, odnosno fu~k2. h, u ko_jima cemo deklarirari funkcijc definirane 11 tunk l cpp, odnosno Eunk2. cpp:

extern void funkcijaPr.va();

II deklaracija funkcije

extern void funkcijaDruga(J;

II deklaracija iunkcije

Ova uk ljucenja daroteka zaglavlja su neophodna zbog zahtjeva da deklaracija funkcije mora prevoditelju biti poznata prije njenog prvog pozivanja. Buduci da se u glavnoj funkciji prozivaju obje funkcije, na pocetku datoreke poglavni cpp morali SI110 ukljuciti obje deklaracije. Takoder, kako se funkcijaPrva () poziva iz funkcijaDruga I), na pocetku datoteke f un k? .cpp morali smo ukljuciti deklaraciju funkcije f un kc i j e Pr ve (). Ako se u neku daroteku i ukljuc: neka suvisna datoteka zaglavlja, nista bitno se nece dogoditi (osim sto ce se program prevoditi nesro dulje). Kako datoteke zaglavlja u priucipu ne sadrze definicije, nego samo deklaracije, nece doci do generiranja nepotrebnog koda.

Uocirno da su irnena datoteka zaglavlja urnjesto unutar znakova < > (manje od - vece od), navedena unutar dvostrukih navodnika " .._ To je naputak procesoru da tc datoteke prvo treba traziti u tekucern kazalu (diretaoriju). Ako ih tamo ne pronade, pretrazuju se kazala koja su definirana u korifiguraciji prevoditelja, prilikorn njegova instal i ranja.

Kljucna rUec ext e rn ispred deklaracija oznacava da 5U funkcije definirane u nekoj drugoj datoteci - ona nije neophodna za pravilno prevodenje zaglavlja,

Vrlo je vazno dobro organizirati datoieke prilikorn razvoja slozenih prograrna.

Pravilnirn prisruporn problemu organizacije koda moze se ustedjeti velika kolicina vrernena (tc zivaca, kaye i neprespavanih noci) prilikorn razvoja. Organizacija se dodatno komplicira uvodenjern razreda te predlozaka funkcija i razreda. Zbog tih razloga, organizaciji koda bit ce posveceno zasebno poglavlje t 5_

53. Tip funkcije

165

5.3. Tip funkcije

Tip funkcije cdreduje kakvog ce tipa biri podatak koj i funkcija vraca pozivajucem kodu. Tip se funkciji pridjeljuje prilikom deklaracije, tako da sc ispred imcna [unkcije navede identifikator tipa, na prirnjer:

double kvadrat(float xl;

float kvadratniKorijen(float xl; char *gdjeSiMojaNevidjeoaLjubavl();

Funkcija kvadr a c I) je tipa double, funkcija kvadra t n i Ko r ijen (J Je tipa float, a gdjeSi~jojilNevidjenaLjubavi I) je tipa char • (ona vraca pokazivac na znak), Funkcija rnoze opcenito biti i korisnicki definiranog tipa, Konkretnu vrijednost koj u funkcija vraca odreduje se pomocu naredbe return u definiciji funkcije, sto mozerno ilustrirati jednostavnim primjerorn, funkcijorn apsoLut no II:

float apsolutno (float x I { return Ix >- 01 ? x : -x;

Uvjetni operator radi na sljedeci nacin: ako je argument x veci iii jednak nuli, rezultai operatora je jednak vrijednosti argumenta, a u protivnorn slucaju rezultat Je argument s prornijenjenirn predznakorn (odnosno apsolutna vrijednost). Naredborn return se rezultat operatora proglasava za povratnu vrijcdnost, izvodenje funkcije se prekida te se vrijednosr vraca pozivajucem programu, Pararnetar uaredbe cecum opcenito moze biti bilo kakav broj, varijabla iii izraz koji se izracunava prije zavrsetka funkcije. Pri tome tip rezultata izraza mora cdgovarati tipu funkcije,

Ako je rezultat izraza naredbe return razlicirog tipa od tipa funkcije, rezultat se (ugradennn iii korisnicki definiranim) pravilima pretvorbe svodi na tip funkcije. Stoga ce funkcija anso l ut no (I deklarirana kao:

double ilpsolutnolfloat xl return (x >- 0) ? x -x;

vracau rezultat tipa double, unaroc tome sto je argument, odnosno rczultat re turn naredbe tipa float Naravno da nerna previse smisla ovako definirati funkciju upa double buduci da se pretvorbom rezultata u naredbi return ne dobiva na tocnosti povratne vrijednosti. Medutim, irna srnisla definirati funkciju:

long apsolutnoCijelo(floa~ xJ return Ix >= 0) ? x : -x;

koja ce (ako argument nije prevelik) vracati cjelobrojni dio arguments. Rezultat uvjetnog pridruzivanja je opet float, ali kako je funkcija deklarirana kao long, pozivajucem kodu ce biti vracen samo cjelobrojni clio argurnerua.

166

5. FunkciJe

U pozivajucern kodu rezultat funkcije moze biti dio proizvoljnog izraza. Konkretno prije definiranu funkciju kvadr a t () mozemo koristiti u aritmetickirn izr azima s desne strane operatora pridruzivanja, tretirajuci ju kao svaki drugi double podatak. Na primjer:

float xNaPetu - kvadrat(x) * kvadrat(x) * x; double·cNaKvadcat - kvadrat(a) + kvadrat(b);

Moguce je cak pozi v fun kcij e smjestiti kao argument poziva funkc ije:

f Ioa txNa4· = kvadrat (kvadrat (x) ) ;

Rezultat funkcije se moze i ignorirati. Funkciju kvedr ar () smo mogli pozvau i ovako:

kvad;::at(5);

Iako je smislenost gornje naredbe upitna, poziv je sasvim dozvoljen. Ako funkcija ne treba vratiti vrijednost, to se moze eksplicitno naznaciti tako da se deklarira tipom vo i d. Obicno su to funkcije za ispis poruka iii rezultata ovisnih 0 vrijednostima argumenata. Na primjer, zarreba Ii narn funkcija koja ce samo ispisivati kvadrat broja, a sam kvadrai nece vracati pozivnorn k6du, deklarirat cerno i definirati funkciju:

vo i.d ispisiKvadrat I float x) ( cout « Ix * x) « endi;

":';. :_.1:~:,~~.rn;

Zadatak. Napisite program za odredivanja dana u tjednu koristenjem dviju funkcija cije su deklaracije:

illtiia,~nevakUbroflint dan, int mj, long LjetoGospodn.jeJ; v?,id9:~ySj~dnu Ii 11 t) ;

Prva funkcija neka pretvara zadani datum u cijeli bra) (izmedu 0 i 6), a drug a funkclja shodno 10m broju neka ispisuje tekst naziva dana u tjednu.

Buduci da funkcija tipa void nista ne vraca pozivnom kodu, naredba return ne smije sadrzavari nikakav podatak, Stovise, u ovakvim slucajevirna se naredba return moze i izostaviti.

5.4. lisia arg umenaia 167

Funkcija tipa void ne moze se korisuti u aritrnctickim operacijama, pa cemo gore definiranu funkciju .i sp i s i Kvadr a t () stoga uvijck pozivati na sljedeci nacin:

ispisiKvadrat(lOI;

Nize navcdeni POkUSRJ pridruzivanja prouzrocit CC pcgresku prilikorn prevodenja:

float a - ispisiKvadra~II0);

II pogreska

Povratak iz funkcije je moguc s bilo kojeg rnjesta unurar tijela funkcije. Stoga sc naredba r e r u r o rnoze pojavljivau r na vise mjesta. Takav primjer irnarno u sljedecoj funkcij i:

void I a kons k i (int odqovor i sw i, tch (odgovor) ( case 0:

cout « "N",".' return;

case 1:

cout « "Da"; return;

/1 1. return

1/ 2 .. return

cout, « "Ne kada s arn bio rake need.lucan, , .

• 1 a rnozda i n i sarn? ~ Hi

! / 3 _ return

U gornjoj funkciji postoje dvije eksplicunc r.eturn naredbe, te podrazuruijevani return na kraju tijela funkcije.

Na kraju, uocirno jednu bitnu razliku kod deklaracija funkcija u prograrnskom jeziku C++ u odnosu ria deklaracije u jeziku C: u prograrnskom jeziku C, tip funkcije u deklaraciji/definiciji se smije izostaviti - u tom slucaju prevoditelj pridruzuje toj funkciji podrazurnij evan i tip in t. Zato sve funkcije u C k6du ne moraj u b iti deklarirane prije prvog poziva, Naleti Ii C-prevoditeJj na poziv nedeklarirane funkcije, on ce pretpostaviti da je ta funkcija tipa Lo t . Naprotiv, u programskorn jeziku C++ lip funkcije JC obavezan i izcstanak upa u deklaracij i, od nosno nai lazak na nedeklariranu funkcij u rezultira pcgreskom tijekom prevodenja.

5.4. Lista argumenata

Argument] funkcije su podaci koji se predaju funkciji da ih ona obradi na odgovarajuci nat in postupkom odredeuim u definiciji funkcije, Argument funkcije rnoze hiti bi!o koji ugradeni iii korisnick i definirani lip podatka (objekta), odnosno pokazivac iii reference na nek! takav objekt. Tip void se ne moze pojaviti u listi argurnenata funkcije (buduci da taj tip ne definira nikakvu konkretnu vrijednost). Dozvoljeno Je prosljedivanje pokazivaca na tip void

168

5. Funkcije

5.4.1. Funkcije hez arqurnenata

Neke funkcije za svoj rad ne iziskuju argumente .- takve funkcije irnaju praznu listu argurnenata, tj. unurar zagrada se u deklaraciji ne navode argumenti. Take smo u dosadasnj im prirnjerima funkciju main () deklarirali bez argumenaia

int main () II

return ();

A Sl standard za programski jezik C zahtijeva da se unutar zagrada ked deklaracije funkcije bez argurnenata navede kljucna rijec VQidt:

int main{void) 1/

Zbog kompatibilnosti, u jeziku C++ je dozvoljen i ovakav zap is. Medutirn, dosljedno gledano kljucna rijec void je potpuno suvisna, jer bi upucivala da se kao argument pojavljuje nekakav podatak tipa void.

Funkcije bel argurnenata se obicno koriste za ispis poruka iii za ispis podataka ciji rezultat ovisi iskljucivo 0 k6du unutar same funkcije.

5.4.2. Prijenos argumenata po vrijednosti

Uobicajeni nacin prijenosa argumenta u funkcije Je$1 prijenos vrijednosu pcdatka, kao sto smo to vee radili s funkcijama faktorijel (J, odnosno kvadr at I J u ovorn poglavlju Medutim, vrlo Je vazno uociti da prilikom takvog poziva funkcije formalni argument i vrijednost koja se preuosi nisu medusobno povezani. Formalni argument "zivi" same unurar funkcije te je njegova vrijednost po izlasku iz. funkcije izgubljena. Ako u funkciji rn ij enjamo vrij ednost argumenta, prornj ena se nece odraziti na objekt koj i smo navel i u Iisti prilikom poziva Pogledajmo na jednom banalnom primjeru kakve to irna prakticne poslj edice:

Hnclude <Lo st re am> using namespace std;

int DodajSto(int i) ( i +~ 100;

r.eturn: i;

in tmain (J. ,{ in.t n ~··l;

t Ovo je posljedica nedosljednosti nastalih tijekom razvoja prograrnskog jezika C Nairne, u izvornoj varijanti jezika C, argument! Iunkcije nisu se deklarirali unutar zagrada, vee iza liste pararnetara, a ispred tijela funkcije.

5.4. Usia afg umenata

169

DodajSto(nj;

cout « "Radio " « n « ",r,dl; n ~ DodajSto{n);

cou t « "Radio " « n « endl; return 0;

II ispisuje 1

II i sp i suj e i 0]

Iako funkcija Dodaj Sto I) u svojoj dctlniciji uvccava vrijednost argumenta, 0110 barata same s lokalnorn varijablorn koja se prilikom poziva inicijal izira na vrijednost stvarnog argumenta. Pojednostavljeno receno, funkcija je napravila kopiju arguments te cijelo vrijcme radi s nJOIl1_ Prilikorn izlaska se kopija unistava, jer je ona definirana sarno unutar funkcijskog bloka, Zbog toga ce nakon prvog poziva funkcije varijabla n i dalje irnati istu vrijednost kao i prije poziva, Ovakav prijeuos vnjednosti funkciji naziva se prijenos po vrijednost! (engl. pass by value).

Kako se uvecana vrijedrtost vraca kao rezultat funkcije, tek nakon drugog poziva funkcije, t_j pridrufivanja povratne vrij edriosti var ij ab lin, varijabla n ce doi sta b iti uvecana.

CitatelJu naviknutorn na osobine nekih drugih programskih jezika (npr. BASIC, FORTRAN) tinil ce se ovakvo ponasanje argumenata funkcije vrlo nespretnirn i nesh vatlj ivim. Medutim, ovakav pristup ima jednu vel iku odiiku: zastitu podataka u pozlvajucem kodu_ Radi ilustracije, pretpostavimo da se prilikom izvodenja funkcije zaista mijenja i vrijednost stvarnog argumenta (nazovimo to BASIC-pristup). Uz takvu pretpostavku, gornj i program bi isp isao broj eve 10 I i 20 I Razmotrimo sada kakve b i pos Ij ed ice u tak vom s lucaj u ima la naizgled banalna promjena defin icije funkcij e DodajSto (). Na prirnjer, definiciju funkcije ce netko napisati krace kao:

int DodajSto(int i) return i + 100;

Ov irne se srnisao funkcije n ije prom ijenio - ona i nad alje kao rezultat vraca broj j ednak argumentu uvecanorn za 1"00. Medutim, u tijelu funkcije se vrijednost formalnog argurnenta vise ne mijenja, tako da bi nakon ovakve preinake uz BASIC-priSIUp konacni ishod prograrna bio drukciji Naprotiv, ispis uz: ne-BASIC-pristup ce biti uvijek isti, stogod mi rad iii s argu mentom unutar funkcij e, uz pretposta vku da je povratna vrijednost u naredb i r e turn pravi lno defin i rana.

Ovakvim pristupom II jeziku C++ u velikoj su rnjeri izbjegnute popratne pojave (engl. side-effects'; koje mogu dovesti do uezeljenih rezultata, buduci da je kolicina podataka koji sc izrnjenjuju izmedu pozivnog koda i funkcije svedeua na najmanju mogucu mjeru: na argumente i povratnu vrijednost,

5_ Funkcije

Ako su s rvarn i argument i formal n i argu meru II deklaracij i razl iti till Ii pova, iada se prilikom inicijalizacije formalnog argurnenta na sivarni argument primjcnjuju uobicajcna pravila pretvorbe, navederia U poglavlju 2_

Kao stvarni argument funkcijc moze sc upouijebiti i ncki izraz. U iakvim slucajevima se izraz izracunava prije poziva sa me tunkcij e _ Tako b isrno tab I icu k vadraia brojeva mogli ispisati i pornocu sljedeceg koda:

int i. = 0;

while (i < 10) cout « kvacirat(++i) « endl;

Meduti 111, pri p isanj U tak v ih i zraza va Ij a b iti oprezan

llustrirajmo to pornocu funkcije pow U za racunanje potencije )"', koja je deklarirana u cmath kao:

double pcw(double x, double y);

Gornja cinjenica prouzrocit ce da vrlo vjerojatno k6d:

int n = 2;

cout « pow(++n, nl « endl;

II promjBnjivi rBzultat!

preveden na nekorn prevoditelju ispisuje kao rezultat 27 (rezultat porenciranja: 3'), a na nekorn drugom prevcditelju 8 (2"), ovisno 0 tome da Ii se prvo izracunava vrijednosi prvog iii drugcg argumenta.

A ko prethodn i primj er dru kc U e nap iserno, dob it cern 0 k6d koj i ce imati ist i rezu liar neovisan 0 irnplementaciji pojediuog prevoditelja:

int n = 2; ++n;

cout « pow(n, nl « endl;

II uvijek ispisuje 21

5.4.3. Pokazivac i referenca kao argument

Ponekad se funkcija ne rnoze implernentirati korlstenjem prijenosa po vrijednosti. Prirnjerice, pokusajrno napisati funkciju koja ce zarnijenin vrijednosti dviju varijabli,

S, 4, trsta a(g umeriata

171

Neprom isljeni programospisarelj bi mogao pokusati lclJ problem rijesili 1121 sljedeci nacin:

vo i d zam.ijeni (int I_HV1" int segrt;

.i n t; d ru q i )

i/ pomocna varijaola

// pogresno

segrt ; prvl; prvi = drugi; druq i ; xe q r t :

Prilikom poziva funkcije z ami j e n i (d, 0), unutar funkcije vrijeduosti varijabli p r v i i dr uq i ce biti zamijenjene. Dakle, algoriiam je sustinski korektan. Medutim, po izlasku iz funkcije ta zarnjena nerna nikakvog efekta, jer je funkcija baratala s preslikarna vrijednosti varijabl i a i b, a ne s izvornicima.

Jedno mcguce rjesenje jc upotreba pokazivaca prilikorn poziva Iunkcije. Urnjesto vrijednosti, funkciji cerno proslijediti pokazivace na objekte. Tada cemo funkciju def n irati ovako:

void zamijeniPok(int *prvi, int *drugi) int segrt ; 'prvi;

=prvt = 'drugi;

*drugi = segrt;

Prilikorn poziva, umjesto stvarnih vrijednosti a i b proslijcdit cemo adrese tih objekaia:

z ami j e n i Po,,(&a, &b);

Pokazivaci se, doduse, prenose po vrijednosti, sro znaci da ce formalni argumenti p r v i i drugi sadrzavati kopije pokazivaca, Medutirn, te kopije i dalje pokazuju na iste lokacije na koje su pokazivali stvarni argurnenti. Funkcija je napisana take da obraduje vrijednosti preko pokazivaca (zbog toga irnamo operator' ispred argurnenata pr v i i drugi) - to zapravo znaci cia ce sc zamjena provesr: na lokacijama na koje p rv i i drugi pokazuju. Nakon zavrsetka funkcije p rv i i drugi "urniru", no to nam nije bitno, jer se promjena odvijala (i odrazila) na objektima a i b u pozivajucern kodu.

Ovakav nacin prjjenosa argurnenata postoji u jeziku C, gdje je to i jedini nacin da se unutar funkcije prornjeni vrijednost nekog objekta dcfiniranog Ll pozivaj ucern k6du. Jezik C++ nudi jos clcgantnije rjescnje: prijenos po referenci (engl. pass by reference) . Urnjesto prijenosa pokazivaca, u funkciju z arn jeni () prcnijet cemo reference na vanjske 0 bJ ekte:

void zamijeniRef(int &prvi, int &drugi) { int segrt ~ prvi;

prvi ; drugi;

drugi ; segrt;

172

5. FunkCije

Poziv funkcije sada nije potrebno kornplicirati operatorom " vec je dovoljuo napisati

zamijeniRe£(a, bl;

Sustinski gledano, nerna razlike izmedu pristupa preko pokazivaca iii referenci: rezultar je isti Reference su \I biti pokazivaci koje nije potrebno dereferencirati prilikorn koristenja pa se mehan izarn prenosenja se u j eel no 1]1 i u drugom s I ucaj u na ra zi n i generiranog strojnog kodn odvija preko pokazivaca. Medutim, vee. je na prvi pogled uocljiva veta jednostavnost koda ako koristimo reference. To se odnosi na definiciju funkcije gdje nije potrcbno koristiti operator dereferenciranja -, a narocito 11" poziv funkcije, jer ne treba navoditi operator adrese s, Zaro citatelju najtoplije preporucujemo koristenje referenci. Posebno se to cdnosi na C-gurue naviknute iskljucivo na pokazivace, buduci da 1I prograrnskom jeziku C reference ne postoje kao tip podataka Uza sve to, pokazivac ne mora uvijek pokazivati na postojeci objekt pa, kada baratarno s pokazivacima, valja uvijek provjeriti ne radi Ii se 0 nul-pokazivacu, Naprotiv, referenca se (kao sto vee znamo) uvijek inicijalizira na neki konkretni objekt te je provjera suvisna.

Podsjetimo se da se pokazivaci i reference na razficite tipove pcdataka ne rnogu pridruzivati. Pokusarno Ii funkcij u zami j en i Po k (I pozvati talco da j oj prenesemo kao argument pokazivac na nesto slo nije int, prevcditclj ce javiti pogresku pril ikom prevodenja:

float a ~ 10.; iot b ~ 3;

z am i j en.i.Pok I va, 'b);

II pogreika: a je float

lvI ed utirn, ako funkcij i z ami j e" iRe f I i urnjesto reference na in t preneserno referencu IlE neki drugi tip podataka, prevoditelj ce sarno uputiti upozorenje:

float a ~ 10.; intb ;. 3; iamij~riiRef(a, bl;

II Opte2!

Kako se proslijedeni lip razlikuje od tipa formalnog argumenta (potrebno je obaviti pretvorbu float U I nt.), prevoditelj ce generirati privrernenu varijabJu tipa int u koju ce srnjestiti osakaceni floar. U funkciju ce se prenijeti adresa te privrernene varijable. U funkciji ce biti provedena zamjena vrijednosti izmedu privremenog objekta i varijable b. Pri izlasku iz funkcije privrerneni objekt sc unistava pa ce nakon gomjeg poziva funkcije obje varijable imati vrijednost 10.

V JCI ojauro J<.: svakoru pdilJlY IJClIl CI{dlcljU J""IU ll<i ,;~ CL PVLIVlllid tUil"'-lj" '"ami) Eo n.i h)~ () I z anu J ~" 1 f{e f (I ne n.ogu kao argu n 10011 LI ua v,",SII bl ojcane kons «I mt: , T C) je i iogicno ako znamo da nije moguce odrediu adresu konstanie.

Pokazivaci 1 reference sc korrsie k~u argumeuu funkcija kada SC zdl pr onujciuu HIJcdnOSl objekta u pozivajucern kodu. To je pornalo u suprotnosti S osnovnom idejom funkcije da 5U argumerui podaci koji ulaze u nju, a povratna vrijednost rezulrat funkcije Mijenjaju Ii se vrijeduosu argumenata, kod ce posiati necitlj ivij I, jer dolaze do izrazaja popratne POJiiW Neupuceni citatelj koda bit ce prisi Iy::n analizirau k6J same runkcije da bi "pohvatao" sve prornjene koja funkcija provodi na argumentirna

Tv ;,e prvenstveuo uduosi na prijeuos ugradenlh upova podataka Zit prijenos p0ljd upotreba pokazivaca Je neizbjezna (vidi odjeljak 5.4,5 na str. 176). Takoder, za korisnicki definirane podatke je prijenos po referenci redovito daleko prakricnije rjesenje buduci da nerna presl ikavanja, kao sto je slucaj pri prijenosu po vrijednosti Naime, ako su korisnicki objekti vrlo veliki, preslikavanje moze oduzeti dosta procesorskog vrernena Ie zauzeti zuatan dio sroga na koji ,<, argumcnti funkcija prebacuju. ZeJimo li pri prijenosu po reterenci za~lili vrijednost argumenta od neovlastenin prOIllJ<'fld unuiar funkcije, tada mozeiuo upotrijebiti rnodifikator CO,,~( kako ce biti opisano u odjeljku 5.4,6 0 konstanrnirn argurneruima, na str. 179,

5.4.4. Promjena pokazivaca unutar funkcije

Pocetnici su cesto vrlo zbunjeni kad uaidu I\d pouebu da UIlU(d[ funkcije prouujene vrijednost nekog pokazivaca proslijedenog kao argument. Na primjer, zamislimo da zelimo napisati funkciju UnesiIme () koja treba rezervirati memorijski prostor te ucitati ime korisnika. Pri tome je sasvim logicno irne proslijediti kao argument. No postavlja se pitanje kojeg tipa mora biti taj argument. Prosljedivanje pokazivaca na znak nece biti dovoUno

vo i d Unt:;$1Ime (cha.!: ,; imt;:) { cout « "Upi!i ime:"; irue ~ new charliOO]; cin » irue;

// QVO IleCe I.ctdlCi

111 L ma i n () (

char <ko r i sn i k r UnesiI~(korisnik) ;

cout « ko r i.sn i k « encr • dele te ll kor i sn i k; return 0;

/1 lOS pozill

174

5, Funkcije

Na veliko razocaranje C++ zutokljunaca, gornj i primjer necc ispisati unescno ime, vee ce po svoj prilici izbaciti neki nesuvisli niz znakova iii cc se blokirati prije nego sro bilo SlO ispi~e. U cemu Je problem?

Do kljuca za razurnijevanja gornjcg problema dod cerno ako se podsjetimo da se argumenti u sve funkcije prenose po vrijednosti. To znaci da se argument naveden 1I pozivu funkcije kopira 1I privrernenu varijablu koja zivi iskljucivo za vrijeme izvoClenja funkcije, Argument mozerno slobodrio mijenjati unutar funkcije, ali prornjene se nece odraziti na stvarni argument naveden 1I pozivajucem kodu, Upravo se to dogada s nasirn argumentorn ko r i sn i k - prilikom poziva se vrijednost pokazivaca ko r i sn i k kopira u privrernenu varijablu i.me Funkcija Unesi l me (I barata s 10m privrcrnenorn vrijednosti, a no sa sadrzajem var ijable ko r i sn i k Adresa rnernorijskog bloka alociranog operatororn new se pridruzuje lokalnoj varijabli, te se ria to mjesto ucitava znakovni niz, Nakon sto runkcija zavrsi, vrijednost varijable ime se gubi, a varijabla korisni k ostaje neprornijenjena. Stovise, prevoditelj ce to uociti i upozoriti nas cia varijabla kor i sn.i k u glavnoj funkcij i nije inicijalizirana - na nama jc da [0 upozorenje prihvatimo i otklonimo njegov uzrok,

Najjednostavnije bi bilo da funkcija kao rezultat vrari pokazivac na novostvorenu varijablu:

char *UnesiIme{char *irne) cout « "Upi§iime:"; ime z new chat[lOO); cin » ime;

return ime

/ I vracs pokaz ivac no nov i. / /objekt

i nt main() 1

char 'korisnik = ''''; /1 inicijaliziramo zbog

.il grevodi telj a. . kor i.s n i k z Unesilme (korisnik);

cout « korisnik « endl;

delete [I ko r i sn i k;

r e t u rn 0;

Ovirne se pokazivac korisnik prenosi kao argument funkciji Unesilmel} te mu se odrnah potorn pridruzuje vrjjednost koju ta funkcija vraca. Pokazivac kor Lsn i k smo morali inicijalizirati da prevoditelj ne bi ispisao upozorenje, iako bi program radio korektno i bez te inicijalizacije.

Time je problem rijesen za slucaj kada imarno u igri same jedan argument kojeg treba vratiti. Medutirn, sto ako baratarno s elva iii vise argurnenata (na primjer uz irne treba unijeti i zaporku) - pokazivace na oba ne mozerno prenijeti kao povratne vrijednosti u p zivajucu funkciju. Rjesenje problema i u tOI11 slucaju nije tako slozeno: umjesto prosljedivaoja vrijednosti varijable, potrebno je funkcij i proslijediti njenu adresu, Argument funkcije ce sada postati pokazivac na pokazivac na znak: sama varijabla korisnik je tipa pokazivac na znak, a njena adresa je pokazivac na pokazivac.

5.4. Lista arqurnenata

175

Unutar funkcije Une s i. lme II takoder [reba voditi 0 tome racuna, taka da se vrijednosti pokazivaca prisiupi pornocu * i me , Evo ispravnog programa (da IlC bisrno dcdatno komplicirali k6d, ogranicili srno se i dalje sarno na jedan argument):

void Unesilme(char **ime)

cout « "Up is i ime ;". 'ime - new charllOOI; cin » ~ime;

int main () [

char *kori5nik; Unesilme(&korisnik) ;

cout « korisrtik « end}; delete [I korisnik;

return 0;

II argument je pokazivac II na pokazivac

1/ pristup preko pokaz i.vaca

II prosljeduje se adresa

1/ uvijek pocistite za

I I sobom' (ne kate se bez II veze da je cistoca ... j

Gornj i primj er ce sada funkcion irati: prosljeduje se adresa varij able ko r i s n i k, te se na taj naein rnoze prornijeniti njen sadrzaj, No sam program time postaje necitljivij i: u pozivnorn kodu je potrebno uzimati adresu, a u funkciji petljati s operatorom '. No reference spasavaju stvar. Nairne, pokazivac na znak je neb tip koji se rnoze proslijediti po vrijednosti, a to znaci da treba proslijediti referencu na njega. Za one koji su na "Vi" sa sinraksom za deklaraciju tipova u jeziku C++, referenca na pokazivac na znak se biljezi char • & deklaracijorn - kljucno je procitati deklaraciju s desna na lijevo. Evo k6da koj i koristi referencu na pokazivac:

void Un@silme(char * & ime)

cout~« "Opi§i ime; Ime = ne,/ char l l.Ou ] ,. cin » ime;

Lrit rnain(l {

char *korisnik; U~esilme(korisnik) ;

cout « korisnik « endl delete (J korisoik; return 0;

i I argument j (; re fe renee if !}a'pokazivac

II pristup preko reference

II prosljeduje se adresa, Ii ali to nije potrebno II'eksplicitno navesti

.i n t main (i I

int b[] ~ (5, 10, 15); <,oeiati (b, 3);

cout « bIOI « endl « bill « endl « b[2] « end L return 0;

II deists je pocistio

176

5.4.5. Polja kao argumenti

Vidjeli smo da kod prijenosa po vrijednosti prornjene argumenaia unutar funkclje nernaj U odraza na stvarnc argumen re 1I pozi vaj uceru k od 11. N a prv i pogled, iZLI zetak od log pravila tine polja, PogJedajmo sljedeci primjer:

void Pocisti(int polje[], int duljina) { whil~ (duljina--)

polje[duljiria) - 0;

Neupuceni cilatelj bi iz gornjeg koda mogao zakljuciti da se polje prenosi po vrijednosti te da ce nakon pozi va funkcije Foci s ti I ), clanovi polja b [I ostati neprom ijen] eni. Medutim, izvodenjem programa uvjerit ce se cia poziv funkcije Pocisti I) uzrokuje traj 110 brisanje svih clallova po U a. Pazlj i vij em citatelj II ce odmah bi ti j asno 0 cemu se radi: -polj e se \l b iti ne prerios i po vrijednosti, vee se prenosi pokazivac na nj egov prvi clan. Stoga funkcija preko tog pckazivaca rukuje s izvornikorn, a ne preslikorn tog polja. Shodno tome, gornju funkciju smo mogl: potpuno ravnopravno deklarirati i na sljedeci nadin:

void Pocisti{int 'polje, int duijina);

Zadatak. Napisite definiciju ovako deklarirane funkcije Pocis ti I) koristeci aritmetiku S pokazivacima

Iako ovakvo ponasanje polja kao argurnenta unosi odredenu nedosljednost U jezik, 0110 je pos ve prakticne narav i. Zarnisl uno da treba funkcij i kao argument pren ij eli po lje od nekoliko trsuea cianovfi. Kada bi pri pozivu funkcije clanovi pol]a inicijalizirali clanove uovcg, lokalriog polja u funkciji, proces pozivanja funkcije bi trajao vrlo dugo, IlC same zbog operacija pridruzivanja pojedinih clanova, vee i zbog vremena potrebnog za dodjeljivanje memorijskog prostora za novo polje. Osim toga, lokalne varijable unutar funkcije se srnjestaju na slog, posebn i dio memorije Gija je duljina cgranicena. Smjesraj dugackih polja na stog vrlo bi ga brzo popunio i onernogucio daljnj i rad prograrna.

Prilikom deklaracjje je rnoguce, ali nije potrebno navesti duijinu polja (ogranicirno se za sada na jednodirnenzionalna polja) - prenosi se same pokazivac na pry; clan. Zbog toga, sljedece tri deklaracije gornje funkcije su istovjetne:

5.4. Lista arqumensta

177

vend FOCi-Sr:.l{ n t p01j~(L iIil duljlLidl; void Pocisti( ot polje(SI, inc dulJina): void Poc i s t i ( n t 'polj e , i.nt duljina);

Dulj ma polJa je uavedeua kao dodatni argument Jun""IJI ~v~l~"', r , SlV vlliuguca'~ c1" se funkcija moze pozvati za ciscenje polja proizvoljne duljine.

Znakovni nizovi su takoder polja, take da za nJ ih vrijede isiovjeura I azrnuuanja. To znaci da funkciji treba prenijeti pokazivac na prvi clan. Kako su znakovni nizovi zak ljuceru nulznakom, podatak 0 duljini niza nije neophodno prenijeti. Na primjer

inc Dul)ir,,,Nizd(c;Ld[ 'niz) int i ~ 0;

while (' (n i z + i))

i ++;

lako puslOji standardna iunkcij« ~trl"" (j koja meum; dUlJlHU Lnakvvlivg 111 La , napbi:lli srno svoju inacicu takve funkcije. Uocimo U uvjetu while petlje kako Je zbog viseg prioriteta operators * bilo neophodno staviti zagrade oko operacije pribrajanja pokazivacu. Da te zagrade nerna, UVJet petlje hi dohvacao prvi clan pclja, re ono sto se tame nalazi (k6d znaka) uvecao za brojac i.

Na pokusaj prosljcdivanja znakovnog niza navedencg u navoduicirna

.i n t n ~ Dulj inaNi z a I" Popo kat spe r L") ;

II pogLeska.!

prevoditelj ce prijaviti pogresku (iii barern upozorenje) kako funkciji pokusavauio "uvaliti" neprornjenjivi niz kao prornjenjivi. Nairne, prema Standardu jezika C++, znakovni niz u navodnicirna jest tipa cons t char [J, tako da gornji poziv predstavlja pokusaj "tihe" pretvorbe nepromjcnjivog II prornjenjivi objekt. Ovorne problemu cemo se detaljnije posvetiti u sljedecern odjeljku.

hob lern prij enosa polj a lunkcij i posta J c S lozen ij i zel iIi se pren ijeu visedirnenzionalno polje Ako su dimenzije polja zadane u izvomorn kodu, prijenos je trivijalan:

ninclude <iOSLrecun.> #include <iomanip> using names pace std;

canSt int redaka - 2; cons[ int stupaca - 3;

void ispisiMatricu(fl",,[ "dl,,(ia~,,) [SLUP"'(;"')) i for (int r - D; r < redaka; r++) (

fo~ .Iint s - 0; S < stupaca; s++)

cout « setwllO) «m[rllsl;

cout --« end l ;

178

5_ Funkcije

int main () (

'flod t rna t ri ce I reda k a ] [s t upaca J

. ([Ll,L2,l.31,

{ 2.1, 2.2, 2.3] );

ispisiMatricu (mat r i ca l , return 0;

Iz poziva funkcije je jasno da se rnatrica prenosi preko pokazi vaca na prvi clan. Stoga je navedeni zapis u deklaraciji funkcije ovakav samo radi bolje razumljivosti. Varijable redaka i s t upac a deklarirane su ispred tijela funkcija i sp i s i.Na t r i cu (J i main (), tako da su one dohvatljive iz obiju funkcija (0 pcdrucju dosega imcna govorit cerno opsirnije u zasebnom poglavlju).

Zadatak. Napisite funkciju ispisiMa tricu II koristeci pokazivace i aritmetiku s pokazivacima. U funkciju prenesite samo pokazivac na prvi clan.

Vidjeli smo da se visedimenzionalna polja pohranjuju U rnemoriju kao nizovi jednodimenzionalnih polja. Stoga je prva dimenzija nebitna za pronalazenje odredenog clana u polju, PUJu nije obavezno ukljuciti u deklaraciju argumerua:

vo i d i sp i s i.Na tricu I Boa t m] J [stupacal, i.n t r eda ka ) { for (int r - 0; r < redaka; r++) {

for (int s - 0; s < stupaca; s++J

cout « satw(lO) <c mlrl (sJ:

CDUt « endl;

Gornja funkcija ce raditi za proizvoljan broj redaka, pa smo pcdatak 0 broju redaka prenijeli kao poseban argument Poteskoce iskrsavaju ako dirnenzije polja nisu zadane u izvomorn kodu, vee se odabiru tijekorn izvrsavanja prcgrama. Sljedeci pokusaj zavrsit ce debaklom vee kod prevodenja:

void ispisiMatricu(float m(II]. int redaka, int stupaca) II pogreska

for (int r: = 0; r < redaka: r++) { for (int s - 0; s < stupaC3; s++) cout « setw(lO) « m[r] l s l . cout « endl;

Argument m [ I [I u deklaraciji funkcije nije dozvoljen, jer u trenutku prevodenja mora biti poznara druga dimenzija (za visedimenzionalna polja: sve dimenzije osirn prve).

U prerhodnom poglavlju smo pokazali kako se clanovi dvodimenzionalnog polja mogu dohvacati preko pokazivaca na pokazivace. Iskoristimo ru cinjenicu pri pozivu funkcije za ispis clanova rnatrice:

5.4. l.ista arqurnenata

179

linclude (iosrream> Hnclude <iomanip> using namespace std;

void ispisiMatricu(float **m, int redaka, int 5tupaca) for (int r ~ 0; r < redaka; r++) (

for (in[ s ~ 0; 5 < stupaca; s++)

cout « set", (10 I « (( float *) m) i r= s tupaca+s I; cout; « e nd L:

i n t main () (

int redaka ~ 2;

canst int stupaca 6 3;

float ('matrica) l s tupece l mat r i ca [0) [0) ~ 1.1;

ma t r ca[Olll] 1.2,

matrica [0) [2) 1. 3;

matrica[l] [01 2.1;

ma t r i.ca [1] I1J 2.2;

matrica[l] 121 2.3;

ispisiMa.tricu ( (float·· I ma t r i ca , redaka, s tupacai r return 0;

new float(redakaj [stupacaj;

Ocito je ovakva notacija tesko citljiva. Daleko elegarunije rjescnje pruzaju korisnicki definirani tipovi - razredi, koji se mogu tako definirati da sadrze pcdatke o pojedinim dimenzijama te da sadrze funkcije za ispis clanova. Razredi ce biti opisani 1I narednim poglavljirna, a za sada uocimo jos sarno da je u gornjem k6du s tupaca bilo neophodno deklarirati kao cons t , buduci da druga dimenzija polja mora biti poznata u trenutku prevodenja koda,

5.4.6. Konstantni arqurnentl

Kada se argurnenti funkciji prenose po vrijednosti, podaci 1I pozivajucem kcdu ostaju zasticeni od prornjena II funkciji, Medutim, neki podaci (poput polja) ne rnogu se preriijeti po vrijednosti, vee ih treba prenijeti preko pokazivaca iii referenci, cime se iziazli opasnosti mozebitnih prornjena prilikorn poziva funkcije. Programer koj i ce pisati funkciju ce sasvim sigurno voditi racuna 0 tome da ne rnijenja vrijednosti argurnenata ako to nije potrebno. No lako se moze dogoditi da kasnije, prilikoru prepravke funkcije smetne tu cinjenicu S lima, ili da netko drugi krene u radikalne zahvate na tijelu funkcije.

180

5. Funkcije

llustrirajmo to na primjeru lunkcije Oc;ljlnaNizd i) koja nam je posluzila za izracunavanje duljine znakovnog niza, Argument nil ce postati pokazivac na nepromjenjivi znakovn i niz dodarno li u dcklaracij i funkcije modifikator COns t ispred identifikatora tipa:

int Dul]inaNiza(const char ·nizJ;

Svaki pokusaj promjene sadrzaja tog n iza unutar funkcije prouzrocit ce pogresku pri I i kom prevodenj a:

int DuljinaNiza(const char *nizl ( //

*niz = 0; return i;

II pogreska: pOKusaj promjene /1 nepromjenjivog objekta

Naravno da treba paziti da se kvalifikator const navede i u deklaraciji i u definiciji funkcije

Drugi vazan razlog zasio je dobro korisiiti kvalifikator cons t kod dcklaracije argurnenata jest taj da se funkciji mogu uputiti kao argument] podaci koj i su deklarirani kao neprornjenjivi. Da bisrno to ilustrirali, deklarirat cerno gornju funkciju bez kva I ifi katora cons t ispred argumenta:

int DuljinaNiza(char 'niz) ( 1/

Sada vise nije moguce prosljedivati kao argument pokazivace na konstantne objekie, jer ih izlazerno pogibelji da se njihova vrijednost eventualno promijeni II funkciji:

int main () (

cons t char' TkoP)eva ~ "Fr enj o Safranek"; cout « DuljinaNiza(TkoPjeva) « end};

cout « Dulj inaNiza ("gospon fulir") « endl; return 0;

II pog-re 5 ka 1/ pogreska

Cak aka i ne mijenjamo vrijednosti na koje pokazivac pokazuje unutar funkcije Dul)inaNiza I), prevoditelj to ne moze znati. Iz svoje paranoje Or) ce sprijeciti gornji pokusaj pridruzivanja nepromjenjivog objekta promjenj ivorn argurnentu kako bi se osiguralo da objekt u svakorn slucaju ostane neprornijenjen (lat. object intactas. Zbog toga, ako funkcija ne mijen_ja svoje argurnente, pozeljno je to obznaniti javno tako da se umetne rnodifikator canst u listu argurnenata.

Naravno da je obrnuto prosljedivanje dozvoljeno: prornjenjivi objekt slobodno se mole pridruziti nepromjenjivorn argumentu. Time se ne narusava inregriret objekta koji se pros ljedu j e.

5.4. Lisla arqurnenata

181

GOIllJe ponasanje je posljedica standardnih pretvorbi pokazivaca: pokazivac na prornjenj ivi objekt se uvijek moze svesti na pokazivac na neprornjenj ivi objekt, dok obrnuto nc ide bez eksplicitne dodjele tipa

5.4.7. Podrazumijevani argumenti

Postoje funkcije koj irna se u vecini poziva prenosi jedna te isla vrijednost argumenta ill argurnenata, Na primjer, u funkciji za racunanje odredenog integrala trapeznom formulom tipicna relativna pogreska rczultata moze biti vrijednost JO-A Kako bi se izbjeglo suvisno navodenje zeljene pogreske u slucaju cia tipicna pogreska zadovoljava, prilikom poziva se pogreska rnoze izostaviti, a u deklaraciji funkcije navesti podrazumijevana vrijednost pogreske.

Na pocetku ovog poglavlja, u programu za racunanje binomnih koeficijenata (vidi kod na str. 159) pretpostavili smo da imamo na raspolaganju funkciju f a k t o r i j e L () Bel ikakve optimizacije, binornnc koeficijente smo racunali doslovno preko formule

(p) p!

r = r!(p-r)!

Svakorn irnalo bistrijern maternaticaru jasno je da se clio umnoska 1I faktorijeli brojnika mozc pokratiti s (p - r)1 u nazivniku, tako da se ukupni broj mnozenja u racunu srnanjuje. Na primjer

(~) 51 5·4 3 2~1 5·4 2. =2!~1=(21)(3'21)=~

Kako iskoristiti IU cinjenicu 1I programu za racunanje binornnih koeficijcnata? Mogli bismo napisati dvije funkcije: jednu za racunanje faktorijele (u nazivniku), drugu za racunanje umnoska svih cijelih brojcva izniedu dva zadana (u brojniku). Time ce se ukupni broj rnnozenja u racunu doista srnanjiti, al i ce se povecati broj funkcija u izvornom k6du.

Obje tako definirane funkcije u sustini bi provodile isti postupak: mnozenje uzastopnih cijelih brojeva. Umjesto da definiramo dvije odvojene funkcije, mozerno poopciti funkciju fa ~torijelll tako da prosirimo listu argumenata dodatnim parametrorn stoj Broj kOJ i ce ogranicavati broj mnozenja. Za uobicajeni faktorijel laj argument bi irnao vrijednost I Oa bisrno otklonil i potrebu za eksplicitnim navodenjern te vrijednosti kod svakog poziva funkcije, definirat cerno podrazumijevanu vrijednost (engl. de/auf! argument value) 10m dodamorn argumentu:

.include <iostream> using nall\espace std;

long fa.ktorije;l (cons t i n t n, canst int stojBroj 1) {

long umnozak - stojBroji

for (int i - stojBroj+l; i <- n; itt) umnozak *~ i;

re[urn umnozak;

182

5. Funkcij"

.i n t main () { int PI r . cout « "p cin » p;

cout « II r.

cin » r;

cout «faktorijel (p, p-r+l) I faktorijel (r) « endl; return 0;

U pozivu funkcije saba argurnenta, svaki od formalnih argumenara (n, odnosno s t o j Broj) ce biti inicijal iziran odgovarajucim vrijednostima (p, odnosno r - r -e 1) Izostavi li se drugi argument u pozivu, stojBroj ce biti inicijaliziran na vrijednost 1, kako je zapisano u deklaraciji funkcijc.

Napornenimo cia ce gornja funkcija faktorijel () korektno raditi samo za pozitivne brojeve (ukljucivo i 0, jer je po definiciji OI=J), te za slucajeve kada su oba argurnenta veca od 0 i kada jc prvi argument veci iii jednak drugcrne,

Zadatak: Napisite [unkciju te+coriies t) koja ce provjeravati da li argument! ispunjavaju ove uvjete, te u protivnom ispisivati poruku 0 pogre§ki i vracati vrijednost -1.

OCilO je cia funkcije s podrazurnljevanim argumemorn (iii vise njih) znace moguce odstupanje cd pravila da broj argurnenata II pOZiVlI funkcije mora biti jednak broju argumenata u definiciji funkcije, Podrazumijevani argument! kompliciraju postupak sintaksne provjcre koju provodi prevoditelj. Da bi se otklonile sve eventualne nedournice zbog neslaganja u broju argurnenata, prevoditclj mora vee prilikom nailaska na poziv funkcije znati da ona moze imati podrazumijcvane argumente. Zbog toga se podrazumijevane vrijednosti argumenata moraju navesti u deklaraciji funkcije.

Ovo je naravno vazno ako su deklaracija i definicija funkcije razdvojene Treba li u tom slucaju ponoviti podrazumijevanu vrijednost u definicij i funkcije? Ne, jer prevoditelj ne usporcduje medusobno vrijednosti koje se pridruzuju argumentu U deklaraciji, odnosno definicij i, taka da ce pokusaj pridruzivanja u definiciji interpretirati kao pokusaj promjene podrazumijevane vrijednosti (cak i ako su one jednake):

i/

/1 deklaracija funkcije:

long faktorijel(const int, const illt 1);

II

Ii definicija funkcije:

long faktorijel(cQllst lnt n, const ·nt stojBroj 1)

II pogre!ka: redeklaracija

II podrazumijevane vrijednosti

II )

54. Lista arqurnenara

183

Prerna tome, u prethodnoj definicij i funkcije treba izostaviti pridruzivanje podrazumijevane vrijednosti:

1/ deklaracija funkcije:

long faktorijel(const int, canst int 1);

I

II definicija tunkcije:

long faktori]el(const int n. const int stojBroj) {

II podrazumjevana vrijednost je II vee pridruzena u deklaraciji

II }

Uocimo kako je u deklaraciji izostavljeno ime formalnog argumenta kojem se pridruzuje podrazumijevana vrijednost, Iako djeluje neobicno, u deklaraciji je to dozvoljeno.

Druga vazna stvar 0 kojoj treba voditi racuna pri pridruzivanju podrazumijevanih vrijed nosti jest:

To znaci cia ni u korn slucaju nasu funkciju faktori jell) ne mozerno deklarirati kao:

long raktorijel lint s t oj Br oj = I, int n);

II pogresk<J

jer pridruzivanjc argumenata pri pozivu funkcije tece s lijeva na desno, pa ce U pozivu funkcije same s jednim argumentom drugi argument ostati neinicijaliziran, Ako se u deklaraciji funkcije podrazurnijevane vrijednosti pridruzuju nekolicini argurnenata, tada svi oni moraju biti grupirani na kraj liste pararnetara:

void GoodBadUgly(int, const char" - "Clint", const char" "Ellie",

cons t char' - "Lee" I;

U ovorn primjeru ukazano je i kako treba paziti prilikorn pridruzivanja pokazivaca. Praznina izrnedu * j - je neophodna, jer bi u slucaju cia napisemo

void redatelj (const char ,_ "Sergio");

if pogre!)ka

prevoditelj prijavio pogresku, interpretirajuci cia se radi 0 operatoru *-.

184

S. Funkcije

S.4,S, Funkcije 5 neodredenim argumentima

Za ncke funkci_je je nernoguce unaprijed tocno znati broj i tip argumenata koj i ce se proslijediti u pozivu funkcije. Takve funkcije se deklariraju tako da se lista argumenata zakljucuje s tri locke (, , .}, koje upucuju da priiikom poziva mogu uslijediti dodatni podaci. Citatelj koji je ikada vidio iii pisao programe u jeziku C zasigurno poznaje standardiziranu funkciju p ri nt t () koja se koristi lit ispis podataka (u jeziku C ne postoje ulazni i izlazni tokovi c i n, odnosno cout.). Ona se moze koristi za ispis bilo kojeg broja podataka razl icitih tipova. Funkcija pr in t f () II jcziku C"+ mogla bi se dek lari rat i kao

Lut pr in t f (cons t cna c' r •• , J ;

Ovirne je odredeno da funkciji p r i n t f () prilikorn njena poziva treha prenijeri barem jedan argument tipa cbar > (pokazivac na znakovni niz). Ostali argumenti nisu neophodni i mogu biti proizvoljnog tipa, Zarez iza zadnjeg dcklariranog arguments nije neophodan, take da smo funkciju print f (I mogli deklarirati i kao:

int printf(coost char- ,;.};

II isto, ali bez zareza

Ovako deklariranu funkciju mozemo pozivati na razne nacine, Na primjer:

printf (" Za C++ spr emn i t \n") ; II .i sp i s same poruke

printf("Nercedes %d %:;;\n", 'broj, oznaka);

II ispis imena, cijelobrojne varijable II 'broj' i znakovl1og niza 'oznaka'

U posljednjem pozivu funkciji printf () prenose se tri argumenta. Prvi argument je znakovni niz koji, uz tekst koji treba ispisati, sadrfi i pcdatke (%d i %3) 0 tipu preostala dva argumenta: cjelobrojnorn argurnentu broj i znakovnorn nizu oz ne ka Umjesto simbolickih imena mogli srno navesti i stvarne vrijednosti:

print£{"Mercedes %.d %s\n", 300, "SL"I;

Buduci da je jedan argument deklariran, izostavljanje argumenaia u pozivu funkcije iii navodenje arguments koji se ne moze svesti na deklarirani tip prouzrocit ce pogresku prilikom prevodcnja:

print.f(); print f (13) ;

II pogre§ka; nedo5taje argument

I I poq re s ka : neodqova re j uc i vt.i.p a rqumerit a

Prilikom prevodenja prevoditelj ne cita sadrzaj pocernog znakovnog niza u kojem su definirani tipovi podataka koji slijede, tako da ne rnoze provjeriti da Ii su tipovi podataka koji slijede odgovarajuce definirani. Na prirnjer:

p r i n t f ("2 + 2 je %5\n", 2 + 2J;

II nepredvidiv rezultat

5.4. Usia argumenaia 185

Prevoditclj ce gornju naredbu prevcsti bez peruke 0 pogreski, ali cc izvodenje Ie uaredbe dati nepred v id iii i i spis.

Kako argument nijc deklariran, prevoduclj ne moze provjeriti tipove argumenata u pozivu Iunkcijc, a time niti provesti preivorba tipova. Stoga se char i sho r c implicitno pretvaraju i prenose kao rnt , a float se prenosi kao double (5tO cesto nije ono sto korisnik ocekuje).

Takcder, vjerujemo da je pazlj ivijern citatelju jasno da:

Nairne. u tom prvom argumentu obicno S\I "kodirani" podaci 0 tipu eventualnih preostalih argumenata. Prilikom izvodenja izvrsnog koda taj se prvi argument dekodira i na osnovu njegova sadrzaja sc odreduje nacin ucitavanja ostalih argumenata.

U dobro pisanim prograrnima koristi se zanemariv broj funkcija s neodredenim argumentima. Da bi se izbjegle zamke koje uzrokuju funkcije s ncodredenirn brojern argumenata i poboljsala provjera tipa, valja koristiti funkcije s podrazumijevanirn argumentirna i preopterecene funkcije (vidi odjeljak 5.8).

Funkcije s ncodredenim argumentima se redovito koriste u deklaracijama funkcija iz biblioteka pisanih u jeziku C, 1I kojem nije bilo niti podrazumijevanih argumenata niti preopterecenja funkcija.

Za dohvacanje nespecificiranih argurnenata prograrneru su na raspolaganju rnakro naredbe iz standardne biblioteke cstdarg. llustrirajmo njihovu primjenu nasom inacicorn funkcije pr intf (J :

#include <iostream> #include <cstdarg> using namespace std;

int spikaNaSpiku(char *format, ... ) {

va list vI; II podatak tipa va_list

va=start (v l , format); I I .i n i c.i j a Li z i r a vl

i nt, i - 0;

while (* (format + i)) I

if (* (format + i) ~~ 'j;' i { switch ('{format + (++i)++I) case I d I :

case 'i':

int br = va_arg(vl, int);

186

5. Funkcije

cout « br; break;

case IS':

char *zl1_niz ~ va_arg(vi, char'); CQut « zn_niz;

br e ak ,

case "c":

char zn va_arg(vl, char);

CQut « Zl1;

break;

de fault:

carr « endl

« "Nede f i n i rani tip pod a taka I " « endl;

va_end (vl) ;

return 0;

else

cout « (* (format + i++);

j;

va_end(vl) ; return 1;

Prvo je deklariran objekt \/1 tipa va _lis l koj i je definiran unutar datoteke zaglavlja cs t da r q. Taj objekt na neki nacin sadrzi sve argurnente funkcije, te cerno pomocu njega pristupati pojedinom argumentu (sama struktura objekta korisniku nije vazna - on je "crna kutija"! ). Prije nego sto zapocnemo citati vrijednosti pojedinih argumenata, objekt v l je potrebno inicijalizirati pozivom makro funkcije va_start I) (takoder iz cstdarg). Slijedi petlja koja prolazi pocerni znakovni niz, ispituje ga i ispisuje, znak po znak. Nailaskorn na znak %, ispituje se slijedi li ga neki od znakova: d, i, s iii c (popis se rnoze prosiriti) - ako nije niti jedan od navcdenih znakova, ispisuje poruku 0 pogreski i prekida izvoclenje funkcije, vracajuci pozivnom kodu nulu. U protivnom, pornocu makro funkcije va _ arg (I uzirna sljedeci neimenovani argument, Funkcij i va _ <l[g (I JC potrebno prenijeti tip parametra koj i se zeli procitati, sto u nasern primjeru nijc problem buduci da ga znamo na osnovu pcdataka 0 formatu iz pocetnog niza (aka bi lip i u ovom trenutku bio nepoznat, najjednostavnije bi ga bilo ucitati kao pokazivac na n iz i potom provesti odgovarajuca ispitivanja). Makro naredba ce vratiti vrijednost argumcnta te ce se pomaknuti na sljedeci argument iz lisle, Prilikom sljedeceg poziva [unkcije v e arg (I

debit cerno vrijednost sljedeceg argurnenta. -

Za one koj i ipak nece moci zaspati dok ne spcznaju 5(0 je va _1 i s t - to je pokazi vat na Slog, Makro funkcija va_start (I inicijalizira taj pokazivac tako da pokazuje iza zadnjcg prenesenog paramerra, dok ve _ arg I) preko njcga prisiupa pojedinom argumenru,

5,5, Pokazivaci i reference kao povraine vrijednosii

187

Prije povratka iz funkcije nuzno je pozvati makro va,_ end (I Nairne, lunkcija va,arg (i moze modificirati Slog (ua koji je, izrnedu ostalog, pohranjena i povratna adresa na kojoj se izvodenje mora nastaviti nakon povratka iz funkcije), take da povraiak u pozivajuci kod bude onemogucen; va_end II uklanja te izrnjene na siogu i osigurava pravilan povratak iz funkcije Konacno, prilikorn uspjesnog povratka, lunkcija spi kaNaSpi k u () vraca pOZiVIlOIll k6du I, povratna vrijednost (0 iii I) iskoristena je ovdje kao pokazarelj uspjesnosri izvodenja funkcije

Uocirno viticaste zagrade uz pojedine case grane kojima su naredbe grupirane li zasebne blokove. One su neophodne, jer u pojedinim granarna dcklarirarno lokalne (privremene) varijable razlicitih tipova, ovisnih 0 smjeru grananja, odnosno 0 tipu podatka koji se ocekuje kao sljedeei argument.

Evo kako bi mogao izgledan poziv nase velernozne inacice standardne funkcije p ri n t Lt ) :

sp i k",;J"Spi ku I "ua Imat Lnac i %d %s%c", 101, "da Lma t.Lne", 'r');

Zudtuak. Prosiritefunkciju spikaNaSpiku Ii lake da prihvaca i sljedece formate: %f za brojeve s pomicnim zarezom, %x za ispis brojeva u heksadekadskom formatu, %0 za ispis brojeva II oktalnom formatu. Takoder osigurajte pravilan ispis posebnih znakova. \ L, In, \ \, \ ", \'

5.5. Pokazivaci i reference kao povratne vrijednosti

Treba li funkcija pozivnorn kodu vratiti neki brojcani rezultat, najjednostavnije je definirati funkciju tako da je onog tipa koji odgovara tipu rezultata, a u return naredbi navesti zeljenu povratnu vrijednost. Na primjer, trebamo li funkciju koja ce na osnovi poznatih odsjecaka na apscisi i ordinati kao rezultat vracati koeficijent smjera pravca (tj. tangens kula), definirat cerno lunkciju

double nagib (float x , Hoat y) return y I x;

Prilikom poziva eve funkcije naredborn

float k ~ nagib(3,2, -12,1);

na stogu ce se stvoriti lokalne varijable x j 'j kojirna ce biti pridruzene vrijednosti stvarnih argumenaia (3.2, odnosno -12.1), Vrijednosti tih lokalnih varijabli ce se potorn podijeliii, a rezultat dijeljenja ce biu pohranjen na stog. Prilikom povrarka iz funkcije, pozivajuci k6d ce taj rezultat skinuti sa stoga i pridruziti ga varijabli k Ovo pridruzivanje podrazumijeva da se sadrzaj pohranjen na stogu preslikava na mjesto gdJe se nalazi sadrzaj vanjable k, uz eventualne pretvorbe tipa (u gornjem primjeru double II float),

188

5. Funkcije

char 'dolijepi(const char "prvi. cnar spojeni[80];

char "indeks ~ spojeni; while ('prvi)

'(indeks++) '(prvitt);

while (*drugi)

"(indeks++) *(drugiH);

*indeks = '\0',

return spoj eru ,

const char *drugi) (

II lokalno polje znakova

Nakon povratka u pozivajuci k6d svi podaci ria siogu koji SLI privrerneno bili stvoreni prilikom izvrsavanja funkcije se gube, ali olli nam ionako nisu vise bitni buducj da SOlO uspjcli dohvatiti i pokupiti rezultar funkcije. Naravno da ako funkciju nagib() pozoverno tako da njen rezultat ne pridruzirno odmah nekoj varijabli, rezulrat tunkcije ce ostati bespovratno izgubljen, iako ga je ona izracunala i pchranila na stog:

nagib(2., 5.);

Poteskoce, a cesto i pogreske nastaju kada se iz funkcije zeli prenijeti lckalni objekr generiran pri pozivu funkcije. Uzmimo da smo pozeljeli funkcij II koja ce sadrzaj jednog znakovnog niza "nalijepiti" na drugi znakovni niz i novonastali niz vratiti kao rezultat. Nazovimo tu funkciju dolijepi (); argumenti te funkcije bit ce pokazivaci na nizove koje telimo spojiti, a povratua vrijednost ce biti refcrenca na novostvoreni niz. Deklaracija funkcije bi prerna tome izglcdala kao:

char &dolijepi(const char", const char"l;

Pokusajmo definirati funkciju na sljedeci nacin:

Ii pogre§ka' pokazivac II na lokaini objekt

Unutar funkcije se generira lokalno polje spojeni u koje se stapaju znakovni nizovi na koje pokazuju argurnenti funkcije. Pri izlasku iz funkcije prenosi se pokazivac na to po lje pozi vnom k6du. Medutirn, kako se lokal n 0 polj e 5 poj en i izlaskom iz funkcije gubi iz vidokruga, rezultat Je nepredvidiv'.

Zelimo Ii ipak da gornja funkcija kao rezuliat vrati znakovni niz, rnorar cemo kao argument proslijediti i pokazivac na rnernorijsko podrucje u koje zelimo srnjesuu rezultat:

char Odolijepi(cons[ char 'prvi, coos[ char 'drugi, cha r • spo j eni) ;

t Prevoditelji ce na ovakav pokusaj vracanja vrijednostl prijaviti upozorenje.

'lu: sc ~1!.0 usposia v It I konur i U 1 t~il !/..I jj~dlL [..ltdj.,.U v rivg r i J Ld IJ PIJL.j V i::.LJ uceu E h()J u tc znakovuog niza OJ samoj funkciji ne saruo pri izlasku iz runkcije (preko povrauie vrijednosu, vet i pri ulasku u funkciju preko dodarnog argumenta

Zudluak: t\'upl.5Jfi::. kilt! Zu u'vuku dekluruaru. ji{nA..r.:lji.~ JU.i. J J epl l J ()OJ"l.ltd~ puiriJu ~iP,) vracanje vrijednosti.

eridL "I,)ljilj ~~l ~(.: .. :.J!i':;; l :';[i.:::;1 ~·~L VI ( :':"':uH":'" L Cii.::;1 "o L uojJ. I

chi" '$poj"'!Li~O! 1

~lNJ';Jj" ~ new char [s trlen (prvi i r s t.r Le u tJLllg~ i s- i j " char 'iHdeks ~ spoj en i ,

while ('prvil

* lindeks++1 • (prvi++);

wh i Le I'drugil * (Lnde ks ++ I 'indeks ; '\0'; return 'spoj eni ,

* Idrugi++I;

IJdlll uaujern podrazuruijevane vrjjednosu treceg Mgu merua sk rau Ii on ill ;;1 mu ke pri pozivu funkcije; ovako ce biti dovoljno poziv napisari kao:

char 'bratsLvoljedinstvo - dclijepi{pocBt8k, _Cdj,;

Da I1i~IIJO uaveli podrazumijevanu vrijednost u eceg Mgumenl~, lUur~li bi~1U0 g~ navesu II pOLlVU. U 10m slucaju prevoditelj bi javio pogresku da varijahla ~"J educ nije deklarirana (pcdsjetimo se: kild narcdbe se uvijek pocinje izvoditi desno od znaka pridruzivanja) Preostaj e narn deklaracij II prebaciti U zasebnu nared bu ispred po zi va funkcije, s time da (zbog upozorenja prevcditelja) morarno ujedno i inicijalizirari njenu vrijednost:

cha~ ~b!a[s~volJeulIis~v9 ~

bratstvoljadinstvD = dolijepilpocetak, k(aj, br~LStvolJedin~Lvui;

Svakako valja voditi racuna da SIlW u funkcij i za rezultantui n iz a locirali pro, WI kUjI se ne smije zaboraviti naknadno osloboditi

5.6, Zivot jednog objekta

I objekti nisu besmrtni - PUSlU] i IIIJeSlU 11>; kOJ uua ,;~ ,;!.vardJ u i sciJc ini sc Ju<1jdji.lJc memorija, [C ITlJ esto gdj e se unisiava j u, odnosno gd je se rnemorij a os lobada. lma v ise vrsta objekata S obzirorn na njihovo irajanje Takoder, neki objekti mogu u odrcdenom irenurku POSlOJ31i, ali ne moraju biti dostupni. Z'bog toga, C++ jezik specificira nekoliko smjestajnih razreda (engl. storage classes] kOJI odreduju nacin na koji ce se objekt stvoriti i unistiti, te kada ce objekt biti dosiupan Smjestajni razred objekta se specificira prilikorn njegove deklaracije

190

5 FllnkClje

5.6.1. l.okalni objekti

U poglavlju 0 argurnenuma tunkcija spoznati ,1ll0 da Sc pr ilikoru poziva funkcije generiraju privrerneni objekti koj ima se pridruzuju vrijednosti stvarn iil argurnenata Buduci da funkcija barata s preslikama podaiaka, za podatke prenesene po vrijednosj] prornjene unutar funkcije nece imati nikakvog odraza na izvornike u kodu koji Je funkciju pozvao Ovo je velika preduost kada se zele pcdaci sacuvati od nekonlroliranih izmjena u pozivajucirn funkcijama.

Takoder, pojedina funkcija u svojem radu rnoze za realizaciju zeljcnog algoritma iziskivati niz pomocnih objekata. Ti objekti nemaju smisla izvan same runkcije tc je njihov zivor vezan za izvodenje funkcije. Takvi objekti se ZOVlI lokalni objekti, jer vrijede lokalno za samu funkciju. Objekt nije vezan striktno za funkciju, vee za blok u kojernu je deklariran (vidi poglavlje 3. l ), no funkcija u biti nije nista drugo nego blok. Na primjer:

void s ort i.r a j (i nt *polj E!, i nt dulj ina) lnt i , j;

for (i = duljina - 1; i > 0; i--) :for (j=; 0; j <: i; j++).

if {polje[j] >: po l je [j+ ,1]) int priv -·poljeJj IJ; polje(j'~ 11 ~ polj~GI; polje[j] = priv;

Varijable i. i j su brojaci k oji nemaju smisla izvan funkcije. Zbog toga se oni deklariraju kao lokalni, Memorija se dodjeljuje na ulasku u funkciju i oslobada na izlasku iz nJe Varijabla p r i v je takoder lokalna, slime da je ona deklarirana u ugnijezdenorn bloku. Ona zivi samo unutar log bloka, stvara se na pocetku bloka i unistava na kraju.

Za lokalne varjjable se kaze da imaju automatski smjestajni razred (eng!. automatic storage class) koj i se ispred deklaracije rnoze eksplicitno navesti kljucriom rijeci auto:

auto int priv;

Automatski srnjesrajni razred se podrazumijeva ako se nista drugo ITt: specificira, pa S~ zbog toga kljucna rUec auco koristi vrio rijerko.

Lokalne varijable mogu imati i registarski smjestajni razred (engl. register storage classv koja se naznacava tako da se ispred deklaracije navede kljucna rijee register:

register int priv;

rime se prevoditelju daje na znanje da se neka varijabla koristi vrlo inrenzivno, te da Se radi perforrnansi programa preporuca srnjestanje varijable u registar procesora urnjesto u glavnu mernoriju. Prevcditelj to moze poslusati, ali i ne mora. Jedna od situacija II kojirna ce prevoditelj gOI()vO sigurno zanernariti registarski srnjestajnl razred jest ako se

uLlli,' mjl~~d vanjable. pUUdlak II l"'~I,;lJU IIcJ"" dUJ":;U. HUljl P'<::VllJIlc:iJI CC Lbo. eksphcimog navodenja autoruatski varijable smjestati u registre ako to rnogu UCIIlIl!.

5.6.2. Globalni objekti

r'ouekad JC ipa], pozetjno tid lJrUlliJ""C II" var ijablarua IlILaJu UcilaL" I izvau llJelct funkcijc. Prc[pOSl<WI1l10 da U uekom programu unamo IIi cjelobrojne varijable lici.'" "'J"'~'K i~odl"ci. koje sadrze pcdatke 0 tekucern daturnu. Funkcija novirrj e sec zaduzena je La prornjenu varijable ",j ~.'~L, ali i La prornjenu varijable qod i na na kraju kalendarske goJille:

inc Iji.JVJ.('1je;j.~';_: \ i ui. Hi] e.':lec:f i nr YVUllLd) i if (++mjesec > 121 {

mjesec ~'l;

godina++; II bBZ efekta

.r e t.u rn . mj e sec r

in t main () ...

in t dan';' 30;, ,< •• " iht'.in,jese,c~ '1;:~ =. 'int godinii ~.1997;

mjesec ~. noviMjesec(mjesec, godiria);

,return 1 ,. ,,' .

UCllO je da ce ;;e prornjena mjescca, preko povratne vrijednosti, odraziti u POZiV[Jlll!I kodu, Medutirn, prornjena godine ce se po povratku u glavnu funkciju izgubiu.

Poneki citatelj ce se dosjetiti, te umjesto po vrijednosti darumske varijable prenosru kao reference iii kao pokazivace. Funkcija ce tada baratati (posredno) s izvomicima, pa povratna vrijednost funkcije nov.it+j e s ec nije niti potrebna. Na zalost, i ovakvo rjesenje tek djelornicno uklanja problem. Ilustrirajrno to sljedecom situaciju: recimo da se funkcija nov i hj es ec ne poziva izravno iz glavne funkcije, vee se prethodno poziva tunkcija nov i Dan koja poveeava dan U iekucem datumu. OtiLU ce funkcija nov i Dan iek povrerneno pozivati funkciju nov i.nj e sec. Cak i kada bismo dan, ",jesec i godi"" prerios i!i preko pokazi vaca na nj ih, trebal i b ismo pokazi val" na 9 od i, "" prenij eli funkcij i riov i Dan samo zato da bi ona taj pokazivac mogla dalje prenijeri funkciji noviMjesBc;

iot noviMjesBc[il!t ~mjesec. int "godina);

i nt no,v;iD,an I int 'dan., int <mj esec, int<godinal;

U ceklaraciji funkcije ce se pojaviti dodatni argumenti koji samoj funkciji nisu potreb.n, sto ce u svakorn slucaju ciuiti k6d glomaznijirn i necitljivijim, Zamislimo sarno kako bi to sve izgledalo za vise razina poziva funkcija. Suvisni argurnenti bi se gomilali, i vee nakon nckoliko razina neke funkcije bi imale desetak arguruenata,

192

5. Funkcije

Jednostavno rjesenje ovakvog problema je da se objekii koji irebaju biti dohvacan] iz nekol.iko razticitih funkcija deklariraju kao globalni. Da bi neka varijabla postala globalna, treba ju deklarirati izvan tijcla funkcije, Vrlo Je vjerojatno cia ce datumske varijable trebati i drugim funkcijarna, pa cerno ill definirati ispred funkcije main {).

int noviMjesec();

II deklaracija funkcije

int dan I mjesec, godina;

II globalne vari)able

int main () ( dan = 30;

mjesec 1;

godina = 1997;

mjesec = novit1jesec(); 'return 1;

121 mj,\sec .; L godina++;

return mjesec;.

Varijable clan, mj e sec i godina deklarirane su kao globalne te su sada vidljive funkcijarna main () i noviMj e s e c (J koje slijede iza deklaracije objekata, tako cia ih ne rnoramo posebno prenositi kao argumente u pozivu funkcija. Stovise, u gornjem primjeru nerna potrebe da se vrijednost mjeseca vraca kao rezultat.

Da srno kojim slucajern u gornjem kcdu, deklaracije varijabli datumskih varijabli stavili iza funkcije main I), ona ih ne bi mogla dohvacati, pa bi prevoditelj javio pogresku da te varijable nisu deklarirane u funkciji main (I.

Globalna deklaracija ujedno je i definicija. Nairne, svi globalni objekti se umecu u izvedbeni kod, Oni zive od pocetka izvodenja programs do njegovog kraja Prilikom deklaracije objekta moguce je cdrnah provesti i inicijalizaciju po pravilima koja smo do sada nautili,

To znaci da ce pri izvodenju sljedeceg programa:

5 6. _livol jeclnog obiekia

193

#.include <iostream> using namespace sto;

int globalna;

II neinicijalizirana globalna varijabla

int main !I [

int lokalna; II neinicijalizirana lokalna varijabla

COU( « glooalna « endl;

cout <:< lokalna « €ndl:

prva naredba za ispis na zaslonu ispisati nulu, a druga neki neodredeni broj koji ce varirati ovisno 0. uvjetima pod koj ima je program prevedcn i pokrenut

Zadatuk: Rozmislite I provjerite sto ce bitt ispisano u gornjem primjeru, aka .Ie globa Lns i Ioka Ina dekloriraju kao pokaiivaci no znakovne nizove,

Ocito se deklariranjern varijabli kao globalnih pojednostavnjuje pristup njim: •. Time se one ujedno izlazu opasnosti cd nekontroliranih prornjena unutar razlicilih funkcija.

Podaci koji trebaju biri dostupni svim funkcijama, ali se ne srniju mijenjmi (npr univerzalne matematicke iii fizicke konstarue) deklariraju se kao globalne konstanre, doda va nj ern modifikatora con s t ispred dek I aracij e tipa.

Globalni objekti mogu irnati dva smjestajna razreda: vanjski iii staticki. Ako se kild proieze preko nekoliko modula, globalni objekti deklarirani na gore opisani nacin bit ce vidljivi u svim modulima. Vise rnodula mote korisriri isti objekt, s time da tada objeki srnije biti definiran iskljucivo u jednom mcdulu. U preosralim mcdulima objekt je potrebno samo deklarirati, ali ne i definirati, Deklaracija se provcdi tako da se ispred nazi va objekta stavi kljucna rijec ext.e r n:

extern int varijablo_u_drugom_i'nodulu;

Objekti deklarirani s extern imaju vanjsko povezivanje (engl. exsernal finkoge), SIO awei da ce biti dostupni drugim modulima prilikom povezivanja. Ako se nista ne navede, podrazurnijevano je da se radi 0 eksternoj deklaraciji i definiciji.

Poneki objekti mogu biti od koristi sarno unutar jednog modula. Takvi objekti se mogll uciniti statick ima, cime se otvara mogucnost da i drugi moduli deklariraju objektc 5 istirn nazivorn. Ti objekti su zapravo lokalni za modul i irnaju unutarnje povezivanje (engl. internal linkage) Objekr se rnoze ucinit] sratickim tako da se ispred deklaracije urnetne klj ucna rij ee 5 ta c i c:

static int SamoMoj - 8;

194

5. Funkcije

Ovo je stari nacin deklaracije, naslijcden iz jezika C Standard jezika C++ za specificiranje unutarnjeg povezivanja preporucuje koristenje bezimenih irnenika, 0 cemu ce biti rijeci u poglav lj L! I 3.

Konstantni objeku, ako se drukcije ne navede, auiomatski imaju siaticki smjestajni razred. Ako takve objekte zelirno koristiti u drugim mcdulima, potrebno Je objekr deklarirati eksternim.

Ako se \I funkciji deklarira lokalni objekt istog imena kao i globalni objeki, globalni objekt ce biti skriven. Na primjer:

int a - 10; I! globalna varijabla
int. main (I {
float a 50; i/ lokalna varijabla
cout « a « endli // ispisuje SO U gomjelll prirnjeru globalna varijabla a je unutar funkcije main i i skrivena: navodenje samo identifikatora a rezultira pristuporn lckalnom objektu, No rnoguce je pristupui globalnom objektu tako da se ispred naziva objekta navede operator: : (dvije dvotocke) - operator za odredivanje podrucja (engl. scope resolution opera/or). Nairne, svi globalni objekti pripadaju globalnorn podrucju, a ovirn opcratororn se eksplicitno odreduje pristup tom podrucju

int a ~ 10;

II globalna varijabla

int main i) {

float a 50;

cout « a « endl; cout « ::~« endl;

/1 lokalna varijabla

II lokalna varijabla: 50 II Olob~l~~ varijabl~: 10

5.6.3. Staticki objekti u funkcijama

Lokalne varijable unutar funkcije se inicijaliziraju svaki puta prilikom poziva funkcije. Medutim, postoje situacije kada jc pozeljno da se vrijednost neke varijable inicijalizira samo pri prvom pozivu funkcije, a izmedu poziva Ie funkcije da se vrijednost cuva. Istina, takva se varijabla moze deklarirari kao globalna, ali ce tada biti dohvatljiva i iz drugih funkcija i izlozena opasnosti od nekontrolirane promjene. Rjesenje za ovakve slucajeve pruzaju staticki objekii (engl. sialic objects). Ove objekte (reba razlikovaf od starickih globalnih objekata iz prethodnog odsjecka - ovdje se radi 0 statickim objektima unutar funkcija,

Dodavanjcm kljucne rijeci stacie ispred oznake tipa, objekt postaje staticki - all se inicijalizira sarno jednom, prilikom prevodenja, te se takav clan pohranjuje u datoteku

zajedno s izvedbenirn k6dom. Ilustrirajmo to sljedccim primjerorn: .

5 6 llvot jednog objekta

195

~lnclude <ioscream> 'include <cstdlib> hncl ucle <cr ime > using namespace sLd;

vo id VoliMeNeVoli () I static bool VoliMe VoliMe - 'VoliMe; if (VolHlel

false;

/ I staticki objekt

cout ~< "vo Ii me! 'I « endl;
else
cout « "He voli~1I « end l , int main() { srand{tirne(O));

int i - rand() • 10 + 1; wh i Le (i--)

VoliMeNeVoli i) ; return 0;

II ifiicijalizira se klica II slucajni broj oct 1 do 10

U gl a v noj fu n kc ij i pomocu standardne Iunkc ij era nd (j (dekla rirane u c 5 tdl i b) generi ra se slucajni broj izrnedu I i 10, te se toliko pula poziva funkcija VoliMeNeVoli I). Pocetna vrij ed nost slucaj no generiranog bOJ a odredu je se pozivorn fu nkcij e s rand I i (vidi prirnjer iz poglavlja 3.7 La objasnjenje). U funkciji VoliNeNeVoli I) deklarirana je staticka varijabla Volil1e tip a bool. Kod prevodenja ona se inicijaliz.ira na vrijednost false ineistina), ali se prillkorn svakog poziva funkcije VoliMeNeVoli (I njena vrijednost mijenja - to ce prouzrociti naizmjenicni ispis obiju poruka i na zaslonu ce se pojaviti slijed oblika

Voli me' Ne vo l i:

Voli mel Ne velil

Da je izostavljena deklaracija s t ac i c, varijabla Voli~j" bi bila pri svakorn ulasku Ll funkciju inicijal izirana na istu vrijednosr (false) te bi svaki poziv funkcijc dao ispis iste peruke:

vo t i me~
voli me!
voli me!
voli me' Sraticki objekti u funkcijama five za vrijeme cijelcg izvcdenja programa, ali su dostupni samo unutar funkcije u kojoj su deklarirani. Te staticke objekte mozemo shvatiti kao cia su deklariran i izvan funkcij e koristeci smj estajni razred s t a ti c, s time da i m se moze pristupiti same iz funkcije u kojoj su deklarirani. Zato se i koristi isla klj ucna rijec st." tic: srnjestaj ovakvih objekata i globalnih statickih objekata se provodi na isu nacin

196

5. Funkcije

(u podatkovni segment programa), pa ce i siaticki objekii irnati poccmu vrijednost 0 ako se ne inicijal iziraju drukcije

5.7. Umetnute funkcije

Cesto se koriste Iunkcije koje irnaju vrlo kratko tijelo. Sam poziv, tijekom kojeg se stvaraju i iriicijaliziraju lokalne varijable, za takve kratke funkcije moze trajati znatno dulje od njenog izvrsavanja, Vet sino imaii primjer funkcijc kvadr a c I) koja se sasto] i samo od jedne naredbe:

double kvadratlfloat xl I ret-urn x .;, Xi

Izvodenje prevedenog koda bile bi brze kada bi svaki poziv funkcije kvad ra I: (i U prograrnu bio jednostavno zamijenjen narcdborn La rnedusobno mnozenje dva ista broja.

Dodavanjern kljucne rijeci i n l i ne ispred definicije funkcije daje se naputak prevoditelju da pokusa svaki poziv funkcije nadomjestiti sarnim kodorn funkcije. Takve se funkcije nazivaju umetnute funkcije (engl. il1/inejimclion) Evo primjera:

inline double kvadratlfloat xl I return x·* x;

Valja naglasiri da je to same naputak prevoditelju, a roe i uaredba. Prevoditelj ce shodno svojim mogucnosrima i procjeni taj napuiak provesti u djelo iii ce ga ignorirau. Svaki bolj i prevoditelj ce generirati umetnuti k6d za ovakvu funkciju kvadrat (I. Medutim, ako Je tij elo funkci]e slozenij e, posebice ako sadrzi petlj 1I, us pj eh uklapanj a funkcij e ce znacajno varirati od prevoditelja do prevoditelja.

Definicija umetnute funkcije mora prethcditi prvorn pozivu funkcije - u ovom slucaju prevoditetju nije dovoljna deklaracija funkcije, buduci da pri nailasku na prvi poziv umetnute funkcije vee mora znaf time ce poziv te funkcije nadomjestiti (ako ce to uopce uraditi). Stoga se definicija urnetnute funkcije obicno navodi zajedno s deklaracij arna ostal i h furtkc ija i razreda, naj cesce u zasebnoj datotec i (0 tome ce bi li govora u zasebnom 15_ poglavlju posvecenorn organizaciji koda).

Koristenjern urnetnutih funkcija elirninira se uepotrebni utrosak vrernena koji nasta j e pri 1 ik om pozi va funkcije, kada se parametri funkcij e i povratna adresa urnecu na stog, a potorn se unutar funkcije preslikavaju sa stoga Medutim, kako se svaki poziv umetnute funkcije nadornjesta se tijelorn funkcije, maze se znatno povecati duljina izvedbencg koda. Pretjerana uporaba urnetnutih funkcija moze znacajno produlj iti posrupak prevcdenja, posebice ako se programski kod rasprostire hOL nekoliko odvojenih datoteka, Promijeni Ii se tije]o umetnute funkcije, prevod ileij 1110ra proc: cjelokupni k6d da bi preslikao tu prornjenu na sva mjesta pOZiVH. Naprotiv, pri promjeni tijela funkcije koja nije umetnuta, postupak prevodenja treba ponoviti samo u datoreci II kojoj se nalazi definicija doticne funkcije.

5.8. Precpterecerue funkcija

197

Zbog navedenih cinjenica, ocigtedno treba biti vrlo obziran s koristenjern i o I ino funkcija i njihovu uporabu ograniciti na najjednostavnije funkcije. Najcesce se kao umetnute definiraju funkcije koje vracaju vriiednost iii referencu Ill! podarkovni clan unutar nekog objekra - 0 lome ce biti vise rijeci u poglavlju 0 razredirna.

Jedan od nedostataka umetnutih lunkcija je i nernogucnost pracenja toka programom 2<1 sirnbolicko otkrivanje pogresaka (engl. debugger) u izvedbcnom kcdu. Buduci da se pozivi funkcije nadomjestaju njenim tijelorn, funkcija II izvedbenorn kodu ne posioji, pa se (najcesce) ne rnoze ni analizirati programom za sirnbolicko otkrivanje pogresaka, Zbog toga je, prilikorn pisanja programa, najbolje za prvu ruku sve funkci_je napraviti kao obicne. Tek nakon sto se k6d pokaze ispravnirn, neke se funkcije redefiniraju kao urnetnute, te se ispita pr ipadajuci dobitak u izvedbenom prograrnu.

S.B, Preopterecenje funkcija

Ako tip stvarnog argumenta funkcije u pozivu 10" odgovara npu navedenoru II deklaracij i, tada se provodi pretvorba tipa i stvarni argument se svodi na lip formalnog argumenta. Prirnjerice, u donjem kodu funkcija kvadrat () deklarirana je za argument tipa double, a poziva se sa cjelobrojnim argumentorn:

float kvadrat (float x ) I return X 4' Xi

int main{) (

i nt i - 16;

i = kvadr a t (i); return' 0;

Prilikom pridruzivanja stvarnog argurnenta (cjelobrojne konstarue j 6) forrnalnom argumentu koji je tipa float, provodi se pretvorba tipa argurnenta. Unutar funkcije barata se argumentom tipa float i kao rezultat se ymca podatak tog ripa. Medutirn, U pozivnorn kodu povratna se vrijednost opel svodi na Lnt Kao sto vidirno, pri pozivu funkcije provedene su dvije suvisne pretvorbe koje U sustini ne utjecu na tocnost rezultata, ali nepotrebno produljuju poziv i izvodenje funkcije jer se mnozenje realnih brojeva odvija dulje nego mncfenje cijelih brojeva.

Ocito bi bilo daleko prik ladnije definirati funkciju za kvadriranje cijclih brojeva koja bi baratala sa cijelim brojern i vracala cjelobrojni rezultat. Ta funkcija bi opcenito rnogla irnati bilo koje nne, ali Je VI razumijevanja k6da najbolje kada bi se i ona zvala kvad r a t {I:

int kvadl4t{int xl return x * Xi

198

5 FunkciJe

No sto uciniti ako su nam 1I programu potrcbna oba oblika Iunkcije %vadraL {I, tj. kada zelimo kvadrirati i cijelc i decimalne brojevc? float inacica Iunkcije ce podrzavau oba tipa podatka, ali ce, kao sto smo vee primijetili, iziskivati nepotrebne pretvorbe tipa.

Programski jezik C++ ornogucava preopterecenje funkcija (eng]. function

overloading) - koristenje istog imena za razlicite inacice funkcije. Pritom se tlHlkcije ~

moraju medusobno razlikovari po potpisu, tj, po tipu argurnenata u deklaracij i funkcije. Prevoditelj ce prerna tipu argurnenara sam prepoznati koju inacicu funkcije mora za pojedini poziv upotrijebiti. Take ce u k6du:

float kvadrat(float); int kvadrat(int);

/ I dek.Le rac.i je preopte recentn !! funkcij<'

i n t main I) I float)( 0,5; int n = 4i

cout « kvadrat(xl « endl; cout « kvadrat(ol « endl; return 0;

II float Kvadrat(float) !I int kvadrat(int)

prcvoditelj prvorn pozivu funkcije kvad r a t I) s argumentom tipa float pridruziti float inacicu funkcije, dok ce drugom pOZiVLi s argument tipa int pridruziti int inacicu funkcije

Glcdano sa SIano vista prevoditelja, jedino sto je zajednicko gornj im funkcijama jest ime. Preopterecenj e irnena same olaksava programeru da srn is leno poistovjeti funkcije koje obavljaju slicne radnje na razlicitim tipovirna podataka, Ovo je narocito prakticno kod funkcija koje koriste opceprihvacena imena, poput s in, cos, sqrt. Pritom valja uociti da se odluka 0 pridruzivanju pojcdine funkcije donosi tijekom prevcdenja k6da; na sam izvedbeni k6d to nema nikakvog odraza te bi izvedbeni k6d (teoretski) trebao biti potpuno identican kao i da su pojedine preopterecene funkcije imale medusobno potpuno razlicita imena, Stoga je u biti tocnije govoriti 0 preopterecenju imena funkcija.

Medutirn, preopterecenje imena postavlja dodatne probleme za prevoditelja: samo ime funkcije vise nije dovoljno da bi se jednoznacno cdredila funkcija koja ce se koristiti za odredeni poziv. SIO ako u gornju glavnu funkciju ubacimo poziv funkcije kvad r a t I) s nekirn drugirn tipom argumenta (primjerice double) za koji nije definirana funkcija?

i n t; main {I (

double duplix - 3456.54321e49; cout « kvadrat{dupliX) « endl; return 0;

Ii pogreSka!

float kvadrat(floa~l; int kvadrat(int);

/ / deklaracije preopt.erecen Ln // f unkc i j a

5.8. PreopterecenJe tunkc'j~

199

Buduci da nije deklarirana Iunkcija kva0td.l (douo Le l ; prevoditelj je prisiljen zadovoljiti se postojecim funkcijama, (.I. 1.110ra provesti odgovarajucu pretvorbu tipa stvarnog argumenta. Na raspolaganju su dvije funkcije kvadra t (1: za argument tipa Eloa I za argument tipa Inc. Pretvorba double podatka u bilo koji od tih tipova u opcenitorn slucaju podrazumijeva "kresanje" podatka tako da su pretvorbe double u float, te daubl e II in t ravnopravne - ovaka II pozi v ce izazvari dvoj bu kod prevod i te Ija i 011 ce prija viti pogresku, S licna s ituac ija je ills 1.1 edecem kodu:

long kvadrat(longl; float kvadrat(floatj;

int main () ( kvadr a t Cd Z) ;

kvadrat(3.4); return 0;

/ / pogr€Skd; l}l6doUUliCc: j e 1.1.

1/ kvadrat(long) ili kvadrat(f10dti II isti problem

Brojcanirn konstantarna nije eksplicitno odreden tip pa ih prevoduelj (iruphcuno) interpretira kso podatke tipa inr , odnosno double, koje prilikorn poziva Iunkcije treba pretvoriti u long iii flail t Pretvorbe u ta dva tipa su fa vnopravne tako d a prevod itelj ostaje u nedournici i prijavljuje pogreske za oba poziva.

Naprotiv, neke pretvorbe tipova (npr. f Loa t U double) su trivijalne, tako da ce sljedcci ked biti preveden bez prijave 0 pogreski:

long apasalutno(long); double apasalutno(double);

// samc deklaracija

int enain() (

float x - -23.76;

cout « apasalutno(x) « endl; return 0;

/1 kvadrat(double)

Ocito je da moraju postojari iocno odredena pravila po kojima prevodiielj prepozuajc koju preopterecenu funkciju mora prilikorn pojedinog poziva pridruziti, Pretrazivanje se provodi sljedecim redoslijedom [StroustrupOOJ:

I Trazi se funkcija za koju postoji egzaktno slaganje argurnenata. To podrazurnijeva slaganje kod kojeg nije potrcbna nikakva pretvorba tipa iii su potrebne samo trivijalne pretvorbe (pretvorba imena polja u pokazivac, imena funkcije 1I pokazivac na funkciju, pretvorbe tipa u referencu i obrnuto, pretvorba tipa u cons t ).

2. Trazi sc funkcija kod koje treba provesti samo cjelobrojnu promociju (che r II inc short II i n t , odnosno 1I njihove unsigned ekvivalente, pobrojenja II i nt ) i pretvorbu float U double.

3 Trazi se funkcija za koju [reba provesti standardne pretvorbe (na primjer i ru \I double, i ut 1I unsigned inc, pokazivae na izvedeni razred u pokazivac na osnovni razred),

200

5. FunkClje

4. Trazi se funkcija La k oju su neophodue korisnicki definirane pretvorbe lO njirna ce

biti rijeci kasnije, u poglavlju 0 razredirna)

5 Traz] se funkcija s necdredenim argurnentima ( ) 1I deklaraciji

Ako se u nekoj od tocaka naide nil dvije iii vise lunkcija koje zadovoljavaj u doricru uvjet, poziv se progiasava neodredenim i prevoditelj prijavljuje pogresku Valja unau nil urnu da pri trazcnju odgovarajuce funkcije redoslijed deklaracija preopierecenih funkcija nerna nikakav utjecaj na odabir

Zadatuk. Odredite zu gornje pritnjere s pugrdko/n /I kojtm ]e. iockama naveJenug redoslijeda nastupila neodredenost.

Ako funkcija ima vise argurneuata, tada se gornj i pcstupak nalazenja odgovarajuce funkcije provcdi za svaki od argurnenata Ako jedna od tunkcija osigurava bolje slaganje za jedan od argurnenata, a barem jednako dobro slaganje za ostale argumente, ona ce biti odabrana

Buduci da bilo koj i rip l' (na primjer i nt.) i reference na raj tip T & (na prirnjer int s) prihvacaju jednake inicijalizirajuce vrijednosti, funkcije ciji se argumenti raztikuju sarno po tome radi Ii se 0. vrijednosti iii referenci na nju, ne mogu se preopterecivati, Zato ce prevoditelj prilikom prevcdenja sljedeceg kcda prijaviti pogresku

void f(int iJ { II

void flint s.re t L) { I I ...

II pogreska: nedovoijna razlika

lsto vrijedi i za razliku izrnedu argurnenara tipa T, const T i volatile T, koje prevouuel] nece razluciti. Nasuprot tome, razliku izmedu argurnenata tipa T & (referenca na tip T), cons t T & i volatile T s; odnosno izmedu '[' * (pokazivac na tip T), cons t T' i volatile T' prevoditelj prepoznaje, tako cia se funkcija S obzirom na te argumerue srnij e preopterecivati.

Pomutnju kod preopterecenja mugu izazvati podrazumijevan: argumenti. Dvije deklaracije parametara koje se razlikuju sarno 1I podrazumijevanirn argurneruirna su potpuno ekvivalentne. Zbog toga ce POZIV funkcije f u glavnoj funkcij i izazvati nedoumicu kod prevoditelja:

void flint i ~ 88) { COllt « i « endl;

void f (j

cout « "Ni§ta!" « endl;

int main (I f();

II pogresk.a t t i nt ] ili fil?

5,8. Preopterecenje Iunkcija

201

return 0;

I

To znaci da je dozvoljeuo funkciju xvadr at (! preoprereriti funkcijarna koje vracaju jednake tipove podataka:

long kvadr a t (int) ; long kvadratllongl;

Medutim, na pokusaj prcopterecenja:

double kub(float); float kub(float):

II pogreAka: obje fun 'cije /1 su 'ublfloat)

prevoditelj ce dojaviti pogresku 0 pokusaju ponovne deklaracije funkcije, buduci da SLi argurnenti obiju funkcija potpuno jednakih tipova.

Pisanje ovako trivijalnih preopterecenih funkcija, u kOJ ima je algoritarn potpuno isti za svaki tip podataka je bitno jednostavnije ako se koriste predlosci funkcijo (vidi poglavlje 5.13), Medutim, ponekad se algorirrni za razlicite operacije svode pod iSIO nazivlje. Primjerice, apsolutna vrijednost (modul) kompleksnih brojeva se racuna prerna bitno drukcijem algoritrnu nego prirnjerice realnih iii cijelih brojeva Takoder, optimizacija koda ponekad iz.iskuje koristenje razlicitih algoritama. Ilustrirajrno to funkcijorn za potenciranje mocn i ca (I: za decirnalne potencije cerno definirati funkciju koja ce potenciju racunati pornocu formulc

ey,ln(x) (za X > 0)

dok ccmo za cjelobrojne potencije koristiti petlju u kojoj cerno mantisu rnnoziti odredeni broj putal. Za racunanje prirodnog logarirrna i ekspcnencijalne funkcije koristit cemo standardne funkcije log (I, odnosno exp () deklarirane 1I cma.th datoteci zaglavlja.

#include <iostream> #include <cmath> using namespace std;

double rnocnica(double K, double y) I if (x <~ 0,) (

ce r r « "Nemocan sam to .i z r acunat i ! If « endl . re turn 0,

return exp(y • log(x));

t U biblioteci maiemauckih funkcija cmath vee postoji funkcija pO'. (double, daub) e) koja obavlja istu zadacu.

202

5. Funkcije

double mocnica(double x, int int b,ojac = y > 0 ? Y double rezultat = 1.; while (brojac-- > 0)

r e z u I tat '= Xi return 'I > 0 ? rezultat

'I) { - 'I;

1, / oezul tat;

Ovakvo preopterecenje fllnkc ije osigurava toea n rezu ltat porenc iran J a cJe 10 broj n i 111 eksponentom i za ncgativne baze:

couc «mocnica(-3, 3) « endl; cout « mocn i ca (-3., 3) « endl; cout « mocnica(-2., 3.) « endl;

/1 ispi3uje -27 II ponovno

II ispisuje po[uku

Prvorn i drugom pozivu prevoditelj pridruzuje funkciju mocnica (double, int), dok trecern pozivu funkciju mocnica (double, double).

Zadatak. Ubacite II kodove gornjih funkcija ispis kontrolnih poruka koje ce identificirati koja je funkcija odista pozvana

Zanimljivo je uociti da unatoc (prividno) kracem izvornom k6du funkcije mocnica (double, double), njeno izvodenje traje znatrio dulje, Uzrok tome su logaritamska i eksponencijalna funkcija koje se u I~oj koriste, a izracunavanje kojih ukljucuje daleko kornpleksnije operacije nego sto je mnczenje 11 petlji druge funkcije, Stoga preopterecenje funkcijorn mocn ice (double, int) mozemo shvatiti i kao odredcnu optimizaciju izvedbenog k6da koja za cdredene vrijednosti (tipove) argumenata omogucava brzc racunanje potencije.

Iz ovih razmatranja je uocljivo koliko zbog slozeuih pravda pronalazenja odgovarajuce funkcije valja biti oprezan prilikom preopterccenja funkcija. U svakom slucaju treba izbjegavati situacije u kojirna nije ocito koja ce funkcija biti izabrana iz skupa preopterecenih funkcija.

Spornenimo na kraj II da se osirn funkcija mogu prcopterecivati i operator]. Pazlj iviji citatelj ce se odmah sj etiti kako su aritmetick i operarori def nita ni za razl it i te ti povc podataka i da je shodrio ti povi rna operanda i rezu ltat razl ititog ti pa. Podsj etirno se kako operator ;, primijenjen na dva podarka tipa inc daje rczultat tipa int, a ako se primijeni na dva podatka ripa floa t, rezultat ce biti toga tipa, itd. Prema lome, vise je nego ocito da je operator zbrajanja preopterecen za parove svih ugradenih tipova podataka - u slucaju da su operandi razlicitih tipova, pretvorbarna se podaci svode na zajednicki tip j primjenjuje odgovarajuci (preoptereceni) operator,

Buduci da su operator: definirani za sve ugradene ripove podataka, ne rnozcmo ih dodamo preoprerecivati, osim ako uvodirno korisnicki definirane tipove. Sroga cerno 0 preopterecivanju operatora govoriti nakon sto upoznamo razrede.

Zadatak. Napisite [unkciju usporedi () koja ce usporedivati dva argumenta koji joj se prenose. Ako je prvi argument veci od drugoga, funkcija treba kao rezultat vratiti I, ako SII oba argumenta medusobno jednaka, rezultat mora biti 0, a ako je drug! argument

5.9. Rekurzija

203

veci, rczuliat mom bili ._/. Preopteretite funkciju t ako da zu argumente prihvuca brojeve tipa daubJ e, te pokazivac na znakovni niz:

int usporedi(const double prviBr, const double dtugiBr); int usporedi(const char 'prviliz, canst char 'drugiNiz);

5.9. Rekurzija

Pozivom funkcije formiraju se unutar njc lokalnc varijable koje su nedohvailjive izvan same funkcije. Neovisnost lokalnih varijabli od okolnog programa ornogucava da funkcija poziva sarnu sebe - ponovnim pozivorn se stvaraju nove lokalne varijablc potpuno neovisne 0 varijablama 1I pozivajucoj funkciji. Ovakav proces uzastopnog pozivanja naziva se rekurzija (engl. recursion). Razmotrirno rekurziju na jeduostavnom prirnjeru: funkcij i za racunanje fakrorijela

i n t faktorijel (int n) {

return (n > 1) ? (n * faktot:ijel (n - 1)) : 1;

Pozoverno li tu funkciju naredborn

int of - faktorijel(3);

Iunkcija faJ'.I:or ijel (i ce pozivati samu sebe u tri "razine"

Prilikorn prvog poziva formalni argument ce biti inicijaliziran na vrijednost 11 ~ 3 U uvjetnorn izrazu ce funkcija faktor ijell) biti pozvana po drugi put, ali sada sa stvarnim argumentorn 2.

2. U drugom pozivu formalni argument se inicijalizira na n ~ 2. Argument pozivajuce funkcije (n - 3) JOS uvijek postoji, ali je on nedohvatljiv i potpuno neovisan od arguments drugcpozvane funkcije. U uvjetnorn izrazu ce funkcija faktorijel (I biti treci pula pozvana, ali ovaj puta sa stvarnirn argumentorn I.

3. U trecern pozivu funkcije f ak t o r i j e l I) formalni argument se inicijalizira 113 vrijednost I (u pozivajucim funkcijama formalni argument] jos uvijek posioje s neizmijenjenim vrijednostirna). Buduci da nije zadovoljen uvjet u uvjetnom izrazu, funkcija f a k t c r i j e Lt ) se vise ne poziva, vee se kao povratna vrijednost pozivnoj funkciji salje broj I.

SI ijedi postepeni povratak do pocelnog poziva Iunkcije:

I. Vrijednost I kOJlI je vratila trecepozvana funkcija mnozi se s vrijednoscu formalnog argumerua u drugopozvanoj funkcij i (n ~ 2), te se taj umnozak vraca kao rezultat prvopozvanoj funkciji,

2. Vrijednost 2 koju je vratila drugopozvana funkcija mnozi se s vrijednoscu formalnog argumenta u pocetnoj funkcij i (n = 3), te se taj umnofak vraca kao rezultat k6du koji je prvi pozvao funkciju t a k t or i j el {I s argumentom 3.

204

5. FunkCIJe

f"ktorijel (3); I

5.10. Pokazivaci na fun kcije

r-

return n*faktorijel(n-l); // vraea 2

..

return n·taktorjel{n-l); II vr a ca 6

I

""

nf - f e k t o r j e I (3);

Slika 5.2. Prirnjer rekurzije.

lzvodenje k6da moze se simbolicki prikazati slikom 5.2.

Mnoge matematicke formule se mogu implementirati pnmjenom rekurzije, Medutim, kod prirnjcue rekurzija valja biti krajnje oprezan: lokalne varijable unutar funkcija i adrese s kojih se obavlja poziv fi.tnkcije se pohranjuju na stog, pa preveliki broj uzastopnih poziva funkcije unutar funkcije moze vrlo brzo prepuniti stog i onernoguciti daljnje izvodenje programa. Daleko je sigurnije (i nerijetko efikasnije) takve formule implcrneruirati jednostavnim petljama. Rekurzije mogu biti vrlo efikasno sredstvo 7..3 rukovanje posebnim strukturama pcdataka (npr. hijerarhijska stabla podataka).

Zadajmo si sljedeci zadatak: zelimo napisati opcenitu funkciju Integral I i za numericko integriranje POlllOCU trapeznog pravila. Uz standardne ulazne pararnetre, kao sto su donja i gornja granica integracije, zelimo da funkcija Integral I ) prihvaca i irne podintegralne funkcije kao argument. To bi nam ornogucilo da jednom napisanu funkciju Integral I ) rnozerno koristiti za bilo koju podintegralnu funkciju, cije cemo ime prenijeti kao argument prilikorn poziva, nesto poput:

5~ 1 O. Pokazivaci na III nkcue

205

Ii ~ Integral{podintegralnaFunkcija, donJaGr, go[njaGr); I) Integral(sin, 0., pi);

A ko 10 n ij e III oguce, b i I cern 0 pris i Ij en i za razl it i te pod integral ne fu nkc ij c svaki puia mijenjati izvorni kod funkcije integral (i ~

Rjesenje za problerne ovakvog tipa pruzaju pokazivaci na funkcijc. Osim 1;(0 funkcija moze prirnati odredene argurnentc i vracati neki rezultat, ona 1I izvedbenorn k6du pl"Ograma zauzima i odredeni prostor U mernoriji racunala. Kada pozivamo neku Iunkciju, tada izvcdenje programa prebacujcrno na memorijsku lokaciju na kojoj je funkcija smjesrena. To prebacivanje se odvija preko pokazivaca na tu funkciju, iako to u dosadasnjirn razmatranj irna nismo eksplicitno naglasili. To znaci da Je ime svake tunkcije u stvari pokazivac na lokaciju gdje pocinje njen kod. Deklaracija funkcije Integral () koja kao argurnente prihvaca pokazivac na funkcij u, te donju i gornju granicu integrala izgledala bi ovako:

float Integral (double ('fJ (double), double xO, double xn);

Prvi argument funkcije Integral I ) .ie pokazivac na funkciju f () tipa double koja kao argument prihvaca samo jedan podatak tipa doub l e. Ispred imena pararnetra u listi argurnenata nalazi se operator dereferenciranja < koji ukazuje na to da cerno funkciji Integral I ) prenijeti pokazivac, a ispred operatora dereferenciranja jc identifikator povrarncg tipa (double) tunkcije f (I Iza pokazivaca na funkciju f I) unutar zagrada se navode tipovi argumenata funkcije. Vjerojatno ne treba naglasavati cia se ti argumenu rnoraju slagati s argumentima funkcije koja ce se pozivati po broju i tipu (ili se stvarni argumenti rnoraju dati svesti na formalne ugradenim iii korisnicki definiranirn preivorbama).

Uocimc zagrade oko oznake za pokazivac f na funkciju. One su neophodne, jer bi u protivnom:

float Integral (double *f (double), double ,,0, double xn j ,

II pogresno: deklaracija runkcije f

zbog viseg prioriteta zagrada nad operatororn ", prvi argument bio interpretiran kao deklaracija funkcije f (double) koja kao rezultat vraca pokazivac na double.

Posvetimo se sada definiciji funkcije 1 ntegral {I. Za pocetak se podsjetirno ukratko o cemu se u trapeznom pravilu radio Interval (xo, x,,) unutar kojeg se funkcija f(x) zeli iruegrirati podijeli se na n jednakih intervala, izmedu kojih se funkcija aproksimira pravcirna (slika 5.3). Integral funkcije (lj. povrsina ispod funkcije) priblizno je jednaka zbroju povrsina tako dobivenih trapeza (srafirane plohe na slici):

gdje je h sirina podintervala:

206

5. Funkcije

yt

I

i

h = xl1 -xo n

_' I I

, 1:._., L. __ ~,~. '>._ :.J __ ' ...

x

Slika 5_3. Trapezno pravilo.

Naravno da je tocnost aprok imacije to bolja sto je broj podintervala n veci, jer se u tom slucajo razlomljeni pravci vise priblizavaju zadanoj funkciji. Radi opcenitosti cemo postupak autornatizirati, tako da ce funkcija prvo racunati aproksimaciju samo za jedan interval:

/ = t; [/(X(I) + I(X])]

o "0 2 2'

a zaurn ce broj intervala udvostrucavati, racunajuci sve tocnije aproksimacije integrals:

Buduci da se broj pcdiruervala udvostrucuje, U svakom koraku se racunaju vrijednosti podintegta!ne funkcije i za sve locke obradene \l prethodnorn koraku, Da se izbjegne nepotrebno ponavljanje racuna, podintegralnu funkciju je dovoljno racunari samo za nove locke i tu novu surnu (nazvat como ju sumauova) pridodati surni iz prethodnog koraka (sllmaStara)_ Postupak ce se ponavljati sve dok su zadovoljena dva uvjeta:

J. relativna prornjena slime je vcca od unaprijed dozvoljene vrijednosti koju funkciji predaj emu kao parameter re lG r i j eh, Ie

2_ broj podintervala je manji iii jednak od broja Ci rcusnax i mus , kojeg takoder prenosimo funkciji kao parameter.

207

Kako f..0J ISJiIL rtc D, 11l,"dU ~V"'" pura i-'1~JlV~IU vr ijeunosu Ova J\d {Jdldll,,,Ua, pi rdt uz.u cemo in: podrazuuiijevane vrijednosti Pogledajruo cjelokupni izvorni ked

ij lLli._.luue -';'_lu:ll Learn.> !include. <cmarh> using neu.e space s t d:

J Loa t integral (double \ < r ) (dGu!)l",.), doub I e xO. doub l e xu, double relGrijeh - 1,e-5~

long CircusMaximus - 655361);

~0uble Podintegrillna(double xl;

a n t main () {

COUt « Integral (sin, 0, 3,141591 « end L.

cout « Integral (podin'tegralna, 0, 1.1 « endl; return 0;

float Integral (double (*f) (double) ,double xO ... doub Ls

double'relGrijeh" long CircusMaximus) long n ~ 1;

double hO =' xn - xO;

cioublesumaS,tara, - 0,; double sumaNova,'- f(xOI dol

XH,

+ f (xn l :

s uma.S tara - 'sumaxove / 2,; s umaelova - 0,;

n *;:;:; 2;

for (long i-I; i < n; i +- 2) sumatlova +- f(hO • i / [J + xO); sumattova.> sumas t ara + sumanova ! n; ,

wh i l e (In<~ CircusMaximus)&& (sumanova I= 0) u (fab;:(i, -sumaStaia /'sumaNova) >':relGrij~!J!);

return'; su~aNova, * hOi' , -'-

daubl e l'odi'n tegr a Lnat double x ) ( re'turnsinl x ) +; Sin (x) ;

N~ poceiku koda ukljucena je datoteka zaglavlja crnat n koja ""Jrzi dcklaracije rnaiematickih funkc ija, LI programu se iz te bib I ioteke korisre funkc ije s i n I. i i f ab s { ) koje vracaju sinus, odnosno apsolurnu vrijednost argumenta. Osirn toga, obratuno pazn] U u gornjem kodu na jos dV3 detalja

Prvo, prirnijeumo sufiks L iza podrazuuujevane VrlJtdJ]0~U za '--J.LL·"~'"~>'lU,u~, un: sutiksom se broj 65536 eksplicitno prcglasava Ion" inc konstantoru. Kako .Ie raj broj yeti od najveceg rnoguceg inc, U protivnorn bi se rnoglo dogoditi da usiijed nepredvidenih pretvorbi prevoditelj krivo pohrani taj broj.

Drugo, uocimo uvjet za ponavljanje do-wn.i Le petlje Uli .It slozen vd (II tlVJ~lii navedenih sljedecirn redoslijedom:

208

5. Funkcije

l. je Ii n <= Ci rcusttax i mus,

2. je Ii sumaNova razlicita od 0, te

3 da Ii se sumaNova relativno razlikuje od sumaScara za vise od re.lGr i j ah.

Redoslijed je ovdje vazan j moze uijecati na pravilan rad funkcije. Prvo se ispituje trlvijalni uvjet (I) - upravo zbog svoje jednostavnosri i kratkoce on je stavljen na pocetak, Tek ako je uvjet (I) ispunjen, prelazi se na uvjet (2), koj i je umetnut zbog rnoguce zamke u uvjetu (3). Nairne, ako se koj im slucajem dogodi da sumaNova bude jednaka 0, dijcljenje u trecern uvjetu uzrokovat ce prekid programa (dijeljenje S nulom). S novododanim uvjetom (2) to se nece dogcditi, jer u stucaj u da on nije zadovoljen, nece se niti poceti ispitivati treci uvjet. lzvodenje petlje ce se prekinuti, a cilatelju ostavljamo na razrnisljanje hoce Ii rezultat funkcije biti ispravan.

Radi bolje ilustracije, pogledajrno uvjet U sljedecern i r-grananju ( og () je funkcija koja vraca prirodni logaritarn In argumenta):

if {(x> 0) && (log(x) < 10) { / / . .- ..

lake funkcija logicki-! ima svojstvo kornutacije, tj. logicki rezultar ne ovisi 0 tome koj i je operand s lijeve, a koji s desne strane, sarno ce gornji redoslijed operanada uvijek osiguravati pravilno izvodenje gornjeg k6da. Ako nije ispunjen prvi uvjet (~. aka je x negativan ili jednak O), daljnje ispitivanje nerna smisla i ono se ne provodi, tako da se uz navedeni redoslijed ne moze funkciji log () prenijeti negativni argument.

Ekvivalenrna situacija je i kada u uvjetu imarno logicki-ili. lzvrnirno uvjer u gornjem if-grananju i napisimo k6d:

if {(x <~ 0) II (log{x) < 10) { ! / ...

U ovom slucaju, cim je zadovcljen prvi uvjet (x <= 0), bit ce zadovoljen cijeli UVJC1, tako da se daljnje ispitivanje ne provodi - ope! je izbjegnuta mcgucnost da se funkciji log () prenese negativni argument.

Iz gomjeg primjera je vidlj ivo da je sintaksa za deklaraciju pokazivaca na funkcije dosta zamrsena. Ako se pokazivac na funkciju c¥sto koristi, jednostavnije je definirati novi sinonim za taj tip pomocu kljucne rijeci t ypade r:

typedef double (*pokFunkcija) (double);

U gomjoj deklaracij] jc pok Funkc i Ja pokazivac ria funkciju koja uzima jedan pararnctar tipa double i vraca tip double. Sada ako zelimo deklarirati pokazivac na funkciju jednostavno mozemo napisati

pokj'un kc ij a mojafunkcij a

5.10, Pokaz,vaci na tunkcl)e

209

Takoder, funkciju ;n'-"''J,d i i bismo mogli dek larrrau UV"KO

t Loa r I ru.eq t a l (p,-,U'unkcl J a t , double xO, doub Le XH, double ralGrijeh - I,e-5,

long CircusMaximu5 - 6SS36L);

PUkdllYace ILd tunkcij U mozerno inicijalizirau tako da itu dodijehu.o alilc:,,, h.JI\~";IJ'" hi tome, slicno kao i kod polja, same ime funkcije bez zagrada za pararnetre .Ie ek vi va I en tno adresi I'll n kc ije:

double xsinxldoubla xl;

mojaFunkci]a xsinx; mojaFunkcija = &xsinx;

l-unkcija se poziva preko pokazivaca tako da 5<'; navede naziv pckazivaca I.(.a ~VJe~d ~e u zagradarna spccificiraju parametri. Pri lome pokazivac se moze prije pozlva dereferencirari operaiorom " ali i ne mora (dereferenciranje so: podrazumijeva):

(tlojafunl<cija(S.) ; (*mojafunk.ci.ja) (5,);

II i s t i po z i v kao i r e da k iZIlild

Potpun tip pokazivaca na tunkciju odreduju povrauia vrijednost funkcije te broj 1 tip argurnenata. Zbog toga nije mcguce pokazivacu dodijeliti adresu funkcije koja irna drukc ij i potp is:

double xy(duubl", x, doubl~ yJ;

mo j e Funkc i j a ~ xy; II pogreska n"\'-'Hlf.'atlbiln~ t i pov r

ISlO vrijedi i za rnedusobnu dodjelu dvaju pokazivaca, Pokazivacka aritrneuka nerua srnisla niti Je dozvoljena za pokazivace na funkcije,

Podrazumijevani pararnetri nisu dio potpisa lunkcije, te je stoga rnoguce dodijeliti adresu neke funkcije pokazivacu koji ima drukcije podrazurnijevane parametre:

void funkcija{int i - 100);

void ('"ok) (Lnt ) - fuokcij a;

II c!ozvoljeno

PI! lome, ako sada tunkciju zelirno pozvau pornocu pokazivaca pok podrazuuujcvane pararnerre ne mozemo koristiu

pok (60) ; pot {I;

il OK

/1 pogrB~ka: pok nema podrazumljevdnih II parametara

f}okilLI vacu pu. IIIOlclllu cak dodijeliu drug: podrazumijevarn parameter.

void (' pok) (lilt ~ 80 i - f unkc.i je ,

pokl); II dozvoljeno &e se poziva funkcijalHUj

210

5. FUl1kcije

5.11. Fun kcija main ( )

Funkcija ma in () po mnogim svojstvima odstupa od ostalih funkcija. U prvorn poglavlju smo nautili da svaki C++ program mora sadrzavati (same jednu) funkciju main () kojom se zapocinje izvodenje programa. Takoder, valja naglasiti da se funkcija ma i n () ne maze pozivati. Postoje dvije moguce varijanre funkcije main I):

int main () / / ...

koja ima praznu lisiu argumeriata (ovaj oblik srno do sada iskljucivo i koristili). iii

int main(int argc, char 'argv[]) I II ...

U ovom drugom obliku, prvi argument ugc jest broj pararnetara koji se prenose prograrnu iz radne okoline unutar koje je program pokrcnut. Ako je a r qc razlicit od nule, tada se parametri koj i se prenose rnogu dohvatiti preko clanova polja argv, od argv[O] do s r qv l a rqc - 11 Ti clanovi su pokazivaci na znakovne nizove koji sadrze pojedine pararnetre Tako je a r qv [0 I pokazivac na pocetak niza koji sadrzi irne kojim je program pozvan iIi prazan niz (''''). a rqv l a rqc l je uvijek nul-pokazivac.

Iiustrirajmo dohvacanje argumenata funkcije main (), jednostavnirn programorn koj i ispisuje rezultat arirmeticke operacije na dva broja, zadana kao pararnetri pri pozivu prograrna:

'include <iostream> Hnclude <cmath> using namespace stci;

int mairi(int argc, char< argyl]) { if (argc < 4) (

cerr .« "Nedovoljno pa rame ta ra !" « end l.: return I;

float operandI atof(argv[l]); char operacija *argv[2]; float operand2 atof(argv[3]); switch (operacija) (

case{'+') ;

cout « (operandI + operand2) « endl; break;

ca s e { I - I :1 :

cout « (operandI - operand2) « endl; break.'

case I '*');

cout « (operandI • operand21 « endl; br,eaY;

case ( , /') :

..

5.11. Funkci)a mainO 211

cout « (operandi I operand21 « end l , break;

default:

cerr « "Nepoznati operator" « operacija « endl; return 1;

return 0;

Za pretvorbu znakovnog niza u realni broj (f Loar ] 1I prirnjeru je koristcna Iunkcija a t of (I, iz biblioieka crnath i cstdl.ib.

Nazoverno jj izvrsni program koji Sto dobiva prevodenjern ovog koda r acuria j , tada ce naredba

l.'acunaj 2 I 3

(praznine izrnedu naziva programa i prvog operands, odnosno izrnedu operanada I operatora su obvezatne) iz operacijskog susiava ispisati kvocijerll brojeva 2 i 3 (0.666667).

Umetnerno li II gOn1ji k6d naredbe za ispise argumenata, izvodenjem prcgrama gornjom naredborn saznat cemo da SlI argumentima pridruzene sljedece vrijednosti:

argc argv(O] argv( 11 <11'g1.1[21 arg1.1[3] argv[4j

(1 racunaj . exe II

"21P

"/" "3"

o

Prvi argument argc je jednak broju pararnetara koj i slijede iza naredbe kojorn je program pokrenur. a rqv ] 0 J jest pokazivac na znakovni niz s imenom kojim je program pokrenut, Pcnekad taj znakovni niz sadrzi cijelu stazu do izvrsnog programs (npr. "C : \ \knj iga . cpp \ \primj e ri \ \racuna.j , e xe -). Slijede pokazivaci na pararnctre (znakovne nizove zakljucene nul-znakorn), a niz zakljucuje nul-pokazivac argv 14J

Zadatak. Dotjerajte gornji program taka cia ispise ne samo rezultat vee i cijelu matematicku operaciju.

Ztutatak. Napisite program kojem se kao parametri navode imena jedne iii vise datoteka cije sadriaje treba iscitali i ispisati 110 zaslonu.

Zadutak. Napisite program kojem se kao parameter navodi neki tekst. Ta] leks/ ucitojte u znakovni niz te izracunajte ukupnu duljinu niza, zajedno s prozninama. Upuia: buduci da pojedine rijeci program prihvaca kao zasebne parametre, visestruke praznine izmedu pojedinih rijec! necete moci registrirati - uzmite da je izmedu svake rijeci samo jedno prazno mjesto. Za "spojanje ,. parametara moiete koristiti funkciju dol ijepi () koju smo napisali 11 poglavlju 5.5 (samo pazite da ukupna duljina niza ne premasi onu koja je zodana u kodu te funkcije).

212

5, Funkcije

Funkcija main I) II pravilu zavrsava naredborn return (koja mora sadrzavati cjelobrojni parameter) koja uzrokuje unistenje svih autornatskih objekata. Operacijskon, sustavu se prenosi vrijednost navedena u re t urn narcdbi. Ako je izvodenjc programs zavrsilo ispravno, podrazumijeva se da povratna vrijednost bucle 0; u protivnorn se vraca neka vrijednost razl icita od 01 Aka izvodenje funkcije main () nije zakljuceno naredborn return (primjerice ako jll zaboravimo napisati), (ada ce oua skoncati kao da sc na kraju nalazi naredba

return 0;

Buduci da u svakorn C++ programu smije biti samo jedna ma i n () funkcija, njeno se irne ne moze preoprerecivati.

Ako ne zelirno posebno obavjestavati operacijski sustav 0 uspjesnosu izvodenja naseg prograrna, funkciju main () mozemo deklarirati tipa void:

void main () II ...

U torn slucaju se naredba return ne mora navesti, a prilikom povratka u operacijski sustav se vraca vrijednost neka nedefinirana vrijednost. Iako ce ga rnnogi prevoditelji bez problema prihvariti, ovakav oblik funkcije main () (relikvija iz prvih verzija jezika C) nije propisan srandardorn te ga stoga (zbog upotrebljivosti k6da u buducnosti) ne preporucujerno

Na kraju, spornenimo problem debuggiranja prograrna ked koj ill se ocekuju ulazni parametri. Mnogi pocetnici se muce s rakvirn prograrnima, jer ne znaju kako predati parametre programu kada ga izvode 1I nekom debuggeru te programe pokrecu u nedogled iz kornandne linije, pokusavajuci iz kontrolnih ispisa shvatiti nit izvodenja prograrna. Svi iole bolji debuggeri (bolje receno: svi koje mi znarno) irnaju u nekorn dijalogu polje u koje se mogu upisati i pohraniti pararnctri iz kornandne linije - time istjerivanje bulla i za takve prograrne postaje "parce kolaceta" (engl. piece oj cake).

5.12. Standardne fu nkcije

Jezik c++ nerna fuukcija koje su ugradene u sam jez ik, no programeru za obavljanje citavog niza ripicnih operacija redovito na raspolaganju stoji mnostvo funkcija u bibliotekama koje se isporucuju zajedno s prevcditeljem. Vecina tih funkcija je obuhvacena standardorn, tako da se mogu koristiti bez bojazni po prenosivost kcda. Medutim, uz vecinu prevoditelja se isporucuju i biblioteke sa specificnirn funkcijama. Koristenje takvih funkcija u k6du ce ponekad znatno olaksati pisanje program a, ali treba voditi racuna da se takav k6d vr lo vjerojatno nece rnoci prevesti nekim drugim prevcditeljem.

Za najcesce pogreske, u svakorn operacijskom sustavu postoji skup definiranih vrijednosti koje bi programi trebali vracati.

5.12. Staridardne fun kcue

213

Za Iunkcije iz prilozenih biblioieka vrijede isla pravila kilo I ill funkcije koje sam; deflnirarno. Izrnedu ostalog, to podrazurnijeva da funkcija 1110ra biti deklarirana prije prvog poziva, Prilozene biblioteke s funkcijama su obicno u objektnom obliku (vee su prevedene) radi sro brzeg povezivanja izvedbencg k6da (izvorni k6d se ne isporucuje radi zastire autorskih prava). Stoga su deklaracije tih funkcija pohranjerie u posebnim datotekama zaglavlja - da bi se funkcija iz neke biblioteke mogla pravilno koristiti, dovcljno je na pocetku izvornog koda ukljuciti pripadajucu daroteku zaglavlja pretpr ocesorskorn naredborn Ii include,

Demonstrirat cerno upotrebu standardnih funkcija prograrnom koji prervara koord inate locke u ravni ni zadane 1I pra vokutnorn koordinatnom sustavu li po lame koordinate. Taj zadarak se obavlja sljedecim formularna:

I'=Jx2+y2

Pri lome su x i y pravokuine koorduiate locke, d I' f If potumjer I kut radij-vekrora tccke. U kodu cerno koristiti dvije standardne funkcije: sqr t I) koja vraca kvadratni korijen argurnenta, te atan2 (double x, double y) koja vraca arkus tangensa (inverz tangensa) za koordinate locke na apscisi j ordinati. Obje funkcije su deklarirane 1I datoteci cma t h koju ukljucujerno na pocetku k6da. Buduci da rezulrat zelirno ispisaii, ukljucit cerno i zaglavlje iostream \I kojern su deklarirani ulazni I izlazni tok te preoptereceni operator «.

#include <Los t r earn> #include <cma t.h> using.na~espace std;

·s
int ma in t) (
d",uble x' '= -1;
double y -1; do0ble r sqrt(x * x + y * yl; double fi - atan2(x, y);

cout« "r =" « r «" f i _no « fi « endl; return 0;

Valja uociti da postoji i funkcija at an (double) koja kao argument prihvaca tangens kuta koji treba izracunati. Medutirn, funkcija atan2 () je prikladnija, jer daje tocan kut u sva cetiri kvadranta (funkcija at an I) daje rezultate samo u prvom i cetvrtom kvadrantu), te za kutove vrlo blizu Te/2 i -nI2, lj kada je x = O.

Ako se neka biblioteka zaboravi ukljuciti, prevoditelj ce javiti pogresku rijekom prevodenja. Ukljucivanje suvisnih biblioteka (tj. biblioteka iz koje nisu koristene funkcije) nece imati nikakve kobne posljedice na izvrsni k6d, jedino ce se sam program

214

5. Funkcije

dulje prevoditi Mnogi danasnji povezivaci (lil1keri) provjeravaju koje funkcije se pozivaju u programu, te sarno njih ukljucuju u izvrsni kod.

U tablici 5.1 Sll navedene same neke od standardiziranih biblioteka te opis funkcija koje se u njima nalaze. Biblioteke cija imena pocinju sa 510vol11 c su prilagodene biblioteke slicnog imena naslijedene iz jezika C (npr. crnath je identicna math. h biblioteci II jeziku C). Vjerujemo da su vecini citatelja najzanimljivije matematicke funkcije i funkcije za obradu znakovnih nizova, deklarirane u cma t h, odnosno c s t r i nq. U prilogu B na kraju kI1J ige zainteresirani citatelj moze naci popis najeesce koristenih funkcija, te nj ihovo kratko objasnjenje.

Tablica 5.1. Neke teste koristene standardne datoteke zaglavlja (abecednim slqedorn)

Naziv

Opis

c floa t climits cmath complex cstdarg

cstdio cs t d l i.b cstring ctime

cctype fstream iomanip iostream localE!

Podaci 0 realn im tipovima podataka

Podaci 0 rasponima vrijednosti cjelobrojnih podataka Matemaricke funkcije

Kornpleksni brojevi i funkcija

Makro Iunkcije i definicije za poziv argurnenata funkcija dek.lariranih s neodredenirn brojem argumenata ( ... )

Tokovi za datotecni ulaz i izlaz (naslijedeni izjezika C) Rutine za pretvorbu, pretrazivanje, sortiranje i sl.

Rutine za rukovanje znakovnim nizovirna i mcmorijskim blokovirna Srrukture za pohranu podataka 0 vremenu i daturnu, te rutine za njihovu obradu

Funkcije za rukovanje znakovima

C++ rokovi za dstotecni ulaz i izlaz Ulazno-izlazni manipulatcri za C++ tokove Osnovni ulazno-izlazni C++ tokovi

Funkcija sa ·lem ljopisno- i jezicno-speci fitnim podacima

Koristenje vecine maternatickih funkcija je intuitivno jasno, tako da ill ncma potrebe detaljnije opisivati. Funkcije za rukovanje znakovnim nizovirna iziskuju posebnu paznju zbog nacina na koji se oni pohranjuju. Vrlo siroka primjena tih funkcija zahtijeva da im posvetimo malo viSe paznje.

5.12.1. Funkctje za rukovanje znakovnim nizovima

Gotovo 1I svakorn programu 1I kojem se ispisuju peruke, javlja se potreba za rukovanjem znakovnim nizovima, na primjer kopiranjern iii povczivanjern znakovnih nizova. Standardni maternaticki operatori nisu definirani za znakovne nizove, tako da se (za razliku od nekih drugih jezika) u prograrnskorn jeziku C++ ne mogu vrsiti jednostavna pridruzivanja iii zbrajanja znakovnih nizova. Primjericc, pokusaj generiranja dva

512. Sta ncard ne funkcije

215

,j cd na ka znako vria n i za pomocu operatora prid ruzi vanj a u s Ij edecern p r i IllJ CI"U nece dat i ocekivan i rezultat:

char *znl -"= "Do r a ",

char '2n2 znl;

! I preusmje rava pokaz ivac

Gornji odsjccak ce sarno preusrnjerjti pokazivac w2 na pocetak znakovncg ruza zn l . Buduci cia se pritom nece stvoriti novi znakovni niz, svaka promjena na nizu Hl2 ce se odraziti j na nizu z n l , sro vjerojatno nije ono 510 bismo ocekivali od operators pridruzivanja. Takoder, jcdnostavnim operaiorom zbrajanja nc mozemo nadovczati elva znakovna niza:

char *znl char 'zn2 char *zn3

"Dora "; "KrLlpiceva" ; znl + zn2;

1/ pogreAka: nepravilno 1/ zbrajanje pokazivaca

Za operacije na znakovnim uizovirna programeru je na raspolaganju citav niz funkcija i rnakro naredbi deklariranih u datoteci cs t r i nq, U njoj su definirane funkcije za kopiranje nizova, nadovezivanje dva niza, pretrazivanje nizova, usporedbu dva nizova, odredivanje dulj inc niza, pretvorbe velika-rnala slova i slicno. Upotreba najkorisnijih funkcija pokazana je 1I sljedecern kodu:

~include Ciostream> #include <cstring> using namespace stu;

r nr main () {

char *prvi = "ma Li " i char *drugi - "princ"; char ·praznina - " ";

inc ukupnaDuljina = strlen(prvi) + strl~D(praznina) +

stz:len(drugi) ;

char 'oba = new char[ukupnaOuljina + IJ; strcpy(oba, prvi);

strcat(oba, praznina);

strcat(oba, drugi);

int usporedba = strcmp(oba, prill); if (uspor edba) {

if (usporedba > 0)

cout « U\,H! «oba «"\1' je vec i, <Xi \'11' « p rvi « "\"" « endL

else

Gout « "\"" « prvi « "\" je veei oC \""

« oba « "\"" « enol;

else

COUl, « " \"" « prvi « "\" i \"'; « oba « "'" au jednaki" « endl;

return Oi

216

5 Funkcije

Funkcija st r len (I izracunava duljinu znakovnog niza Ne Sill ije se zaboraviti sljedece:

Zato je bilo neophcdno prilikom alokacije prostora za znakovni nil. obe zbroju dulj ina znakovnih nizova prvi , druq i i p r az n i na pridodati I_

Zadatak. Razmislite i provjerite .510 ce ispisati sljedeca naredba:

cout « str'lenl"\"lOl\""1 « end l ;

Funkcija s t r cpy I J, cijaje deklaracija oblika

char *strcpy(char *kamo, const char 'odakle);

preslikava znakovni niz (zajedno sa zakljucnirn nul-znakom) na koji pokazuje drugi argument, na lokaciju na koju pokazuje prvi argument (slika SA). Povratna vrijednost

char *oba --~D:=o=r::::IJ

char =p r v i __ n--.EJ al~

strcpy(oba, prvi);

Slika 5.4. Djelovanje funkcije s t rcpy I),

funkcije je pokazivac na odrediste preslikavanja (taj se podatak rijetko koristi), Iz deklaracije je ocito da niz koji se preslikava ostaje nepromijenjen. Medutim, buduci da se preslikavanjem rnijenja sadrzaj memorije na koju pokazuje prvi argument, neophodno je osigurati dovoljan prostor (ukljucujuci i zakljucni nul-znak) 1I koji ce se izvorni niz preslikari. Na prirnjer, k6d

char 'replika;

strcpy(replika, "r.e rmi ne t o r "}.

II opasno!

ce biri preveden, ali ce prilikorn izvodenja funkcija s t rcpy {) preslikati znakovni niz u dio mernorije koji ne pripada (same) nizu r ep.l i ka, sto ce zasigumo poluCiti nezeljene efekte:

Zadatak. Razmislite sto ne val]a u sljedecem kodu:

char. broj I)J;" . .. s t rcpy (broj, "pet",;,

y~: '

II potencijalna opas~ost!

5,12. Standardne funkcije

217

Ako niste sigurni 1I odgovor, isprobajte ovaj k6d dodajuci naredbu za ispis niza br o j nakon poziva funkcije s t rcpy {I, Sam primjer je vee sam za sebe potcncijalno "opasan", al i cak i ako se prezivi kopiranje, imat cemo problema ako niz broj kasnije pokusamo preslikari 1I ncki drugi niz (razrnislitc zasto). Ovakva pogreska JC vrlo cesta 1I C++

~ pocetnika.

Funkcija s treat I) se koristi za nadovezivanje sadrzaja dva znakovna niza. Ona irna deklaracij u s ljedeceg Q blika:

char *strcat (char <spr i j eda, canst char *straga);

Djeluje tako da nadovezuje znakovni rnz S t raqa na znakovni niz sp r i j eda. Prilikorn preslikavanja niza straga preslikava se i njegov zakljucni nul-znak (vidi sliku 5,5),

char *oba ,----c'- ... r:r:I': Il_lJl~r]

crra r *praznina ---" .. -- -t.-LJ\o] strcat (oba , praznina);

char 'oba - --+E_l_:~IID]~]\01

Slika 5.5. Djelovanje funkciie s t rce t (I

Kao i kod funkcije s t rcpy (I, programer mora paziti je Ii za rezultanini niz (zajedno sa zakljucnirn nul-znakom) osiguran dovoljan prostor, kako se preslikavanjern ne bi zaslo u rnemorijski prostor rezerviran za druge varijable ili izvedbeni kod. Stoga nije naodrnet jos jednorn naglasiti:

Zadatak. Zasto sljedeca naredba nije izvodiva:

s treat ("Dras", ':Katalenic" I;

!! pcq re s ka

Zadatak: SID ce biti ispisano izvodenjem sljedeceg koda:

chac deponij 125};

s t rcpy (deponi j, "kana.l "j , strcpy(deponij + 3, "tata u r-duru"); cout « deponij « endl:

Funkcija s t rcmp ( I j e deklarirana kao

218

5. Funkcije

inc s t r cmp I cons t char <p rv i Ni z , cons t ella r 'd.rugiNi z ) ;

Ona obavija abecednu usporedbu sadrzaja dvaju niza, znak po znak sve dok su odgovarajuci znakovi 1I oba niza medusobno jednaki iii dok ne naleu na zakljucni nulznak u jednorn od nizova Ako je prvi niz veci (u abccednom slijedu dolazi iza drugoga), funkcija kao rezultat vraca cijeli broj veci ad nule; ako su nizovi potpuno jednaki vraca se nula, a ako je drugi niz veci, vraca se negativni cijel i broj.

Funkcija s tr cmp I J razlikuje velika i mala 510va. Preciznije, usporedba se obavlja prerna kodovima znakova gdje najcesce (barern je tako slucaj s najrasircnij 1[11 ASClJ k6dovima) velika slova prethode rnalim slovima. Zbog toga ce usporedba nizova II sljedecem primjeru:

char *prvi = Plmamaf!; char 'clrllgi ~ "Tara";

int usporedba - strcmp(p-vj, drugi); if I Llsporedba=-O) (

cout « " \"" « prvi « "\" i \nn « drugi

« "\" s u j ednal .i " « endl;

e Ls e (

if lusporedba > 0)

COllt « "\"" «prvi «"\" jc; vee! ad \"" « drugi « "\"" « endl;

else

cout, « "\'''' « drug! « "\" je yeti ad \"" « prvi « "\"n « endl;

javiti da po abecednorn slijedu niz "T'a t a " prethodi nizu "mama", a 10 vjerojatno nije ono sto bi korisnik ocekivao Da bi se to izbjeglo, valja prije usporedbe sva vel ika slova pretvor iti II mala (iii obrnuto). Za to se rnogu upotrijebiti standardne funkcije t o Love r (I, cdnosno toupper (), definirane u zaglavlju cctype. Na zalost, one obavljaju pretvorbu same za pojedine znakove, take da moramo sami napisati petlju koja ce kcnvertirati svaki pojedini znak u nizu:

char *umalo{char *ni.) char =z n ::;;:_ niz; while (*zn) (

*zn = tolower{'zn); zn++;

ret urn ni 2;

U biblioteci cs t r i nq mnogih prevoditclja mogu se naci funkcije s t r Iwr (i, odnosno strupr() koje rade upravo (0 za 'ito smo ru: maloprije napisali funkciju uma Lc I ) , no kako te funkcije nisu standardizirane, ne treba se previse oslanjati na nj ihovu upotrebu.

Preostaj e j os problem nasih s lova u znako vn i rn n i zovima. U spored i rno Ii funkcij 0111 s t rcmp () dva niza:

5.12. Standardne funkcije

219

char <kn j i s k i = "t a t.a", char <na ski. ~ "caca ":

dobit cemo da je "caca" iza "tala". Zlobnici ce se sloziti da je to u redu, no po abccednom slijedu je ipak slovo '6' ispred slova 'r'. Za leksicku usporcdbu nizova s nacionalnim znakovljern treba koristiti funkciju strcoll (), koja je potpuno ekvivaleruna gore opisanoj funkciji s t rcmp (). Ona usporedbu radi po trenutno aktivnirn zcmljopi sn i m i .I ezicn i 111 postavkarna na racuna Ill. Ako Ie postavke ne odgovaraj u, prij e prvog poziva funkcije s t rco l I () treba definirati lokalne zemljopisne pararnetre pozrvorn funkcije setlocale ().

setlocale{LC ALL, "Cr oa r i anvj :

int usporedbi = strcolllknjiski, naati):

Prvi argument funkcije setlocaLe (I jest cijeli broj (int) koji odreduje koje CC SVe zernljopisne parametre funkcija prornijeniti (vidi Tablicu 5.2). Drugi pararnetar funkcije jest znakovni niz s opisom zemljopisnog lokaliteta, Ako nije posebno specificirano, podrazumijevana vrijednost za lokalitet jest "C", sto cdgovara minimalnim uvjetima prerna Arnerickorn Nacionalnorn lnstitutu za Standarde (ANSI). Funkcija s e t l.oca Le I) kao i cjelobroj ni pararncni iz Tablice 5.2 definirani su II biblioteci cloca l e

Tablica 5.2. Cjelobrojni pararnetar za funkciju setiocale()

LC i'.LL

LC COLL!'.'!'E LC C'tyPE

LC MONET~.RY LC_NUMERIC LC_THlE

S vi lokal no zemljopisn i pararnetri (kako je opisano do lje) Pararnetri vezani na funkcije st rco l Lt) , s t r x f rm () i 51. Parametri vezani uz pismo (znakove),

Pararnetri vezau i uz novcane vel icine.

Parametri vezani uz pisanje brojeva (decimalni znak, razdvajanje risuca). Parametri vezani za ispis vrernena.

Kako bi se pojednostavnilo rukovanje znakovnim uizovima, pogodno je znakovni niz opisati pomocu objekta. Za raj objekt tada mozemo definirati operarore koji bitno pojcdnostavnjuju gore navedene operacije. Tako se usporedba moze obavljati siandardnim poredbenirn operatorirna «, <=, ==, I =, )~, »), dodjela operatororn = i slicno. Stovisc, dio C++ standarda je i razred 5 tring koja vee posjeduje SVLl navedenu funkcionalnost - nju cerno detaljnije obradiri na kraju sljedeceg poglavlja,

Zadatak. Napisite program za sortiranje polja znakovnih nizova (za pohranjivanje rijeci koristite polje pokazivoca. kako je bito opisano 11 poglavlju 4.4. I)

5.12.2. Funkcija exa, t ()

S 1 ozcn i prograrni sc sasto je od mnosrva fu n kc ij a koje se pozi vaju iz fu nkcij e ma i r; (I il i iz neke druge korisnick: definirane funkcijc. Struktura medusobncg pozivanja funkcija u

220

5, FunkCIJe

takvirn prograrnima nerijetko moze biti vrlo slozena. Teskoce nastupaju ako tijekom izvodenja program a nastupi pcgreska rako da Je potrebno trenutacno prekinuti izvoaenje programa. Regularni povratak iz funkcija u takvirn situacijama moze biti vrlo Illukolrpan iii cak nemoguc.

llustrirajmo to prunjerorn programa koji poziva funkciju za racunan]e prirodnog logaritma, Kao sto znarno, logaritarnska funkcija definirana je same za pozitivne brojevc vece od nule, Sto, medutim, napraviti ako se kojirn slucajern u tijeku proracuna toj funkciji prosl ijedi nula iii negativan broj? Kako ce funkcija za racunanje logariuna dati do znanja pozivajucoj funkcij i da je rezultat nedefiniran, kad ona mace kao rezultat rnoze vratiti bilo koji pozitivni Iii negativni broj? Nije narn ostala na raspolaganju niri jedna "specijalna" vrijednost koja bi signalizirala pozivajucoj funkciji neispravnosi rjesenja.

J edno rj esenj e je trenutacno prek i n uti i zvodcnje programa, za SlO mozemo iskoristiti funkciju exi t (I deklariranu u biblioteci cs t d Li.b:

void exit(inc status);

Cjelobrojni argument status te funkcije predaje se operacijskom susiavu kao rezulrai program3, ekvivalentno onorne koji se vraca u naredbi return na kraju glavne funkcije. Pcgledajmo kako bisrno mogli napisati k6d za navedeni program:

'include <iostream> #include <cmath> 'include <cstdlib> using namespace std;

II fja prirodni logaritam double In (doub l e x I I

if Ix <= 01 exit(l); return log (x);

II poziv funkcije exit()

i nt; ma i n t) (

~out « In(2) « endl; ~out « In(O) « endl; cout « In(5) « endl; return 0;

II ciao da "program main"

U fun kcij i 1 n (I pozi va se standardna funkcija log II iz mate mati eke b: b lioteke P, IJ ~ poziva te funkcije provjerava se argument i ako je on manji iii jcdnak nuli, poziva se funkcija e x i t () Zbog toga ce se prilikom drugog poziva funkcije in I) (argument jednak nuli) prekinuti izvodenje programa, a treci poziv funkciji In () nece niti biti upucen,

Pozivom funkcij e ex it I) se zatvaraj u sve daroteke kOJ e su bile ot vorene tij ekorn izvodenja prcgrama i unistavaju svi staticki objekti, redosl ijedom obrnutim od redoslijeda njihova stvaranja.

5.13, Pradlosc, funkcljJ

221

5.13. Pred losci fu n kc ija

U odjcljku 0 preopterecenju imena lunkcija vidjeli SIl1(, kako se isri idenufikator uioze koristiti za razlicite definicije funkcija _ prcopterecene funkcjje su irnale jednaka imena. ali su se razlikovale prerna argumentima. Preopterecenje funkcija ornogucava svodenje na zajednicko irne slicnih operacija nad razlicitim tipovima podataka. ajocitiji primjer za to je bila funkcija za porenciranje mocru ca (1 koju smo razlicito definiral i za ejelobrojne, cdnosno decimalne potencijc (primjer na str. 201)

Meduurn, cesto su algoritmi ill razlicite tipove podataka potpuno idenucni. Ako se takav problem rjesava preopierecenjem, potrebno Je definirati funkciju za svaki moguci lip arguments Funkciju kvadr at () treba ponoviti za sve moguce tipove argurnenata unatoc cinjenici da one sadrze POtPUIlQ isti kod:

inline int kvadratlint xl { return X * x;

inline float kvadrat(floal xl ( return x • x;

il11il18 double kvadratldoubl~ xi { return x * x;

U ovakvim slucajevirna je urnjesto preopterecenih funkcija daleko prakticnije korrsuu predloske funkcija (engl. [unction templates) koji ornogucavaju da se jednorn definirani ked prevede vise puta za razlicite tipove argumeuata. Siniakticki, deklaracija predloska funkcije ima oblik:

template <argument_predloska, ... > deklardcija_funkcije;

Svaki argument predloska se sastoji od kljucne rijec: class i imena argumenta, tc se svaki argument mora pojaviti u 1 isti argurnenata funkcije. Ako je deklaracija funkcije ujedno i njena definicija, tada je deklaracija predloska ujedno i definicija predloska.

Pogledajmo kako bismo funkciju kvadr at (I napisali pornocu predlozaka i time izbjegi. visestruko pisanje koda:

template <class Tip>

inline Tip .kvadr at (Tip x) ( return x * Xi

Ovum deklaracljom/dcfinicijom je funkcija kvaa:[at {) parametnzirana, tako da So; lip argurneuta i tip funkcije rnogu prema potrebi rnijenjati. Prilikorn prevodenja, prevoditelj ce za svaki poziv funkcije kvadr at II nadornjestiti Tip odgovarajucirn tiporn podatka. Konkretno, sljedeci pozivi ce uzrokovati genenranje triju razlicitih oblika funkcije kvadrat I):

222

5. FunkciJe

float f - 1.2; int i ~ 9;

double d = 3.14159;

co~t « kvadrat(f) « endl; cout « kvadrat(i) « endl; cout « kvadrat(dl « endl; cout « kvadrat(1.4) « endl;

II float kvadrat(float) II int kvadrat(intl

II double kvadrat(double) /1 double kvadrat(double)

Uocimo da se u posljednjem pozivu rea Ina konstania implicitno tretira kao podatak tipa double, te sc shcdno tome poziva funkcija s takvirn tiporn argumenta,

Argumenti predloska se mogu koristiti visckratno kao argument: u deklaraciji funkcije, Prirnjerice, sasvim opcenitu funkciju z am i j eni II kojoj je zadaca zamjena sadrzaja dvaju objekata, mozerno deklarirati i de fin irati kao

template <class Tip>

void zamijeni{Tip &prvi, Tip &drugi) { Tip privremeni ~ prvi;

prvi = drugi;

drugi ~ privremeni;

U ovorn poglavlju su predlosci funkcija obradeni sarno informativno. Kako upotreba predlozaka funkcija posebno dobiva Ita znacaju uvodenjern razreda i predlozaka razreda, predlosci funkcija ce vrlo detaljno biti obradeni u poglavlju 10.

5.14. Pogled na funkcije "ispod haube"

Prograrneri koj i prograrne pisu u nekom visern programskom jeziku, kakav je i jezik C++, vrlo rijetko trebaju znati ista 0 organizaciji i radu racunala. Prevoditelj i povezivac ce (ponekad cak uspjesno) prevesti njihov program pisan u jeziku razumljivorn ljudimaprogramerima, u strojni kod, jedini jezik koji prccesor u racunalu "razurnije". U tom strojnom jeziku ne postoje objekti, konstante i funkcije, vee samo memorijske adrese. Unatoc cinjenici da golerna vecina programera gO!OVO nikad nece moran isla znati 0 organizacij i rnernorije i 0 tome kako se strojni k6d izvodi, za cjelovito razurnijevanje problematike je ipak dobro razjasniti neke osnovne stvari, Citatelji koji su familijarni s tim pojmovima iIi to znanje smatraju suvisnim, slobodno mogu preskociti ovaj odjeljak.

Prilikorn pokrctanja nekog prograrna, izvedbeni ked se s vanjske jedinice (diska, diskete, CD-RO!\·1-a) ucitava u radnu rnemoriju racunala (RAM, engl. random access memo,y) te se na njega prenosi izvodenje, Pri likorn ucitavanja i pokretanja prograrna, dio rnemorije se iskoristava 711 srnjesraj sarncg izvedbenog koda, a dio se koristi za pohranjivanje podataka. Pr itorn je dio za pohranjivanje podataka pcdijeljen na tri prakucki neovisna podrucja: podatkovni segment (engl. data segments, hrpu (eng!. heap) i Slog (engl. stack). Podatkovni segment (moze ih biti i vise) je pcdrucje mernorije 1.1 kojem SlI smjesteni globalni i staticki objekti. Hrpa je dio rnemorije koji je dostupan izvedbenorn kodu u bilo kojern trenutku izvodenja - u taj dio se pohranjuju dinarnicki alocirani pcdaci. Stog je dio mernorije na koji se pohranjuju lokalni podaci, dostupni

5.14_ Pogled na funkcije "is pod naube"

223

sarno Iunkcij i eiji se k6d trenutacno izvodi (osim lokalnih podaiaka na stcg se pohranjuju jos i podaci potrebni prilikom poziva i povrata iz funkcija, no 10 cemo jos kasn ij e razjasn i ti)

Naravno da se sam program ne mote izvoditi bez "ruozga" cijele akcije - sredisnje procesorske [edinice (engL Central Processing Unit, CPU) iii krace (mikro)procesoro_ On interpretira naredbe strojnog koda i poduzima odgovarajuce akcije: dohvaca podatke iz rnemorije, obavlja operacije S njima i pohranjuje ih natrag u memoriju. Pritorn on koristi svoju "internu" memoriju - registre (engl. registers). Radi se 0 vrlo skucenoj rnemoriji u koju stanu svega nekoliko osnovnih podataka (tipa i nt, floa c, eventualno doubt e), al i ona ionako s luii sarno za privrerneno pohranj ivan] e pcdataka tij ekom obrade, Osim nekoliko registara za podatke koji se obraduju, za pravilan rad prograrna (a i cijelog racunala) bitna su dva registra: pokazivac instrukcija (engl. instruction pointer) te pokazivac stoga (engl. slack pointer). Pokazivac instrukcija u svakom trenutku sadrzi adresu memorije na kojoj se nalazi sljedeca instrukcija koju procesor mora izvesti. Procesor pri likom doh vacan j a i nstru kcij e prvo ocita sadrza J pokazi vaca instrukcija, eiji sadrzaj upucuje na mjesto gdje se nalazi sljedeca izvedbena naredba. P011l0CU adrese iz pokazivaca instrukcija procesor ocitava naredbu iz dijela memorije u kojoj je smjesten izvedbeni k6d te pristupa njenom izvodenju. Istovremeno se poveca sadrzaj pokazivaca instrukcija tako da on pokazu je na 51) edccu instrukciju programa.

Naide Ii se tijekom izvodenja programa na naredbu za skok na neku drugu memorijsku adresu, procesor ce adresu sadrzanu u instrukcij i ubaciti u pckazivac instrukcij a, time ce se izvodenje prograrna autornatsk i prebaciti na ielj enu adresu. Teskoce nastupaju kod poziva funkcija, jer se nakon izvcdenja funkcije valja vratiti na prvu naredbu iza naredbe koja je pozvala funkciju. Ocito je da prilikom poziva funkcije treba pohraniti adresu naredbe na koju se procesor po povrarku iz funkcije mom vratiti Valja primijetiti da to pohranjivanje adrese nije trivijalno, buduci da se unutar pozvane funkcije moze pozvati jedna iii visc drugih funkcija, a za svaki od tih poziva treba pohraniti pri padaj uce povratne adrese.

Vjerojatno je svakorn jasno da ce prilikorn uzastopnih poziva funkcija jedne unutar druge, PI-VO biti potrebna adresa vezana uz zadnji poziv funkcije, a tek potom ostalih funkcija, redoslijedorn obrnutim od redoslijeda pozivanja funkcija. Zbog toga se povratne adrese ipohranjuju na slog - posebni dio rnernorije u kojem se podaci dohvacaju obrnutim redoslijedom od redoslijeda kojirn su tarno ostavljani. To znaci da ce podatak koji je na stogu bio ostavljen posljednji, biti dohvalljiv kao prvi, a podatak koj i j e bio ostavljen prvi bit ce doh vatlj i v posljednj i. Takva struktura se cesto oznacava kraticom LiFO, od engl. Last-In-First-Out »- posljednji unutra, prvi van. Pokazivac stoga pokazu JC na zadnj i urnetnuti pcdatak, tj na "vrh" stoga. Kada procesor u stroj nom kodu nalcti na poziv funkeije, on ce pohraniti adresu iz pokazivaea instrukcija (to je adresa naredbe na koju se izvodenjc mom vratiti po povratku iz funkcije) na adresu na koju pokazuje pokazivac stoga, te ce sadrzaj pokazivaca stoga povecati Ako se unutar funkcije pozove neka druga fun kcij a, po stupak ce se ponoviti - povratne adrese se postepeno slazu II stog, a pokazivac stoga se pritorn uvecava Pri povratku iz zadnje funkcije, preko pokazivaca stoga se ocita posljednja povratna adresa, te se pokazivac srnanj i. Postupak sc ponavlja pri svakom povratku iz funkcij a u nizu, te se stog "prazni",

5 FUnkcije

Buduci da su podaci na stogu dohvarljivi iskljucivo preko pokazivaca stoga, treba voditi racuna 0 redoslijedu punjenja i praznjenja stoga. Srecorn, sve operacije vezane uz stog sreduje prevoditelj, oslobadajuci programers od prizemnog posla. Medutim, ono 0 cemu autor program a mora voditi racuna Jest da se taj stog ne prepuni, jer ce to i7-'1 zvari prekid rada programa, Na prvi pogled programer nerna ni tu velikog utjeeaja buduci da velicinu stoga odreduje prevoditelj. Istina, svaki prevcditelj ima mogucnost da se veliciua sroga za neki program poveca, ali to nije univerzalni lijek. Ipak, iz gomjih razmatranja je jasno da mnogobrojni uzastopni pozivi funkcija opterecuju stag. To se posebno odnosi na glomazne rekurzije, gdje broj poziva funkeije moze postau vrlo velik, Uz to valja znati da se osim povratnih vrijednosti, na stog pohranjuju i argument: funkcija (to je uostalorn i razlogom zasto se polja ne prenose po vrijednosti) i lokalni objekti. Ako su objekti koji se prenose funkcijarna veliki, stog ce vrlo brzo biti ispunjen do vrha.

You're Reading a Free Preview

Download
scribd
/*********** DO NOT ALTER ANYTHING BELOW THIS LINE ! ************/ var s_code=s.t();if(s_code)document.write(s_code)//-->