You are on page 1of 17

Željko Jurić: Principi programiranja /kroz programski jezik C++/

17. Reference i prenos parametara po referenci

Mehanizam prenošenja parametara u funkcije koji smo do sada upoznali naziva se prenos
parametara po vrijednosti (engl. passing by value). Ovaj mehanizam omogućava prenošenje vrijednosti
sa mjesta poziva funkcije u samu funkciju. Međutim, pri tome ne postoji nikakav način da funkcija
promijeni vrijednost nekog od stvarnih parametara koji je korišten u pozivu funkcije, s obzirom da
funkcija manipulira samo sa formalnim parametria koji su posve neovisni objekti od stvarnih parametara
(mada su inicijalizirani tako da im na početku izvršavanja funkcije vrijednosti budu jednake vrijednosti
stvarnih parametara). Slijedi da se putem ovakvog prenosa parametara ne može prenijeti nikakva
informacija iz funkcije nazad na mjesto poziva funkcije. Informacija se doduše može prenijeti iz funkcije
na mjesto poziva putem povratne vrijednosti, ali postoje situacije gdje to nije dovoljno.

Ograničenja prenosa po vrijednosti lako možemo vidjeti iz sljedećeg razmatranja. Zamislimo da


želimo da definiramo funkciju “Povecaj” koja uvećava vrijednost cjelobrojne promjenljive koja joj je
prosljeđena kao parametar, tako da sljedeća sekvenca naredbi

int a = 5;
Povecaj(a);
cout << a;

ispiše broj “6”. Postavljeni problem je zapravo principijelne prirode, jer je jasno da nam za nešto ovakvo
uopće nije potrebna nikakva funkcija (dovoljno je samo izvršiti izraz “a++”). Međutim, činjenica je da
ovakvu funkciju nije moguće napisati koristeći samo do sada izložene koncepte. Naime, ako napišemo
nešto poput

void Povecaj(int x) {
x++;
}

nismo postigli ništa korisno. Naime, vrijednost promjenljive “a” će prilikom poziva funkcije “Povecaj”
biti prenesena u formalni parametar “x”, koji će nakon toga zaista biti povećan za 1, ali se to neće
odraziti na sadržaj promjenljive “a”. Naime, formalni parametar “x” je posve neovisan objekat od
promjenljive “a”, koji pored toga biva uništen prilikom završetka funkcije, odnosno prilikom povratka iz
funkcije na mjesto poziva. Naravno, ne bi ništa pomoglo ni da formalni parametar ima isto ime kao
stvarni parametar, s obzirom da su formalni i stvarni parametri uvijek različiti objekti, bez obzira na to
kako se zovu. Ni sljedeća definicija ne nudi mnogo veću korist:

int Povecaj(int x) {
return x + 1;
}

Ovako definiranu funkciju bismo doduše mogli iskoristiti za postizanje željenog efekta (povećanje
promjenljive “a”) upotrebom konstrukcije poput

a = Povecaj(a);

ali to nije forma poziva kakvu smo tražili. Naime, nama je potreban mehanizam koji omogućava da
funkcija promijeni svoj stvarni parametar.

Izloženi problem se rješava upotrebom tzv. referenci (ili upućivača, kako se ovaj termin ponekad
prevodi). Reference su specijalni objekti koje je najlakše zamisliti kao alternativna imena (engl. alias
names) za druge objekte. Reference se deklariraju kao i obične promjenljive, samo se prilikom
deklaracije ispred njihovog imena stavlja oznaka “&”. Reference se obavezno moraju inicijalizirati
prilikom deklaracije, bilo upotrebom znaka “=”, bilo upotrebom konstruktorske sintakse (navođenjem

XVII – 1
Željko Jurić: Principi programiranja /kroz programski jezik C++/

inicijalizatora unutar zagrada). Međutim, za razliku od običnih promjenljivih, reference se ne mogu


inicijalizirati proizvoljnim izrazom, već samo nekom drugom promjenljivom potpuno istog tipa (dakle,
konverzije tipova poput konverzije iz tipa “int” u tip “double” nisu dozvoljene) ili, općenitije, nekom
l-vrijednošću istog tipa (mada su, za sada, promjenljive i individualni elementi niza jedine l-vrijednosti
koje poznajemo). Pri tome, referenca postaje vezana (engl. tied) za promjenljivu (odnosno l-vrijednost)
kojom je inicijalizirana u smislu koji ćemo uskoro razjasniti. Na primjer, ukoliko je “a” neka cjelobrojna
promjenljiva, referencu “b” vezanu za promjenljivu “a” možemo deklarirati na jedan od sljedeća dva
ekvivalentna načina:

int &b = a;
int &b(a);

Kada bi “b” bila obična promjenljiva a ne referenca, efekat ovakve deklaracije bio bi da bi se
promjenljiva “b” inicijalizirala trenutnom vrijednošću promjenljive “a”, nakon čega bi se ponašala kao
posve neovisan objekat od promjenljive “a”, odnosno eventualna promjena sadržaja promjenljive “b” ni
na kakav način ne bi uticala na promjenljivu “a”. Međutim, reference se potpuno poistovjećuju sa
objektom na koji su vezane. Drugim riječima, “b” se ponaša kao alternativno ime za promjenljivu “a”,
odnosno svaka manipulacija sa objektom “b” odražava se na identičan način na objekat “a” (kaže se da
je “b” referenca na promjenljivu “a”). To možemo vidjeti iz sljedećeg primjera:

b = 7;
cout << a;

Ovaj primjer će ispisati broj “7”, bez obzira na eventualni prethodni sadržaj promjenljive “a”, odnosno
dodjela vrijednosti “7” promjenljivoj “b” zapravo je promijenila sadržaj promjenljive “a”. Objekti “a” i
“b” ponašaju se kao da se radi o istom objektu! Potrebno je napomenuti da nakon što se referenca
jednom veže za neki objekat, ne postoji način da se ona preusmjeri na neki drugi objekat. Zaista, ukoliko
je npr. “c” također neka cjelobrojna promjenljiva, tada naredba dodjele poput

b = c;

neće preusmjeriti referencu “b” na promjenljivu “c”, nego će promjenljivoj “a” dodijeliti vrijednost
promjenljive “c”, s obzirom da je referenca “b” i dalje vezana za promjenljivu “a”. Svaka referenca
čitavo vrijeme svog života uvijek upućuje na objekat za koji je vezana prilikom inicijalizacije. Kako
referenca uvijek mora biti vezana za neki objekat, to deklaracija poput

int &b;

nema nikakvog smisla.

S obzirom da se reference uvijek vežu za konkretan objekat, i ponašaju se kao alternativno ime za
drugi objekat, postavlja se pitanje da li su one uopće posebni objekti, ili predstavljaju isti objekat kao i
objekat na koji su vezani. Da bi se u potpunosti shvatio mehanizam prenosa parametara putem referenci,
koji ćemo uskoro objasniti, moramo reći da je, tehnički gledano, odgovor potvrdan. Reference jesu
posebni objekti, koji zauzimaju mjesto u memoriji neovisno od objekta na koji su vezani. Reference
zapravo u sebi sadrže informaciju o tome gdje se u memoriji nalazi objekat za koje su vezane (tj. adresu
objekta za koji su vezane), tako da svaki pristup referenci koristi ovu pohranjenu informaciju da
indirektno pristupi objektu na koji referenca upućuje. U tom smislu, reference su slične tzv.
pokazivačima, sa kojima ćemo se upoznati kasnije. Međutim, za razliku od pokazivača, koji se ponašaju
bitno drugačije od objekata na koji upućuju, reference se ponašaju istovjetno kao i objekat sa kojim su
vezani. Drugim riječima, ne postoji nikakav način kojim bi program mogao utvrditi da se referenca i po
čemu razlikuje od objekta za koji je vezana, odnosno da ona nije objekat za koji je vezana. Čak i neke od
najdelikatnijih operacija koje bi se mogle primijeniti na referencu obaviće se nad objektom za koji je
referenca vezana. Na primjer, operator “sizeof” primijenjen na referencu neće vratiti kao rezultat
veličinu same reference, nego veličinu objekta na koji referenca upućuje. Dakle, referenca i objekat za

XVII – 2
Željko Jurić: Principi programiranja /kroz programski jezik C++/

koji je referenca vezana tehnički posmatrano nisu isti objekat, ali program nema način da to sazna. Sa
aspekta izvršavanja programa, referenca i objekat za koji je ona vezana predstavljaju potpuno isti
objekat! Ipak, potrebno je naglasiti da tip nekog objekta i tip reference na taj objekat nisu isti. Dok je, u
prethodnom primjeru, promjenljiva “a” tipa cijeli broj (tj. tipa “int”) dotle je promjenljiva “b” tipa
referenca na cijeli broj (ovo je tip bez posebnog imena, koji se često obilježava kao “int &”). Tip
reference je moguće posebno imenovati “typedef” naredbom. Na primjer, sljedeća sekvenca naredbi
prvo uvodi tip “Referenca” koji predstavlja referencu na cijeli broj, a zatim novouvedeni tip koristi za
deklaraciju reference “b” koja upućuje na promjenljivu “a”:

typedef int &Referenca;


Referenca b = a;

Početnik se ovom razlikom u tipu između reference i objekta na koji referenca upućuje ne treba da
zamara, s obzirom da je ona uglavnom nebitna, osim u nekim vrlo specifičnim slučajevima, na koje
ćemo ukazati onog trenutka kada se pojave. U suštini, nije loše znati da ova razlika postoji, s obzirom da
je u jeziku C++ pojam tipa izuzetno važan. Bez obzira na ovu razliku u tipu, čak ni ona se ne može
iskoristiti da program sazna da neka promjenljiva predstavlja referencu na neki objekat, a ne sam
objekat. Reference u jeziku C++ su zaista izuzetno dobro “zamaskirane”.

Mada reference na prvi pogled izgledaju dosta čudno, one u jeziku C++ imaju mnogobrojne
primjene. Jedna od najočiglednijih primjena je tzv. prenos parametara po referenci (engl. passing by
reference) koji omogućava rješenje problema postavljenog na početku ovog poglavlja. Naime, da bismo
postigli da funkcija promijeni vrijednost svog stvarnog parametra, dovoljno je da odgovarajući formalni
parametar bude referenca. Na primjer, funkciju “Povecaj” trebalo bi modificirati na sljedeći način:

void Povecaj(int &x) {


x++;
}

Da bismo vidjeli šta se ovdje zapravo dešava, pretpostavimo da je ova funkcija pozvana na sljedeći
način (“a” je neka cjelobrojna promjenljiva):

Povecaj(a);

Prilikom ovog poziva, formalni parametar “x” koji je referenca biće inicijaliziran stvarnim parametrom
“a”. Međutim, prilikom inicijalizacije referenci, one se vezuju za objekat kojim su inicijalizirane, tako da
se formalni parametar “x” vezuje za promjenljivu “a”. Stoga će se svaka promjena sadržaja formalnog
parametra “x” zapravo odraziti na promjenu stvarnog parametra “a”, odnosno za vrijeme izvršavanja
funkcije “Povecaj”, promjenljiva “x” se ponaša kao da ona u stvari jeste promjenljiva “a”. Ovdje je
iskorištena osobina referenci da se one ponašaju tako kao da su one upravo objekat za koji su vezane. Po
završetku funkcije, referenca “x” se uništava, kao i svaka druga lokalna promjenljiva na kraju bloka
kojem pripada. Međutim, za vrijeme njenog života, ona je iskorištena da promijeni sadržaj stvarnog
parametra “a”, koji razumljivo ostaje takav i nakon što referenca “x” prestane “živjeti”!

U slučaju kada je neki formalni parametar referenca, odgovarajući stvarni parametar mora biti
l-vrijednost (tipično neka promjenljiva), jer se reference mogu vezati samo za l-vrijednosti. Drugim
riječima, odgovarajući parametar ne smije biti broj, ili proizvoljan izraz. Stoga, pozivi poput sljedećih
nisu dozvoljeni:

Povecaj(7);
Povecaj(2 * a - 3);

U suštini, ako malo bolje razmislimo, vidjećemo da ovakvi pozivi zapravo i nemaju smisla. Funkcija
“Povecaj” je dizajnirana sa ciljem da promijeni vrijednost svog stvarnog argumenta, što je nemoguće
ukoliko je on, na primjer, broj. Broj ima svoju vrijednost koju nije moguće mijenjati!

XVII – 3
Željko Jurić: Principi programiranja /kroz programski jezik C++/

Kako su individualni elementi niza također l-vrijednosti, kao stvarni parametar koji se prenosi po
referenci može se upotrijebiti i individualni element niza. Tako, na primjer, ukoliko imamo deklaraciju
int niz[10];

tada se sljedeći poziv sasvim legalno može iskoristiti za uvećavanje elementa niza sa indeksom 2:
Povecaj(niz[2]);

Ovo je posljedica činjenice da se referenca može vezati za bilo koju l-vrijednost, pa tako i za
individualni element niza. Drugim riječima, deklaracija
int &element = niz[2];

sasvim je legalna, i nakon nje ime “element” postaje alternativno ime za element niza “niz” sa
indeksom 2. Drugim riječima, naredba
element = 5;

imaće isti efekat kao i naredba


niz[2] = 5;

Moguće je čak definirati i reference na čitav niz, ali se ova mogućnost prilično rijetko koristi. Na
primjer, deklaracija
int (&klon)[10] = niz;

deklarira referencu “klon” koja je vezana za niz “niz” (od 10 cijelih brojeva), i koja se, prema tome,
može koristiti kao njegovo alternativno ime (tj. kao alternativno ime čitavog niza, a ne nekog njegovog
individualnog elementa). Primijetimo pomalo čudnu sintaksu u kojoj se koriste i obične zagrade.
Ukoliko bismo izostavili ove zagrade, smatralo bi se da želimo deklarirati niz od 10 referenci na cijele
brojeve (a ne referencu na niz od 10 cijelih brojeva). Ovo bi dovelo do prijave greške, jer jezik C++ ne
dozvoljava kreiranje nizova referenci. Ukoliko bi ovo bilo moguće, postojao bi trik koji bi omogućavao
preusmjeravanje referenci (zasnovan na činjenici da se imena nizova upotrijebljena sama za sebe
konvertiraju u adresu prvog elementa niza), a tvorci jezika C++ su željeli da takvu mogućnost spriječe
po svaku cijenu.

Treba naglasiti da su reference u jeziku C++ mnogo fleksibilnije nego u većini drugih programskih
jezika. Na primjer, u jeziku Pascal samo formalni parametri mogu biti reference, odnosno referenca kao
pojam ne postoji izvan konteksta formalnih parametara, tako da se u Pascalu pojam reference (kao
neovisnog objekta) uopće ne uvode, nego se samo govori o prenosu po referenci. S druge strane, u jeziku
C++ referenca može postojati kao objekat posve neovisan o formalnim parametrima, a prenos po
referenci se prosto ostvaruje tako što se odgovarajući formalni parametar deklarira kao referenca.

Sljedeći primjer ilustrira realističniju situaciju u kojoj se javlja potreba za prenosom parametara po
referenci nego što je bilo demonstrirano u trivijalnoj funkciji “Povecaj”. Zamislimo da želimo da
napravimo funkciju “UnesiMjesec” koja sa tastature prihvata redni broj mjeseca u opsegu od 1 do 12,
pri čemu ponavlja unos sve dok korisnik ne unese broj u ispravnom opsegu, a zatim smješta unesenu
vrijednost u promjenljivu koja je navedena kao argument. Na primjer, ako je izvršen poziv poput
UnesiMjesec(mjesec);

i ako korisnik unese vrijednost 4, vrijednost promjenljive “mjesec” treba da postane 4. Jasno je da
moramo koristiti prenos po referenci, jer funkcija “UnesiMjesec” treba da promijeni vrijednost
promjenljive “mjesec”. Funkcija “UnesiMjesec” mogla bi izgledati na primjer ovako (podsjetimo se
da konstrukcija “for(;;)”, slično kao i “while(true)” ili “while(1)” označava beskonačnu petlju
iz koje se može izaći samo nasilnim putem):

XVII – 4
Željko Jurić: Principi programiranja /kroz programski jezik C++/

void UnesiMjesec(int &m) {


cout << "Unesi mjesec u opsegu od 1 do 12: ";
for(;;) {
cin >> m;
if(cin && m >= 1 && m <= 12) return;
cout << "Neispravan unos!\n";
if(!cin) cin.clear();
cin.ignore(10000, '\n');
}
}

Šta bi se dogodilo da smo zaboravili deklarirati formalni parametar “m” kao referencu? U tom
slučaju bi formalni argument “m” i stvarni argument “mjesec” prilikom poziva funkcije
“UnesiMjesec” predstavljali striktno različite objekte. Vrijednost promjenljive “mjesec” bila bi,
naravno, prenesena u parametar “m” (u ovom trenutku je potpuno nebitno kakva je ta vrijednost, niti da li
je ta promjenljiva uopće imala neku konkretno definiranu vrijednost). Nakon toga, sve manipulacije
unutar funkcije sa promjenljivom “m” (uključujući i unos sa tastature) ne bi imale nikakav utjecaj na
promjenljivu “mjesec”. Po završetku funkcije “UnesiMjesec” formalni parametar “m” bio bi uništen,
čime bi vrijednost koju smo unijeli sa tastature bila izgubljena, a vrijednost promjenljive “mjesec” bi na
kraju bila ista kao što je bila i prije poziva funkcije!

Prema načinu na koji se koriste, odnosno prema smjeru toka informacija koji se putem njih prenose,
parametre možemo podijeliti na ulazne, izlazne i ulazno-izlazne. Parametri koji se prenose po vrijednosti
su uvijek ulazni, s obzirom da putem njih informacija može samo ući u funkciju, ali ne može iz nje izaći.
Parametri koji se prenose po referenci u načelu također mogu biti striktno ulazni, ali se oni mnogo češće
koriste kao izlazni odnosno ulazno-izlazni. Na primjer, parametar funkcije “UnesiMjesec” je izlazni
parametar. Putem njega informacija o unesenom mjesecu izlazi iz funkcije, i smješta se u stvarni
argument koji je upotrijebljen u pozivu funkcije. Pri tome je za funkciju posve nebitno kakva je bila
vrijednost stvarnog argumenta prilikom poziva funkcije (ona će svakako biti prebrisana). Stoga je
parametar ove funkcije striktno izlazni, s obzirom da funkcija putem njega ne prima nikakvu informaciju
od onoga ko je poziva (bolje rečeno, prima ali je ne koristi). S druge strane, parametar funkcije
“Povecaj” je tipičan primjer ulazno-izlaznog parametra. Preko njega funkcija dobija informaciju o
vrijednosti stvarnog parametra, izmijeni ovu vrijednost, i vrati izmijenjenu vrijednost nazad u stvarni
parametar. Drugim riječima, protok informacija se odvija u oba smjera.

Razmotrimo sada malo detaljnije opisane mehanizme prenosa parametara. Kod prenosa parametara
po vrijednosti, vrijednosti stvarnih parametara se kopiraju u formalne parametre. U ovom slučaju,
funkcija samo dobija informaciju o vrijednosti nekog stvarnog parametra, ali nema nikakvu informaciju
o tome gdje se on nalazi u memoriji, pa ga ne može ni promijeniti. S druge strane, kod prenosa po
referenci, koji se u jeziku C++ realizira tako što se formalni parametar deklarira kao referenca, u
funkciju se prenosi informacija o mjestu u memoriji računara (adresi) gdje se odgovarajući stvarni
parametar čuva. Referenca na taj način saznaje gdje se nalazi objekat koji treba da predstavlja, i može
preuzeti potpunu kontrolu nad njim. Nekada se ovaj način u malo slobodnijoj interpretaciji naziva i
prenos po imenu (engl. passing by name) s obzirom da, na izvjestan način, formalni parametar pri pozivu
funkcije saznaje ime objekta s kojim treba da se poistovijeti. U suštini, kod prenosa po vrijednosti,
formalni i stvarni parametar uvijek predstavljaju različite objekte čak i kada imaju isto ime. S druge
strane, možemo smatrati da kod prenosa po referenci formalni i stvarni parametar predstavljaju iste
objekte čak i kada imaju različito ime (ova tvrdnja je tačna sa aspekta ponašanja mada, kao što smo već
opisali, sa tehničkog aspekta nije posve precizna). Na ovu činjenicu (koja u početku može djelovati
zbunjujuće) treba dobro obratiti pažnju, jer je ona jedan od najčešćih uzročnika grešaka u programima
koje koriste funkcije!

Neki početnici često pogrešno shvataju da kod parametara koji se prenose po referenci dolazi do
dvostrukog kopiranja, odnosno da se prilikom poziva funkcije vrijednosti stvarnih parametara kopiraju u
formalne parametre, a da se na završetku funkcije vrijednosti formalnih parametara kopiraju nazad u
stvarne parametre. Mada sa aspekta funkcioniranja izgleda da je tako, ovo nije ono što se zaista dešava,

XVII – 5
Željko Jurić: Principi programiranja /kroz programski jezik C++/

nego se formalni i stvarni parametri poistovjećuju i ni do kakvog kopiranja uopće ne dolazi (već samo do
prenosa adrese). Kopiranje parametara može biti vremenski zahtjevna operacija u slučaju kada su
parametri masivni objekti (tj. objekti koji zauzimaju puno memorijskog prostora) sa kakvim ćemo se
susretati kasnije. Stoga je sa aspekta efikasnosti važno znati da kod prenosa po referenci ne dolazi ni do
kakvog kopiranja parametara, a pogotovo ne do dvostrukog kopiranja.

Funkcije koje ne vraćaju vrijednost, a kod kojih se jedan parametar prenosi po referenci nisu
pretjerano korisne, s obzirom da se načelno isti efekat, samo uz drugačiji način pozivanja, može ostvariti
i korištenjem funkcija koje vraćaju vrijednost. Tako smo, na primjer, funkciju “UnesiMjesec” mogli
realizirati kao funkciju bez parametara a koja vraća kao rezultat uneseni mjesec:
int UnesiMjesec() {
cout << "Unesi mjesec u opsegu od 1 do 12: ";
for(;;) {
int m;
cin >> m;
if(cin && m >= 1 && m <= 12) return m;
cout << "Neispravan unos!\n";
if(!cin) cin.clear();
cin.ignore(10000, '\n');
}
}

Naravno, ovakvu funkciju bismo morali pozivati na nešto drugačiji način, pozivom poput
mjesec = UnesiMjesec();

U ovom slučaju, funkcija ne smješta traženu vrijednost u svoj parametar, nego je jednostavno vraća kao
povratnu vrijednost, koju prostom dodjelom smještamo u željenu promjenljivu “mjesec”. Drugim
riječima, imamo način da postignemo isti efekat, samo uz neznatno izmijenjenu sintaksu. Čak se smatra i
da je ovaj drugi način (tj. korištenje povratne vrijednosti) više “u duhu” jezika C++. Međutim, prenos po
referenci dolazi do punog izražaja kada je potrebno prenijeti više od jedne vrijednosti iz funkcije nazad
na mjesto njenog poziva. Pošto funkcija ne može vratiti kao rezultat više vrijednosti, kao rezultat se
nameće korištenje izlaznih parametara putem prenosa po referenci, pri čemu će funkcija koristeći
reference prosto smjestiti tražene vrijednosti u odgovarajuće stvarne parametre koji su joj proslijeđeni.
Ova tehnika je ilustrirana u sljedećoj verziji programa za proračun obima i površine kruga, u kojoj
funkcija “ProracunajKrug” smješta izračunate vrijednosti obima i površine kruga sa poluprečnikom
koji joj je proslijeđen kao prvi parametar u promjenljive koje su proslijeđene kao drugi i treći parametar.
Ovo smještanje se ostvaruje tako što su drugi i treći formalni parametar ove funkcije (“O” i “P”)
deklarirani kao reference, koje se za vrijeme izvršavanja funkcije vezuju za odgovarajuće stvarne
argumente (usput, nije posebno mudro promjenljivu nazvati “O”, s obzirom da se slovo “O” lako može
pobrkati sa oznakom “0” koja predstavlja nulu):
#include <iostream>
using namespace std;
void ProracunajKrug(double r, double &O, double &P) {
const double PI(3.141592654);
O = 2 * PI * r;
P = PI * r * r;
}
int main() {
double poluprecnik, obim, povrsina;
cin >> poluprecnik;
ProracunajKrug(poluprecnik, obim, povrsina);
cout << "Obim: " << obim << endl
<< "Površina: " << povrsina << endl;
return 0;
}

XVII – 6
Željko Jurić: Principi programiranja /kroz programski jezik C++/

Sličnu situaciju imamo i u sljedećem programu u kojem je definirana funkcija “RastaviSekunde”,


čiji je prvi parametar broj sekundi, a koja kroz drugi, treći i četvrti parametar prenosi na mjesto poziva
informaciju o broju sati, minuta i sekundi koji odgovaraju zadanom broju sekundi:

#include <iostream>
using namespace std;
void RastaviSekunde(int br_sek, int &sati, int &minute, int &sekunde) {
sati = br_sek / 3600;
minute = (br_sek % 3600) / 60;
sekunde = br_sek % 60;
}
int main() {
int sek, h, m, s;
cout << "Unesi broj sekundi: ";
cin >> sek;
RastaviSekunde(sek, h, m, s);
cout << "h = " << h << " m = " << m << " s = " << s << endl;
return 0;
}

Kao što je već rečeno, stvarni parametri koji se prenosi po vrijednosti mogu bez ikakvih problema
biti konstante ili izrazi, dok stvarni parametri koji se prenose po referenci moraju biti l-vrijednosti
(obično promjenljive). Tako su uz funkciju “RastaviSekunde” iz prethodnog primjera sasvim legalni
pozivi

RastaviSekunde(73 * sek + 13, h, m, s);


RastaviSekunde(12322, h, m, s);

dok sljedeći pozivi nisu legalni, jer odgovarajući stvarni parametri nisu l-vrijednosti:

RastaviSekunde(sek, 3 * h + 2, m, s);
RastaviSekunde(sek, h, 17, s);
RastaviSekunde(1, 2, 3, 4);
RastaviSekunde(sek + 2, sek - 2, m, s);

Navedimo još jedan primjer u kojem se koriste reference kao parametri. U Britanjiji i Sjevernoj
Americi se pored metričkog sistema još uvijek koriste stare jedinice za dužinu, stope i inči (1 stopa ima
12 inča, a u jednom metru ima 1250/381 ≈ 3.280839895 stopa). Ove mjerne jedinice (kao i neke druge
podjednako nestandardne jedinice za težinu i zapreminu) poznate su pod nazivom “kraljevske” jedinice.
Ovdje je dat program u kojem je upotrijebljena funkcija koja pretvara dužinu u metrima proslijeđenu kao
prvi parametar u dužinu izraženu u stopama i inčima (zaokruženo na najbliži cijeli broj inča), pri čemu
se odgovarajući iznosi u stopama i inčima smještaju u promjenljive proslijeđene kao drugi i treći
parametar u funkciju:

#include <iostream>
using namespace std;
// Pretvara broj metara predstavljen parametrom "metri" u broj stopa
// i inča, i vraća ih kroz parametre "stope" i "inci"
void PretvoriUKraljevske(double metri, int &stope, int &inci) {
const double StopaPoMetru(1250/381);
double pomocna = metri * StopaPoMetru;
stope = int(pomocna);
inci = int((pomocna - stope) * 12);
}

XVII – 7
Željko Jurić: Principi programiranja /kroz programski jezik C++/

int main() {
double broj_metara;
int broj_stopa, broj_inca;
cout << "Unesi dužinu u metrima: ");
cin >> broj_metara;
PretvoriUKraljevske(broj_metara, broj_stopa, broj_inca);
cout << "Dužina u kraljevskim jedinicama je: "
<< broj_stopa << " stopa i " << broj_inca << " inča\n";
return 0;
}

U svim dosadašnjim primjerima (osim u trivijalnom slučaju funkcije “Povecaj”) parametri koji su
se prenosili po referenci korišteni su isključivo kao izlazni parametri, odnosno nikakva informacija se
putem njih nije prenosila sa mjesta poziva funkcije u samu funkciju. U sljedećem primjeru definirana je
interesantna funkcija “Razmijeni” koja razmjenjuje sadržaj dvije realne promjenljive (odnosno dvije
l-vrijednosti) koje joj se prosljeđuju kao parametri:

void Razmijeni(double &p, double &q) {


double pomocna = p;
p = q; q = pomocna;
}

Na primjer, ukoliko je vrijednost promjenljive “a” bila 2.13 a sadržaj promjenljive “b” 3.6, nakon
izvršenja naredbe

Razmijeni(a, b);

vrijednost promjenljive “a” će biti 3.6, a vrijednost promjenljive “a” biće 2.13. Nije teško uvidjeti kako
ova funkcija radi: njeni formalni parametri “p” i “q”, koji su reference, vežu se za navedene stvarne
parametre. Funkcija pokušava da razmijeni dvije reference, ali će se razmijena obaviti nad promjenljivim
za koje su ove reference vezane. Jasno je da se ovakav efekat može ostvariti samo putem prenosa po
referenci, jer u suprotnom funkcija “Razmijeni” ne bi mogla imati utjecaj na svoje stvarne parametre.

Kako su individualni elementi niza također l-vrijednosti, funkcija “Razmijeni” se lijepo može
iskoristiti za razmjenu dva elementa niza. Na primjer, ukoliko je “niz” neki niz realnih brojeva (sa
barem 6 elemenata), tada će naredba

Razmijeni(niz[2], niz[5]);

razmijeniti elemente niza sa indeksima 2 i 5 (tj. treći i šesti element).

Bitno je naglasiti da se kod prenosa po referenci formalni i stvarni parametri moraju u potpunosti
slagati po tipu, jer se reference mogu vezati samo za promjenljive odgovarajućeg tipa (osim u nekim
rijetkim izuzecima, sa kojima ćemo se susresti kasnije). Na primjer, ukoliko funkcija ima formalni
parametar koji je referenca na tip “double”, odgovarajući stvarni parametar ne može biti npr. tipa
“int”. Razlog za ovo ograničenje nije teško uvidjeti. Razmotrimo, na primjer, sljedeću funkciju:

void Problem(double &x) {


x = 3.25;
}

Pretpostavimo da se funkcija “Problem” može pozvati navodeći neku cjelobrojnu promjenljivu kao
stvarni argument. Formalni parametar “x” trebao bi se nakon toga vezati i poistovjetiti sa tom
promjenljivom. Međutim, funkcija smješta u “x” vrijednost koja nije cjelobrojna. U skladu sa
djelovanjem referenci, ova vrijednost trebala bi da se zapravo smjesti u promjenljivu za koju je ova
referenca vezana, što je očigledno nemoguće s obzirom da se radi o cjelobrojnoj promjenljivoj. Dakle,

XVII – 8
Željko Jurić: Principi programiranja /kroz programski jezik C++/

smisao načina na koji se reference ponašaju ne može biti ostvaren, što je ujedno i razlog zbog kojeg se
formalni i stvarni parametar u slučaju prenosa po referenci moraju u potpunosti slagati po tipu.

Posljedica činjenice da se parametri koji se prenose po referenci moraju u potpunosti slagati po tipu
je da se maloprije napisana funkcija “Razmijeni” ne može primijeniti za razmjenu dvije promjenljive
koje nisu tipa “double”, npr. dvije promjenljive tipa “int” (pa čak ni promjenljive tipa “float”), bez
obzira što sama funkcija ne radi ništa što bi suštinski trebalo ovisiti od tipa promjenljive. Kao rješenje
ovog problema možemo koristiti preklapanje funkcija po tipu. Naime, sasvim je dozvoljeno napraviti još
jednu verziju funkcije “Razmijeni” koja razmjenjuje dvije cjelobrojne promjenljive:

void Razmijeni(int &p, int &q) {


int pomocna = p;
p = q; q = pomocna;
}

U ovom slučaju, prilikom pokušaja razmjene dvije promjenljive pozivom funkcije “Razmijeni”
pozvaće se odgovarajuća verzija ovisno o tome da li su promjenljive tipa “double” ili “int”. Međutim,
razmjena opet neće raditi za neki treći tip (npr. “char”, “float”, “bool” ili neki pobrojani tip).
Naravno, principijelno je moguće napraviti verziju funkcije “Razmijeni” za svaki tip za koji nam treba
razmjena, ali ovo dovodi do potrebe za pisanjem velikog broja gotovo identičnih funkcija istih imena.
Kasnije ćemo vidjeti kako se ovaj problem može elegantnije riješiti upotrebom tzv. šablona i generičkih
funkcija.

Na ovom mjestu nije loše ukazati na jednu činjenicu koja često može zbuniti početnika. Razmotrimo
šta će ispisati sljedeći program:

#include <iostream>
using namespace std;
void Potprogram(int &a, int &b) {
cout << a << b;
a += 3; b *= 2;
cout << a << b;
}
int main() {
int a(2), b(5);
cout << a << b;
potprogram(b, a);
cout << a << b;
return 0;
}

Da su za imenovanje formalnih parametara upotrijebljena neka druga slova a ne “a” i “b” (npr. “p” i
“q”), vjerovatno biste bez ikakvih problema odgovorili da će biti ispisana sekvenca “25528448”.
Naravno, i u ovom slučaju biće ispisana ista sekvenca, s obzirom da rad programa ne može ovisiti od
toga kako smo imenovali formalne parametre. Međutim, ukoliko probate neposredno pratiti tok ovog
programa ovako kako je napisan, veoma se lako možete “zapetljati”. Naime, pri pozivu funkcije
“Potprogram”, njen formalni parametar sa imenom “a” (koji je referenca) vezuje se za promjenljivu
“b” iz glavnog programa, dok se formalni parametar sa imenom “b” vezuje za promjenljivu “a” iz
glavnog programa. Stoga, sve ono što se obavlja sa promjenljivom (referencom) “a” u potprogramu,
zapravo se obavlja sa promjenljivom “b” u glavnom programu, a sve što se obavlja sa promjenljivom
“b” u potprogramu djeluje na promjenljivu “a” u glavnom programu. Izgleda kao da su u potprogramu
promjenljive “a” i “b” razmijenile svoja imena u odnosu na glavni program! Ovakve zavrzlame ne
dešavaju se često u praktičnim situacijama, ali je potrebno da shvatite šta se ovdje tačno dešava da biste
u potpunosti ovladali mehanizmom prenosa parametara.

XVII – 9
Željko Jurić: Principi programiranja /kroz programski jezik C++/

Znanja koja smo do sada stekli o prenosu parametara iskoristićemo da učinimo modularnim program
koji računa dan u sedmici za proizvoljan datum u okviru 2000. godine, koji je ranije napisan na
nemodularan način:

#include <iostream>
using namespace std;
const int mjeseci[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int main() {
void UnesiDatum(int &, int &); // Prototipovi korištenih funkcija
int BrojDanaDoZadanog(int, int);
void IspisiDan(int);
int dan, mjesec;
UnesiMjesec(dan, mjesec);
IspisiDan(BrojDanaDoZadanog(dan, mjesec) % 7);
return 0;
}
// Unosi datum, uz provjeru ispravnosti unosa
void UnesiDatum(int &dan, int &mjesec) {
for(;;) {
cout << "Unesite datum u obliku DD MM: ";
cin >> dan >> mjesec;
if(cin && mjesec >= 1 && mjesec <= 12
&& dan < 1 && dan > mjeseci[mjesec]) return;
if(!cin) cin.clear();
cout << "Unijeli ste besmislen datum!\n";
cin.ignore(10000, '\n');
}
}
// Određuje broj dana od 1. I 2000. do zadanog datuma
int BrojDanaDoZadanog(int dan, int mjesec) {
int suma(0);
for(int i = 0; i < mjesec - 1; i++) suma += mjeseci[i];
return suma + dan - 1;
}
// Ispisuje ime dana prema ostatku koji je proslijeđen kao parametar
void IspisiDan(int ostatak) {
switch(ostatak) {
case 0: cout << "Subota"; break;
case 1: cout << "Nedjelja"; break;
case 2: cout << "Ponedjeljak"; break;
case 3: cout << "Utorak"; break;
case 4: cout << "Srijeda"; break;
case 5: cout << "Četvrtak"; break;
case 6: cout << "Petak";
}
}

Obratimo pažnju na prototip potprograma “UnesiDatum”. U njemu su izostavljena imena formalnih


parametara, ali se oznaka za referencu ne smije izostaviti. Formalni parametri ovog potprograma nisu
tipa “int”, nego su tipa reference na “int” (odnosno “int &”), što mora biti jasno naznačeno, bez
obzira što su imena parametara izostavljena. Također, primijetimo da smo niz “mjeseci” deklarirali kao
globalni, s obzirom da nam je njegov sadržaj potreban kako u potprogramu “UnesiDatum”, tako i u
potprogramu “BrojDanaDoZadanog”. Kao alternativa bi se ovaj niz također mogao prenositi kao
parametar u potprograme (prenos nizova kao parametara biće objašnjen u sljedećem poglavlju), mada je
ovakvo rješenje sasvim zadovoljavajuće, jer potprogrami samo čitaju sadržaj ovog niza, bez mijenjanja

XVII – 10
Željko Jurić: Principi programiranja /kroz programski jezik C++/

njegovog sadržaja, čime je izbjegnuto nehotično stvaranje zavisnosti između potprograma putem
mijenjanja sadržaja niza koji oba zajednički koriste. Pomoću ključne riječi “const” jasno je istaknuta
namjera da sadržaj niza “mjeseci” neće i ne smije biti promijenjen. Kao što je već istaknuto, konstantni
objekti tipično nisu problematični čak i ukoliko im je vidljivost globalna. Ipak, bez velikog razloga ne
treba koristiti čak ni konstantne objekte sa globalnom vidljivošću, s obzirom da dobar dizajn programa
nalaže da oni dijelovi programa koji ne treba da koriste neki resurs ne treba ni da ga vide. Na taj način se
dizajn održava “čistijim” i manje je podložan greškama.

Funkcije koje koriste prenos parametara po referenci su najčešće funkcije koje ne vraćaju vrijednost,
iako ne postoji pravilo koje bi nalagalo da mora biti tako. Naime, ukoliko bi funkcija istovremeno
koristila prenos po referenci i imala povratnu vrijednost, tada bi informacije iz funkcije “izlazile” na dva
suštinski različita načina, što može stvoriti konfuziju. Na primjer, funkcija

int MinIMax(int a, int b, int &max) {


if(a > b) {
max = a; return b;
}
else {
max = b; return a;
}
}

kao rezultat vraća manji od svoja prva dva parametra, a smješta u treći parametar veći od svoja prva dva
parametra. Ovo je svakako konfuzno, tako da je bolje izbjeći povratnu vrijednost, i umjesto toga uvesti i
četvrti parametar, tako da funkcija smješta kako minimum tako i maksimum u parametre proslijeđene po
referenci, kao u sljedećoj funkciji:

void NadjiMinIMax(int a, int b, int &min, int &max) {


if(a > b) {
max = a; min = b;
}
else {
max = b; min = a;
}
}

Još jedan razlog zbog kojeg treba izbjegavati funkcije koje vraćaju vrijednost i koriste prenos po
referenci je činjenica da takve funkcije istovremeno mogu biti upotrijebljene unutar izraza i mijenjati
vrijednosti svojih stvarnih argumenata, čime se zapravo ostvaruje bočni efekat nad argumentima.
Pogledajmo, na primjer, gore napisanu funkciju “MinIMax”. Ona se, u principu, može pozvati na
sljedeći način (uz pretpostavku da su “x” i “y” cjelobrojne promjenljive):

x = MinIMax(3, 5, y) + 5;

Međutim, iz ovog poziva nimalo nije očigledno da će on dovesti do promjene sadržaja promjenljive “y”.
Što je još gore, moguće je kreirati izraze nedefiniranog dejstva, poput sljedećeg:

x = MinIMax(3, 5, y) * y;

Naime, ovdje nije definirano da li će se kao drugi operand operacije množenja upotrijebiti vrijednost
promjenljive “y” prije izmjene ili poslije izmjene (tj. vrijednost po izlasku i funkcije “MinIMax”), jer
standardom nije definiran redoslijed izračunavanja operanada (tj. da li će prvo biti izračunat lijevi ili
desni operand operacije množenja). Ovdje zapravo imamo upotrebu promjenljive “y”, nad kojom je
obavljen bočni efekat, na dva mjesta u izrazu što, kao što već znamo, vodi ka nedefiniranom ponašanju.
Kao još jedan primjer, posmatrajmo sljedeći logički izraz (uvjet):

x >= 0 && MinIMax(a, b, y) > 3

XVII – 11
Željko Jurić: Principi programiranja /kroz programski jezik C++/

Programer koji bi se oslonio na to da će, zbog poziva funkcije “MinIMax” promjenljiva “y” sigurno
dobiti vrijednost veće od promjenljivih “a” i “b” došao bi u gadnu zabludu, zbog specifičnosti operatora
“&&”. Naime, ukoliko je vrijednost promjenljive “x” manja od nule, drugi operand operatora “&&”
uopće se ne izračunava, tako da funkcija “MinIMax” uopće neće biti pozvana. Izloženi primjeri ukazuju
na to da funkcije koje istovremeno vraćaju vrijednost i koriste prenos parametara po referenci treba
izbjegavati, a ukoliko ih već definiramo, treba ih koristiti sa izuzetnim oprezom.

Jedan od slučaja u kojem se može smatrati opravdanim definirati funkciju koja vraća vrijednost i
koristi prenos po referenci je slučaj u kojem funkcija kao rezultat vraća neku statusnu informaciju o
obavljenom poslu, koji može uključivati i smještanje informacija u parametre prenesene po referenci. Na
primjer, u sljedećem programu definirana je funkcija “KvJed”, koja je principijelno dosta slična funkciji
“KvadratnaJednacina” iz jednog od ranijih poglavlja, samo što ne vrši nikakav ispis na ekran
(prikladnije ime ove funkcije bilo bi “NadjiRjesenjaKvadratneJednacine”, koje ovdje nećemo
koristiti radi dužine). Ova funkcija nalazi rješenja kvadratne jednačine čiji se koeficijenti zadaju preko
prva tri parametra. Za slučaj da su rješenja realna, funkcija ih prenosi nazad na mjesto poziva funkcije
kroz četvrti i peti parametar i vraća vrijednost “true” kao rezultat. Za slučaj kada rješenja nisu realna,
funkcija vraća “false” kao rezultat, a četvrti i peti parametar ostaju netaknuti. Obratite pažnju na način
kako je ova funkcija upotrijebljena u glavnom programu:

#include <iostream>
using namespace std;
bool KvJed(double a, double b, double c, double &x1, double &x2) {
double d = b * b - 4 * a * c;
if(d < 0) return false;
x1 = (-b - sqrt (d)) / (2*a);
x2 = (-b + sqrt (d)) / (2*a);
return true;
}
int main() {
double a, b, c;
cout << "a = ";
cin >> a;
cout << "b = ";
cin >> b;
cout << "c = ";
cin >> c;
double x1, x2;
if(KvJed(a, b, c, x1, x2))
cout << "x1 = " << x1 << "\nx2 = " << x2;
else cout << "Jednačina nema realnih rješenja!";
return 0;
}

Iz svega što je do sada rečeno može se zaključiti da izlazne i ulazno-izlazne parametre treba koristiti
sa oprezom, i samo onda kada je zaista neophodno. Pošto se iz načina pozivanja funkcije ne može
zaključiti da će funkcija eventualno izmijeniti svoj stvarni argument, treba to učiniti jasnim davanjem
odgovarajućeg imena funkciji, tako da ta činjenica bude očigledna (npr. davanjem naziva poput
“UnesiMjesec”, ili “NadjiMinIMax” umjesto samo “MinIMax”). Posebno su problematični čisto
izlazni parametri, jer se njihovom upotrebom ne može ostvariti preporuka da svaka promjenljiva treba po
mogućnosti biti smisleno inicijalizirana odmah pri deklaraciji. Naime, pri korištenju izlaznih parametara,
stvarni argumenti dobijaju smislene vrijednosti tek nakon poziva funkcije.

Sljedeći primjer ilustrira upotrebu pobrojanih tipova kao parametara. Neka je definiran pobrojani tip
“Boje” koji definira četiri pobrojane konstante: “Crvena”, “Plava”, “Zelena” i “Bijela”. Funkcija
“IspisiBoju” ima jedan parametar tipa “Boje” i ona ispisuje naziv odgovarajuće boju na ekranu. Na
primjer, poziv funkcije

XVII – 12
Željko Jurić: Principi programiranja /kroz programski jezik C++/

IspisiBoju(Crvena);

ispisaće riječ “Crvena” na ekranu. Funkcija “UnesiBoju” također ima jedan parametar tipa “Boje”.
Ova funkcija zahtijeva od korisnika da unese sa tastature boju predstavljenu jednim slovom ‘C’, ‘P’, ‘Z’
ili ‘B’ (respektivno za crvenu, plavu, zelenu i bijelu boju), i dodjeljuje odgovarajuću pobrojanu
vrijednost promjenljivoj zadanoj kao stvarni parametar funkcije. Npr. ako pretpostavimo da je
“moja_boja” promjenljiva tipa “Boja”, tada će naredba

UnesiBoju(moja_boja);

smjestiti u promjenljivu “moja_boja” vrijednost “Crvena”, “Plava”, “Zelena” ili “Bijela” ovisno
o tome da li je korisnik unijeo ‘C’, ‘P’, ‘Z’ ili ‘B’. Funkcija pored toga vodi računa o ispravnosti
unesenih podataka. Obje funkcije su demonstrirane u kratkom testnom programu. Obratite pažnju da se u
funkciji “IspisiBoju” parametar prenosi po vrijednosti, a u funkciji “UnesiBoju” po referenci:

#include <iostream>
using namespace std;
enum Boje {Crvena, Plava, Zelena, Bijela};
void IspisiBoju(Boje b) {
switch(b) {
case Crvena: cout << "Crvena"; break;
case Plava: cout << "Plava"; break;
case Zelena: cout << "Zelena"; break;
case Bijela: cout << "Bijela";
}
}
void UnesiBoju(Boje &b) {
for(;;) {
char znak;
cin >> znak;
switch(znak) {
case 'C' : b = Crvena; return;
case 'P' : b = Plava; return;
case 'Z' : b = Zelena; return;
case 'B' : b = Bijela; return;
}
cout << "Neispravan unos!\n";
cin.ignore(10000, '\n');
}
}
int main() {
Boje moja_boja;
cout << "Unesi slovo koje predstavlja boju: ";
UnesiBoju(moja_boja);
cout << "Unijeli ste boju: ";
IspisiBoju(moja_boja);
return 0;
}

Do sada smo vidjeli da formalni parametri funkcija mogu biti reference. Međutim, interesantno je da
rezultat koji vraća funkcija također može biti referenca (nekada se ovo naziva vraćanje vrijednosti po
referenci). Ova mogućnost se ne koristi prečesto, ali postoje situacije kada je ovakva mogućnost od
neprocjenjive važnosti. Naime, kasnije ćemo vidjeti da se neki problemi u objektno-orijentiranom
pristupu programiranja ne bi mogli riješiti da ne postoji ovakva mogućnost. U slučaju funkcija čiji je
rezultat referenca, nakon završetka izvršavanja funkcije ne vrši se prosta zamjena poziva funkcije sa
vraćenom vrijednošću (kao u slučaju kada rezultat nije referenca), nego se poziv funkcije potpuno

XVII – 13
Željko Jurić: Principi programiranja /kroz programski jezik C++/

poistovjećuje sa vraćenim objektom (koji u ovom slučaju mora biti promjenljiva, ili općenitije
l-vrijednost). Ovo poistovjećivanje ide dotle da poziv funkcije postaje l-vrijednost, tako da se poziv
funkcije čak može upotrijebiti sa lijeve strane operatora dodjele, ili prenijeti u funkciju čiji je formalni
parametar referenca (obje ove radnje zahtijevaju l-vrijednost).

Sve ovo može na prvi pogled djelovati dosta nejasno. Stoga ćemo vraćanje reference kao rezultata
ilustrirati konkretnim primjerom. Posmatrajmo sljedeću funkciju, koja obavlja posve jednostavan
zadatak (vraća kao rezultat veći od svoja dva parametra):

int VeciOd(int x, int y) {


if(x > y) return x;
else return y;
}

Pretpostavimo sada da imamo sljedeći poziv:

int a(5), b(8);


cout << VeciOd(a, b);

Ova sekvenca naredbi će, naravno, ispisati broj “8”, s obzirom da će poziv funkcije “VeciOd(a, b)” po
povratku iz funkcije biti zamijenjen izračunatom vrijednošću (koja očigledno iznosi 8, kao veća od dvije
vrijednosti 5 i 8). Modificirajmo sada funkciju “VeciOd” tako da joj formalni parametri postanu
reference:

int VeciOd(int &x, int &y) {


if(x > y) return x;
else return y;
}

Prethodna sekvenca naredbi koja poziva funkciju “VeciOd” i dalje radi ispravno, samo što se sada
vrijednosti stvarnih parametara “a” i “b” ne kopiraju u formalne parametre “x” i “y”, nego se formalni
parametri “x” i “y” poistovjećuju sa stvarnim parametrima “a” i “b”. U ovom konkretnom slučaju,
krajnji efekat je isti. Ipak, ovom izmjenom smo ograničili upotrebu funkcije, jer pozivi poput

cout << VeciOd(5, 7);

više nisu mogući, s obzirom da se reference ne mogu vezati za brojeve. Međutim, ova izmjena
predstavlja korak do posljednje izmjene koju ćemo učiniti: modificiraćemo funkciju tako da kao rezultat
vraća referencu:

int &VeciOd(int &x, int &y) {


if(x > y) return x;
else return y;
}

Ovako modificirana funkcija vraća kao rezultat referencu na veći od svoja dva parametra, odnosno
sam poziv funkcije ponaša se kao da je on upravo vraćeni objekat. Na primjer, pri pozivu
“VeciOd(a, b)” formalni parametar “x” se poistovjećuje sa promjenljivom “a”, a formalni parametar
“y” sa promjenljivom “b”. Uz pretpostavku da je vrijednost promjenljive “b” veća od promjenljive “a”
(kao u prethodnim primjerima poziva), funkcija će vratiti referencu na formalni parametar “y”. Kako
reference na reference ne postoje, biće zapravo vraćena referenca na onu promjenljivu koju referenca
“y” predstavlja, odnosno promjenljivu “b”. Kad kažemo da reference na reference ne postoje, mislimo
na sljedeće: ukoliko imamo deklaracije poput

int &q = p;
int &r = q;

XVII – 14
Željko Jurić: Principi programiranja /kroz programski jezik C++/

tada “r” ne predstavlja referencu na referencu “q”, već referencu na promjenljivu za koju je referenca
“q” vezana, odnosno na promjenljivu “p” (odnosno “r” je također referenca na “p”). Dakle, funkcija je
vratila referencu na promjenljivu “b”, što znači da će se poziv “VeciOd(a, b)” ponašati upravo kao
promjenljiva “b”. To znači da postaje moguća ne samo upotreba ove funkcije kao obične vrijednosti, već
je moguća njena upotreba u bilo kojem kontekstu u kojem se očekuje neka promjenljva (ili l-vrijednost).
Tako su, na primjer, sasvim legalne konstrukcije poput sljedećih (ovdje su “Povecaj” i “Razmijeni”
funkcije iz ranijih primjera, a “c” neka cjelobrojna promjenljiva):

VeciOd(a, b) = 10;
VeciOd(a, b)++;
VeciOd(a, b) += 3;
Povecaj(VeciOd(a, b));
Razmijeni(VeciOd(a, b), c);

Posljednji primjer razmjenjuje onu od promjenljivih “a” i “b” čija je vrijednost veća sa promjenljivom
“c”. Drugim riječima, posljednja napisana verzija funkcije “VeciOd” ima tu osobinu da se poziv poput
“VeciOd(a, b)” ponaša ne samo kao vrijednost veće od dvije promjenljive “a” i “b”, nego se ponaša
kao da je ovaj poziv upravo ona promjenljiva od ove dvije čija je vrijednost veća! Usput, upravo smo
naučili šta još može biti l-vrijednost osim obične promjenljive ili elementa niza: l-vrijednost može biti i
poziv funkcije koji vraća referencu (kasnije ćemo upoznati još izraza koji mogu biti l-vrijednosti). Kako
je poziv funkcije koja vraća referencu l-vrijednost, za njega se može vezati i referenca (da nije tako, ne
bi bili dozvoljeni gore navedeni pozivi u kojima je poziv funkcije “VeciOd” iskorišten kao stvarni
argument u funkcijama kod kojih je formalni parametar referenca). Stoga je sasvim dozvoljena
deklaracija poput sljedeće:

int &v = VeciOd(a, b);

Nakon ove deklaracije, referenca “v” ponaša se kao ona od promjenljivih “a” i “b” čija je vrijednost
veća (preciznije, kao ona od promjenljivih “a” i “b” čija je vrijednost bila veća u trenutku deklariranja
ove reference).

Vraćanje referenci kao rezultata funkcije treba koristiti samo u izuzetnim prilikama, i to sa dosta
opreza, jer ova tehnika podliježe brojnim ograničenjima. Na prvom mjestu, jasno je da se prilikom
vraćanja referenci kao rezultata iza naredbe “return” mora naći isključivo neka promjenljiva ili
l-vrijednost, s obzirom da se reference mogu vezati samo za l-vrijednosti. Kompajler će prijaviti grešku
ukoliko ne ispoštujemo ovo ograničenje. Mnogo opasniji problem je ukoliko vratimo kao rezultat
referencu na neki objekat koji prestaje živjeti nakon prestanka funkcije (npr. na neku lokalnu
promjenljivu, uključujući i formalne parametre funkcije koji nisu reference). Na primjer, zamislimo da
smo funkciju “VeciOd” napisali ovako:

int &VeciOd(int x, int y) {


if(x > y) return x;
else return y;
}

Ovdje funkcija i dalje vraća referencu na veći od parametara “x” i “y”, međutim ovaj put ovi
parametri nisu reference (tj. ne predstavljaju neke objekte koji postoje izvan ove funkcije) nego
samostalni objekti koji imaju smisao samo unutar funkcije, i koji se uništavaju nakon završetka funkcije.
Drugim riječima, funkcija će vratiti referencu na objekat koji je prestao postojati, odnosno prostor u
memoriji koji je objekat zauzimao raspoloživ je za druge potrebe. Ovakva referenca naziva se viseća
referenca (engl. dangling reference). Ukoliko za rezultat ove funkcije vežemo neku referencu, ona će se
vezati za objekat koji zapravo ne postoji! Posljedice ovakvih akcija su potpuno nepredvidljive i često se
završavaju potpunim krahom programa ili operativnog sistema. Dobri kompajleri mogu uočiti većinu
situacija u kojima ste eventualno napravili viseću referencu i prijaviti grešku prilikom prevođenja, ali
postoje i situacije koje ostaju nedetektirane od strane kompajlera, tako da dodatni oprez nije na odmet.

XVII – 15
Željko Jurić: Principi programiranja /kroz programski jezik C++/

Pored običnih referenci postoje i reference na konstante (ili reference na konstante objekte). One se
deklariraju isto kao i obične reference, uz dodatak ključne riječi “const” na početku. Reference na
konstante se također vezuju za objekat kojim su inicijalizirane, ali se nakon toga ponašaju kao konstantni
objekti, odnosno pomoću njih nije moguće mijenjati vrijednost vezanog objekta. Na primjer, neka su
date sljedeće deklaracije:

int a = 5;
const int &b = a;

Referenca “b” će se vezati za promjenljivu “a”, tako da će pokušaj ispisa promjenljive “b” ispisati
vrijednost “5”, ali pokušaj promjene vezanog objekta pomoću naredbe

b = 6;

dovešće do prijave greške. Naravno, promjenljiva “a” nije time postala konstanta: ona se i dalje može
direktno mijenjati (ali ne i indirektno, preko reference “b”). Treba uočiti da se prethodne deklaracije
bitno razlikuju od sljedećih deklaracija, u kojoj je “b” obična konstanta, a ne referenca na konstantni
objekat:

int a = 5;
const int b = a;

Naime, u posljednjoj deklaraciji, ukoliko promjenljiva “a” promijeni vrijednost recimo na vrijednost
“6”, vrijednost konstante “b” i dalje ostaje “5”. Sasvim drugačiju situaciju imamo ukoliko je “b”
referenca na konstantu: promjena vrijednosti promjenljive “a” ostavlja identičan efekat na referencu “b”,
s obzirom da je ona vezana na objekat “a”. Dakle, svaka promjena promjenljive “a” odražava se na
referencu “b”, mada se ona, tehnički gledano, ponaša kao konstantan objekat!

Postoji još jedna bitna razlika između prethodnih deklaracija. U slučaju kada “b” nije referenca, u
nju se prilikom inicijalizacije kopira čitav sadržaj promjenljive “a”, dok se u slučaju kada je “b”
referenca, u nju kopira samo adresa mjesta u memoriji gdje se vrijednost promjenljive “a” čuva, tako da
se pristup vrijednosti promjenljive “a” putem reference “b” obavlja indirektno. U slučaju da
promjenljiva “a” nije nekog jednostavnog tipa poput “int”, nego nekog masivnog tipa koji zauzima
veliku količinu memorije (takve tipove ćemo upoznati u kasnijim poglavljima), kopiranje čitavog
sadržaja može biti zahtjevno, tako da upotreba referenci može znatno povećati efikasnost.

Činjenica da se reference na konstante ne mogu iskoristiti za promjenu objekta za koji su vezane


omogućava da se reference na konstante mogu vezati i za konstante, brojeve, pa i proizvoljne izraze.
Tako su, na primjer, sljedeća deklaracije sasvim legalne:

int a = 5;
const int &b = 3 * a + 1;
const int &c = 10;

Također, reference na konstante mogu se vezati za objekat koji nije istog tipa, ali za koji postoji
automatska pretvorba u tip koji odgovara referenci. Na primjer:

int p = 5;
const double &b = p;

Interesantna stvar nastaje ukoliko referencu na konstantu upotrijebimo kao formalni parametar
funkcije. Na primjer, pogledajmo sljedeću funkciju:

double Kvadrat(const double &x) {


return x * x;
}

XVII – 16
Željko Jurić: Principi programiranja /kroz programski jezik C++/

Kako se referenca na konstantu može vezati za proizvoljan izraz (a ne samo za l-vrijednosti), sa ovako
napisanom funkcijom pozivi poput

cout << Kvadrat(3 * a + 2);

postaju potpuno legalni. Praktički, funkcija “Kvadrat” može se koristiti na posve isti način kao da se
koristi prenos parametara po vrijednosti (jedina formalna razlika je u činjenici da bi eventualni pokušaj
promjene vrijednosti parametra “x” unutar funkcije “Kvadrat” doveo do prijave greške, zbog činjenice
da je “x” referenca na konstantu, što također znači i da ovakva funkcija ne može promijeniti vrijednost
svog stvarnog parametra). Ipak, postoji suštinska tehnička razlika u tome šta se zaista interno dešava u
slučaju kada se ova funkcija pozove. U slučaju da kao stvarni parametar upotrijebimo neku l-vrijednost
(npr. promjenljivu) kao u pozivu

cout << Kvadrat(a);

dešavaju se iste stvari kao pri klasičnom prenosu po referenci: formalni parametar “x” se poistovjećuje
sa promjenljivom “a”, što se ostvaruje prenosom adrese. Dakle, ne dolazi ni do kakvog kopiranja
vrijednosti. U slučaju kada stvarni parametar nije l-vrijednost (što nije dozvoljeno kod običnog prenosa
po referenci), automatski se kreira privremena promjenljiva koja se inicijalizira stvarnim parametrom,
koja se zatim klasično prenosi po referenci, i uništava čim se poziv funkcije završi (činjenica da će ona
biti uništena ne smeta, jer njena vrijednost svakako nije mogla biti promijenjena putem reference na
konstantu). Drugim riječima, poziv

cout << Kvadrat(3 * a + 2);

načelno je ekvivalentan pozivu

{
const double _privremena_ = 3 * a + 2;
cout << Kvadrat(_privremena_);
}

Formalni parametri koji su reference na konstantu koriste se uglavnom kod rada sa parametrima
masivnih tipova, što ćemo obilato koristiti u kasnijim poglavljima. Naime, prilikom prenosa po
vrijednosti uvijek dolazi do kopiranja stvarnog parametra u formalne, što je neefikasno za slučaj
masivnih objekata. Kod prenosa po referenci do ovog kopiranja ne dolazi, a upotreba reference na
konstantu dozvoljava da kao stvarni argument upotrijebimo proizvoljan izraz (slično kao pri prenosa po
vrijednosti). Stoga, u slučaju masivnih objekata, prenos po vrijednosti treba koristiti samo ukoliko zaista
želimo da formalni parametar bude kopija stvarnog parametra (npr. ukoliko je unutar funkcije
neophodno mijenjati vrijednost formalnog parametra, a ne želimo da se to odrazi na vrijednost stvarnog
parametra).

XVII – 17

You might also like