You are on page 1of 13

Dr.

eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_a
Akademska godina 2013/14

Predavanje 12_a
Vidjeli smo da strukture i klase omoguavaju dizajniranje tipova podataka koji na jednostavan i
prirodan nain modeliraju stvarne objekte sa kojima se susreemo prilikom rjeavanja realnih problema.
Meutim, jedini operatori koji su na poetku definirani za ovakve tipove podataka su operator dodjele
= i operator uzimanja adrese &. Sve ostale operacije nad sloenim objektima do sada smo izvodili
bilo pozivom funkcija i prenoenjem objekata kao parametara u funkcije, bilo pozivom funkcija lanica
nad objektima. Tako smo, na primjer za ispis nekog objekta v tipa Vektor3d ili VektorNd
koristili poziv poput v.Ispisi(), dok smo za sabiranje dva vektora v1 i v2 i dodjelu rezultata
treem vektoru v3 koristili konstrukciju poput v3 = ZbirVektora(v1, v2). Rad sa tipovima
Vektor3d odnosno VektorNd bio bi znatno olakan kada bismo za manipulacije sa objektima
ovog tipa mogli koristiti istu sintaksu kakvu koristimo pri radu sa prostim ugraenim tipovima podataka,
tj. kada bismo za ispis vektora mogli koristi konstrukciju std::cout << v, a za sabiranje konstrukciju
v3 = v1 + v2. Upravo ovakvu mogunost nudi nam preklapanje odnosno preoptereivanje operatora
(engl. operator overloading) Preciznije, preklapanje (preoptereivanje) operatora nam omoguava da
nekim od operatora koji su definirani za proste tipove podataka damo smisao i za tipove podataka koje
smo sami definirali. Na prethodnom predavanju upoznali smo se sa preklapanjem operatora dodjele, koje
predstavlja specijalni sluaj opeg postupka preklapanja operatora, koji emo sada detaljno razmotriti.
Preklapanje operatora ostvaruje se uz pomo operatorskih funkcija, koje izgledaju kao i klasine
funkcije, samo to umjesto imena imaju kljunu rije operator iza koje slijedi oznaka nekog od
postojeih operatora. Za sada emo pretpostaviti da su operatorske funkcije izvedene kao obine
funkcije, dok emo izvedbu operatorskih funkcija kao funkcija lanica razmotriti neto kasnije.
Operatorske funkcije izvedene kao obine funkcije mogu imati dva parametra, ukoliko je navedeni
operator binarni operator, ili jedan parametar ukoliko je navedeni operator unarni operator. Meutim,
tip barem jednog od parametara operatorske funkcije mora biti korisniki definirani tip podataka, u koji
spadaju strukture, klase i pobrojani tipovi (tj. tipovi definirani deklaracijom enum), to ukljuuje i
predefinirane tipove definirane u standardnoj biblioteci jezika C++ (kao to su vector, string,
list, set, map itd.). Na primjer, neka je data sljedea deklaracija klase Vektor3d, u kojoj su,
radi jednostavnosti, definirani samo konstruktor i trivijalne metode za pristup atributima klase:
class Vektor3d {
double x, y, z;
public:
Vektor3d(double x, double y,
double DajX() const { return
double DajY() const { return
double DajZ() const { return
};

double z) : x(x), y(y), z(z) {}


x; }
y; }
z; }

Za ovakvu klasu moemo definirati sljedeu operatorsku funkciju koja obavlja sabiranje dva vektora
(razmak iza rijei operator moe se izostaviti):
Vektor3d operator +(const Vektor3d &v1, const Vektor3d &v2) {
return {v1.DajX() + v2.DajX(), v1.DajY() + v2.DajY(),
v1.DajZ() + v2.DajZ()};
}

Ovdje smo u return naredbi iskoristili skriveni poziv konstruktora kao funkcije (automatska
pretvorba iz inicijalizacione liste), to je uvedeno u C++11. U naelu, operatorske funkcije principijelno
su mogu pozvati kao i svaka druga funkcija. Na primjer, ukoliko su a, b i c objekti tipa
Vektor3d, konstrukcija poput c = operator +(a, b) je sasvim ispravna. Meutim, uvoenje
operatorskih funkcija omoguava da umjesto toga moemo prosto pisati
c = a + b;

// Ovo se interpretira kao c = operator +(a, b)

Razmotrimo ovu mogunost malo openitije. Neka je proizvoljan binarni operator podran u
jeziku C++ (to ukljuuje i operatore koji se sastoje od vie od jednog znaka kao to su +=, >=, <<

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_a
Akademska godina 2013/14

itd.) i neka su x i y neki objekti od kojih je barem jedan tipa strukture, klase ili pobrojanog tipa.
Tada se izraz x y prvo pokuava interpretirati kao izraz operator (x, y). Drugim rijeima,
pri nailasku na neki izraz sa korisniki definiranim tipovima podataka koji sadri binarne operatore,
prilikom interpretacije tog izraza prvo se pokuavaju pozvati odgovarajue operatorske funkcije, koje
odgovaraju upotrijebljenim operatorima (kako po oznaci operatora, tako i po tipovima argumenata).
Ukoliko takve operatorske funkcije postoje, prosto se vri njihov poziv, iji rezultat daje interpretaciju
izraza. Meutim, ukoliko takve operatorske funkcije ne postoje, vri se pokuaj pretvorbe operanada u
neke druge tipove za koje je odgovarajui operator definiran, to ukljuuje podrazumijevane pretvorbe u
standardne ugraene tipove, kao i korisniki definirane pretvorbe, koje se mogu ostvariti konstruktorima
sa jednim parametrom (o emu smo ve govorili ranije), kao i operatorskim funkcijama za pretvorbu (o
emu emo govoriti kasnije). Ukoliko se i nakon obavljenih pretvorbi ne moe nai odgovarajua
interpretacija navedenog izraza, prijavljuje se greka. Greka se takoer prijavljuje ukoliko je pretvorbe
mogue izvriti na vie razliitih naina, jer je tada nejasno koje pretvorbe treba primijeniti.
Operatorske funkcije se takoer mogu deklarirati kao funkcije prijatelji klase. U praksi se gotovo
uvijek tako radi, s obzirom da u veini sluajeva operatorske funkcije trebaju pristupati internim
atributima klase. Na primjer, sasvim je razumno u klasi Vektor3d deklarirati prethodno napisanu
operatorsku funkciju kao prijateljsku funkciju klase:
class Vektor3d {
... // Kao i ranije...
friend Vektor3d operator +(const Vektor3d &v1, const Vektor3d &v2);
};

Uz ovakvu deklaraciju, definicija prethodne operatorske funkcije mogla bi se pojednostaviti:


Vektor3d operator +(const Vektor3d &v1, const Vektor3d &v2) {
return {v1.x + v2.x, v1.y + v2.y, v1.z + v2.z};
}

Stoga emo u nastavku, ukoliko se drugaije ne naglasi, podrazumijevati da su sve operatorske funkcije
koje definiramo deklarirane kao prijateljske funkcije klase.
Slino vrijedi i za sluaj unarnih operatora. Ukoliko je neki unarni operator, tada se izraz x
prvo pokuava interpretirati kao izraz operator (x). Tek ukoliko odgovarajua operatorska
funkcija ne postoji, pokuava se pretvorba u neki drugi tip za koji je taj operator definiran, osim ukoliko
se radi o operatoru uzimanja adrese & (koji je podrazumijevano definiran i za korisniki definirane
tipove). Ukoliko takve pretvorbe nisu podrane, prijavljuje se greka.
Kao primjer operatorske funkcije za preklapanje unarnih operatora, moemo definirati operatorsku
funkciju za klasu Vektor3d, koja omoguava da se unarna varijanta operatora - moe primijeniti
na objekte tipa Vektor3d (sa znaenjem obrtanja smjera vektora, kao to je uobiajeno u matematici):
Vektor3d operator (const Vektor3d &v) {
return {v.x, v.y, v.z};
}

Izvjesni operatori (kao to je upravo pomenuti operator ) postoje i u unarnoj i u binarnoj


varijanti, tako da moemo imati za isti operator operatorsku funkciju sa jednim parametrom (koja
odgovara unarnoj varijanti) i sa dva parametra (koja odgovara binarnoj varijanti). Tako, na primjer, za
klasu Vektor3d moemo definirati i binarni operator za oduzimanje na sljedei nain:
Vektor3d operator (const Vektor3d &v1, const Vektor3d &v2) {
return {v1.x v2.x, v1.y v2.y, v1.z v2.z};
}

Meutim, istu operatorsku funkciju mogli smo napisati i jednostavnije na sljedei nain (zbog svoje
kratkoe, funkcija je pogodna za realizaciju kao umetnuta funkcija, to smo i uinili):

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_a
Akademska godina 2013/14

inline Vektor3d operator (const Vektor3d &v1, const Vektor3d &v2) {


return v1 + -v2;
}
// Kratko, ali ne ba efikasno...

Ovdje je iskoritena injenica da smo prethodno definirali operator unarni minus za tip
Vektor3d (tako da je kompajler nauio ta znai konstrukcija v2), kao i da smo definirali
binarni operator + za isti tip (tako da kompajler zna kako se sabiraju dva vektora). Ovdje ne treba
naivno pomisliti da smo umjesto v1 + v2 mogli napisati v1 v2 (s obzirom da se matematski
gledano radi o dva ista izraza). Naime, znaenje izraza v1 + v2 je od ranije dobro definirano, s
obzirom da smo prethodno definirali znaenje binarnog operatora + i unarnog minusa za objekte tipa
Vektor3d. S druge strane, znaenje izraza v1 v2 tek trebamo definirati i to upravo pisanjem
operatorske funkcije za binarni operator . Upotreba izraza v1 v2 unutar ove operatorske funkcije
bila bi zapravo interpretirana kao da ova operatorska funkcija treba pozvati samu sebe (tj. kao rekurzija,
i to bez izlaza)! Pored toga, programer ima puno pravo da napie operatorsku funkciju za binarni
operator kako god eli (pa ak i tako da uope ne obavlja oduzimanje nego neto drugo), tako da
izrazi v1 + v2 i v1 v2 uope ne moraju imati isto znaenje (mada to nije dobra praksa). U
svakom sluaju, prvi od ovih izraza se interpretira kao operator +(v1, operator (v2)), a drugi
kao operator (v1, v2). Ipak, zbog razloga efikasnosti, bolje je operatorsku funkciju za binarni
operator implementirati neposredno, ne oslanjajui se na operatorske funkcije za sabiranje i unarni
minus (nije teko vidjeti da e ukupan broj izvrenih operacija biti manji u tom sluaju).
Primijetimo da u sluaju da smo operatorsku funkciju za binarni operator definirali skraenim
postupkom (preko sabiranja i unarnog minusa), ne bi bilo potrebe da je deklariramo kao funkciju
prijatelja klase (s obzirom da tada u njoj uope ne pristupamo atributima klase). S druge strane, od takve
deklaracije ne bi bilo ni tete. Zbog toga je preporuljivo sve operatorske funkcije koje manipuliraju sa
objektima neke klase uvijek deklarirati kao prijatelje te klase (ak i kada to nije neophodno), jer se tada
posmatranjem interfejsa klase jasno vidi koji su sve operatori definirani za tu klasu.
Sasvim je mogue imati vie operatorskih funkcija za isti operator, pod uvjetom da se one razlikuju
po tipu svojih argumenata, tako da kompajler moe nedvosmisleno odrediti koju operatorsku funkciju
treba pozvati u konkretnoj situaciji. Na primjer, kako je dozvoljeno pomnoiti realni broj sa vektorom,
prirodno bi bilo definirati sljedeu operatorsku funkciju:
Vektor3d operator *(double d, const Vektor3d &v) {
return {d * v.x, d * v.y, d * v.z};
}

Nakon ovakve definicije, izrazi poput 3 * v ili c * v gdje je v vektor a c realan broj postaju
posve smisleni. S druge strane, izraz poput v * 3 je i dalje nedefiniran, i dovee do prijave greke,
odnosno prethodnom definicijom kompajler je nauio kako se mnoi broj sa vektorom, ali ne i vektor
sa brojem! Naime, po konvenciji, usvojeno je da se prilikom preklapanja operatora ne prave nikakve
pretpostavke o eventualnoj komutativnosti (niti asocijativnosti) operatora, jer bi u suprotnom bilo
nemogue definirati operatore koji kre ove zakone (recimo, ne bi bilo mogue definirati mnoenje
matrica, koje nije komutativno). Zbog toga, ukoliko elimo dati smisao izrazima poput v * 3, moramo
definirati jo jednu operatorsku funkciju za binarni operator *, koja bi glasila ovako
Vektor3d operator *(const Vektor3d &v, double d) {
return {d * v.x, d * v.y, d * v.z};
}

Ova funkcija se od prethodne razlikuje samo po tipu parametara, a implementacija joj je potpuno
ista. Da utedimo na pisanju, mogli smo pisati i ovako:
inline Vektor3d operator *(const Vektor3d &v, double d) {
return d * v;
}

Na ovaj nain smo eksplicitno rekli da je v * d gdje je v vektor a d broj isto to i d * v, a


postoji operatorska funkcija koja objanjava kakav je smisao izraza d * v. Interesantno je da ovakvom

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_a
Akademska godina 2013/14

definicijom nita ne gubimo na efikasnosti, s obzirom da smo ovu operatorsku funkciju izveli kao
umetnutu funkciju, tako da se izraz oblika v * d prosto zamjenjuje izrazom d * v (odnosno,
pripadna operatorska funkcija se ne poziva, ve se njeno tijelo prosto umee na mjesto poziva). Da ovu
operatorsku funkciju nismo izveli kao umetnutu, imali bismo izvjestan gubitak na efikasnosti, s obzirom
da bi izraunavanje izraza v * d prvo dovelo do poziva jedne operatorske funkcije, koja bi dalje
pozvala drugu operatorsku funkciju (dakle, imali bismo dva poziva umjesto jednog).
Prethodne definicije jo uvijek ne daju smisla izrazima poput a * b gdje su i a i b objekti
tipa Vektor3d. Za tu svrhu potrebno je definirati jo jednu operatorsku funkciju za binarni operator
*, ija e oba parametra biti tipa Vektor3d. Meutim, u matematici se produkt dva vektora moe
interpretirati na dva naina: kao skalarni produkt (iji je rezultat broj) i kao vektorski produkt (iji je
rezultat vektor). Ne moemo napraviti obje interpretacije i pridruiti ih operatoru *, jer se u tom
sluaju nee znati na koju se interpretaciju izraz a * b odnosi (u matematici je taj problem rijeen
uvoenjem razliitih oznaka operatora za ove dvije interpretacije, tako da operator oznaava skalarni,
a operator vektorski produkt). Slijedi da se moramo odluiti za jednu od interpretacija. Ukoliko se
dogovorimo da izraz a * b interpretiramo kao skalarni produkt, moemo napisati sljedeu operatorsku
funkciju, koja definira tu interpretaciju:
double operator *(const Vektor3d &v1, const Vektor3d &v2) {
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}

Ovo ne znai da se moramo odrei mogunosti da definiramo i operator za vektorski produkt dva
vektora, samo to vie ne moe biti operator *. Na alost, ne postoji mogunost dodavanja novih
operatora, ve samo proirivanje znaenja ve postojeih operatora. Na primjer, nije mogue definirati
operator @ i dati mu neko znaenje, s obzirom da takav operator ne postoji u jeziku C++. Zbog toga
nije mogue definirati ni operator sa ciljem da nam a b predstavlja vektorski produkt dva
vektora. Stoga, jedino to moemo uiniti je da nekom od postojeih operatora proirimo ulogu da
obavlja raunanje vektorskog produkta. Na primjer, mogli bismo definirati da operator / primijenjen
na vektore predstavlja vektorsko mnoenje, tako da u sluaju kada su a i b vektori, a / b
predstavlja njihov vektorski produkt. Na alost, takvo rjeenje bi moglo biti zbunjujue, s obzirom da za
sluaj kada su a i b brojevi, izraz a / b oznaava dijeljenje. Moda je bolje odluiti se za operator
%, tako da definiramo da nam a % b oznaava vektorski produkt (ako nita drugo, onda barem zbog
injenice da znak % vie vizuelno podsjea na znak od znaka /):
Vektor3d operator %(const Vektor3d &v1, const Vektor3d &v2) {
return {v1.y * v2.z v1.z * v2.y, v1.z * v2.x - v1.x * v2.z,
v1.x * v2.y v1.y * v2.x};
}

Pored toga to nije mogue uvoditi nove operatore, nije mogue ni promijeniti prioritet operatora.
Na primjer, da smo definirali da operator & predstavlja vektorsko mnoenje, njegov prioritet bi ostao
nii od prioriteta operatora +, tako da bi se izraz v1 + v2 & v3 interpretirao kao (v1 + v2) & v3
a ne kao v1 + (v2 & v3). Ovo je jo jedan razlog zbog ega je izbor operatora % relativno dobar
izbor za vektorski produkt dva vektora. Naime, prioritet ovog operatora je u istom rangu kao i prioritet
klasinog operatora za mnoenje *.
Na ovom mjestu emo jo jednom naglasiti da operatorske funkcije moraju imati barem jedan
argument koji je korisniki definiranog tipa (strukture, klase ili pobrojanog tipa). Ovo je uraeno da se
sprijei mogunost promjene znaenja pojedinih operatora za proste ugraene tipove. Na primjer, nije
mogue napisati operatorsku funkciju poput
int operator +(int x, int y) {
return x * y;
}

// OVO NE MOE!!!

kojom bismo postigli da vrijednost izraza 2 + 3 bude 6 a ne 5. Mada bi, u izvjesnim situacijama,
promjena znaenja pojedinih operatora za proste ugraene tipove mogla biti od koristi, tvorci jezika C++

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_a
Akademska godina 2013/14

su zakljuili da bi teta od mogue zloupotrebe ovakvih konstrukcija mogla biti znatno vea od
eventualne koristi, pa su odluili da zabrane ovakvu mogunost. Pored toga, takva mogunost bi veoma
lako mogla dovesti do formiranja posve neoekivanih i nekontroliranih neeljenih rekurzivnih poziva. S
druge strane, operatorske funkcije je sasvim mogue definirati za tipove uvedene u standardnoj biblioteci
jezika C++, s obzirom da su oni gotovo bez izuzetka realizirani kao klase (preciznije, kao generike
klase). Na primjer, vrlo je lako omoguiti sabiranje primjeraka standardne generike klase vector
definirane u istoimenoj standardnoj biblioteci sintaksom poput v3 = v1 + v2 prostim definiranjem
(generike) operatorske funkcije poput sljedee:
template <typename Tip>
std::vector<Tip> operator +(const std::vector<Tip> &v1,
const std::vector<Tip> &v2) {
if(v1.size() != v2.size())
throw std::domain_error("Razliite dimenzije vektora!");
std::vector<Tip> v3(v1.size());
for(int i = 0; i < v1.size(); i++) v3[i] = v1[i] + v2[i];
return v3;
}

Gotovo svi operatori ugraeni u jezik C++ mogu se preklopiti (tj. dodefinirati). Jedini operatori koje
nije mogue preklopiti su operatori ::, ., .*, ?: i operatori sizeof i typeof, s obzirom
da ovi operatori imaju u jeziku C++ toliko specifine primjene da bi mogunost njihovog preklapanja
dovela do velike zbrke. Tako je mogue preklopiti sljedee binarne operatore:
+
==
/=

!=
%=

*
&
&=

/
^
^=

<<

|
|=

&&

<<=

>>
||
>>=

<
=
->*

&

++

--

<=
+=
,

>
=

>=
*=

zatim sljedee unarne operatore:


+

kao i sljedee specijalne operatore koje je teko svrstati meu gore prikazane operatore:
[]

()

->

new

new[]

delete delete[]

Svi navedeni binarni operatori osim operatora dodjele =, kao i svi navedeni unarni operatori,
mogu se preklopiti na ve opisani nain (pri emu preklapanje operatora ++ i zahtijeva i neke
dodatne specifinosti koje emo kasnije objasniti, zbog injenice da oni imaju kako prefiksni, tako i
postfiksni oblik). O preklapanju operatora dodjele smo ve govorili (mada emo kasnije dati jo
nekoliko napomena vezanih za njegovo preklapanje), dok emo o preklapanju specijalnih operatora
govoriti neto kasnije. Treba naglasiti da programer ima punu slobodu da znaenje pojedinih operatora
za korisniki definirane tipove podataka definira kako god eli. Tako je sasvim mogue definirati da
operator + obavlja oduzimanje dva vektora, a da unarni operator rauna duinu vektora. Jasno je
da takvo definiranje nije nimalo mudro. Meutim, ne postoji formalni mehanizam da se to sprijei, isto
kao to je nemogue sprijeiti nekoga da promjenljivu koja uva poluprenik kruga nazove brzina,
niti da funkciju koja nalazi rjeenja kvadratne jednaine nazove ProduktMatrica. Stoga emo u
nastavku rei nekoliko rijei kako bi trebalo definirati izvjesne operatore sa ciljem da se odri
konzistencija sa nainom djelovanja pojedinih operatora na razliite proste ugraene tipove.
Na prvom mjestu, neki operator je za neki tip potrebno definirati samo ukoliko je intuitivno jasno ta
bi taj operator trebao da znai za taj tip. Na primjer, sasvim je jasno ta bi operator + trebao da znai
za dva objekta koji predstavljaju neke matematske strukture za koje je pojam sabiranja definiran.
Takoer, ukoliko pravimo klasu koja omoguava rad sa nizovima znakova, ima smisla definirati
operator + koji bi mogao da predstavlja nadovezivanje nizova znakova jedan na drugi (kao to je
izvedeno u standardnoj klasi string iz istoimene biblioteke). S druge strane, teko je dati smisleno
znaenje izrazu a + b za sluaj kada su a i b primjerci klase Student.
Drugo pravilo preporuuje da kada god definiramo neki od binarnih aritmetikih operatora kao to
su +, , * itd. trebamo definirati i odgovarajue operatore sa pridruivanjem poput +=, =,
5

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_a
Akademska godina 2013/14

*= itd. Naime, pri radu sa prostim ugraenim tipovima programeri su navikli da umjesto a = a + b
piu a += b. Stoga bi lijepo bilo da isto vrijedi i za rad sa sloenim tipovima za koje je prethodno
definiran operator +. Meutim, ovo se nee podrazumijevati samo po sebi, jer je += posve drugaiji
operator od operatora +, i injenica da je definiran operator + uope ne povlai da je definiran i
operator +=. Pored toga, izraz a += b se interpretira kao operator +=(a, b) iz ega slijedi da
se on principijelno moe definirati tako da ima potpuno drugaije znaenje od izraza a = a + b, koji
se interpretira kao a = operator +(a, b) koji se, u sluaju da je operator = takoer preklopljen,
dalje interpretira kao a.operator =(operator +(a, b)). Ipak, takva definicija bi bila protivna
svim moguim kodeksima programerskog bontona. Stoga bi operator += trebalo definirati tako da
izrazi a = a + b i a += b imaju isto znaenje. Slijedi jedan jednostavan nain kako se ovo moe
izvesti za tip Vektor3d:
Vektor3d &operator +=(Vektor3d &v1, const Vektor3d &v2) {
v1.x += v2.x; v1.y += v2.y; v1.z += v2.z;
return v1;
}

Ovdje je veoma bitno uoiti da je prvi formalni parametar u ovoj operatorskoj funkciji referenca na
nekonstantni objekat (odnosno, imamo klasini prenos parametra po referenci). Naime, izraz a += b
koji se interpretira kao operator +=(a, b) treba da promijeni vrijednost objekta a, a to je
mogue ostvariti jedino prenosom po referenci (i to na nekonstantni objekat). Takoer, treba uoiti da
ovu funkciju nismo izveli kao void funkciju, nego iz nje vraamo kao rezultat referencu na
izmijenjeni vektor (vraanjem reference izbjegavamo kopiranje objekta koji ve postoji). Razlog za ovo
je saglasnost sa nainom kako operator += djeluje na proste ugraene tipove. Naime, za sluaj kada su
a, b i c objekti nekog prostog ugraenog tipa (npr. cjelobrojne promjenljive) sasvim je legalno
pisati a = b += c (to ima identino znaenje kao sekvenca instrukcija b += c i a = b). Sa ovako
napisanom operatorskom funkcijom, postiemo da ista konstrukcija radi i za objekte tipa Vektor3d.
Potreba da se operatori sa pridruivanjem implementiraju odvojeno od odgovarajuih operatora bez
pridruivanja oteava odravanje programa, s obzirom da se svaka eventualna izmjena u implementaciji
jednog operatora mora izvesti i u implementaciji drugog. Zbog toga je prirodno postaviti pitanje moe li
se nekako implementacija jednog od njih prosto osloniti na implementaciju drugog, tako da se
eventualne prepravke vre samo na jednom mjestu. Na prvi pogled, djeluje logino implementaciju
operatora poput += zasnovati na implementaciji operatora +. Recimo, mogli bismo uraditi neto
poput sljedee definicije:
inline Vektor3d &operator +=(Vektor3d &v1, const Vektor3d &v2) {
return v1 = v1 + v2;
}
// Ovo se ne preporuuje!!!

Mada nema nikakve sumnje da ovakva definicija radi ispravno, ona je izrazito nepreporuljiva, s
obzirom na neefikasnost. Naime, ona izraz oblika v1 += v2 efektivno svodi na prividno ekvivalentan,
ali mnogo neefikasniji izraz v1 = v1 + v2. Zaista, dok se izraz v1 += v2 moe izvesti direktnom
izmjenom sadraja objekta v1, izraz v1 = v1 + v2 prvo zahtijeva da se izvri konstrukcija
pomonog objekta koji sadri vrijednost v1 + v2 (to se izvodi pozivom operatorske funkcije za
operator +), nakon ega se tako stvoreni objekat dodjeljuje objektu v1 (to tipino zahtijeva
unitavanje prethodnog sadraja objekta i kopiranje novog sadraja na mjesto starog sadraja), dok se
kreirani pomoni objekat unitava. Ovo moe biti veoma neefikasno ukoliko je objekat masivan, a
pogotovo ukoliko su u igru moraju ukljuiti destruktori, i vlastiti kopirajui konstruktor i kopirajui
operator dodjele. Zbog toga, operatorsku funkciju za operator += nikada ne treba implementirati na
ovaj nain (analogna pria vrijedi i za sve ostale operatore sa pridruivanjem). Umjesto toga, mnogo je
bolje neposredno izvesti operatorsku funkciju za operator += (na ranije opisani nain), a operatorsku
funkciju za operator + izvesti indirektno preko ve implementiranog operatora +=. Mada ovo djeluje
malo neobino, postoji mnogo jednostavnih naina da se ovo izvede. Vjerovatno najbolji nain je ovako:
inline Vektor3d operator +(const Vektor3d &v1, const Vektor3d &v2) {
Vektor3d v3(v1); v3 += v2; return v3;
}

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_a
Akademska godina 2013/14

Ovdje smo kreirali pomoni objekat v3 koji vraamo kao rezultat funkcije. Razlog zbog kojeg
nismo operator += primijenili direktno nad argumentom v1 lei u injenici da on ne smije biti
izmijenjen. Ako paljivo analiziramo ovu izvedbu, primijetiemo da ovako definirana operatorska
funkcija za operator + vri isti broj kreiranja, kopiranja i unitavanja objekata kao i direktna izvedba,
tako da nije nita manje efikasna od direktne izvedbe. Interesantni su pokuaji da se ova izvedba skrati.
Naime, dosta autora preporuuju da se posljednje dvije naredbe spoje u jednu, kao u sljedeem primjeru:
inline Vektor3d operator +(const Vektor3d &v1, const Vektor3d &v2) {
Vektor3d v3(v1); return v3 += v2;
}

Meutim, praksa je pokazala da se znatan broj kompajlera ne snalazi najbolje sa optimizacijom vraanja
vrijednosti iz funkcije u sluaju kada se iza naredbe return nalazi neki izraz koji mijenja promjenljive
koje u njemu uestvuju, tako da ova izvedba tipino generira loiji mainski kd od prethodne verzije.
Tanije, u prvoj verziji kompajler tipino uspijeva eliminirati vei broj nepotrebnih kopiranja u sluaju
sloenijih objekata. Neki preporuuju i ovakvu izvedbu:
inline Vektor3d operator +(Vektor3d v1, const Vektor3d &v2) {
v1 += v2; return v1;
}

Ova izvedba se moe jo vie skratiti, to daje izuzetno kompaktnu izvedbu:


inline Vektor3d operator +(Vektor3d v1, const Vektor3d &v2) {
return v1 += v2;
}

Ideja ovih izvedbi je da se prvi parametar prenese po vrijednosti, ime e v1 svakako biti kopija
odgovarajueg stvarnog parametra, tako da primjena operatora += na njega nee uticati na stvarni
parametar. Nevolja ove izvedbe je u tome to e se ovo kopiranje uglavnom zaista i izvriti. Neko e rei
da se i u prvoj prikazanoj izvedbi svakako formalni parametar v1 kopira u pomoni objekat v3. U
naelu je to tano, ali veliki broj kompajlera je u stanju da izvri takvu optimizaciju da u potpunosti
izbjegne stvaranje objekta v3 i da umjesto toga kopiranje direktno vri na odredite koje treba da
prihvati rezultat operacije +. Istina je da postoje kompajleri koji su u stanju optimizirati kopiranje i u
posljednje dvije prikazane verzije, ali su takvi kompajleri znatno rjei. Da rezimiramo, eksperimenti su
pokazali da prva prikazana verzija generira najbolji mainski kd na najveem broju raspoloivih
kompajlera.
Sljedee pravilo preporuuje da ukoliko smo se uope odluili da definiramo operatore za neku
klasu, treba definirati i smisao operatora == i !=. Ovi operatori bi, ukoliko potujemo zdravu
logiku, trebali testirati jednakost odnosno razliitost dva objekta. Za klasu Vektor3d, odgovarajue
operatorske funkcije bi mogle izgledati ovako:
bool operator ==(const Vektor3d &v1, const Vektor3d &v2) {
return v1.x == v2.x && v1.y == v2.y && v1.z == v2.z;
}
bool operator !=(const Vektor3d &v1, const Vektor3d &v2) {
return v1.x != v2.x || v1.y != v2.y || v1.z != v2.z;
}

Operatorsku funkciju za operator != smo mogli napisati i na sljedei nain, oslanjajui se na


postojeu definiciju operatorske funkcije za operator == i na znaenje operatora ! primijenjenog na
izraze logikog tipa:
inline bool operator !=(const Vektor3d &v1, const Vektor3d &v2) {
return !(v1 == v2);
}

Na ovaj nain smo istakli da elimo da znaenje izraza v1 != v2 treba da bude isto kao i znaenje
izraza !(v1 == v2), to se ne podrazumijeva samo po sebi sve dok ne definiramo da je tako. Naime,
7

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_a
Akademska godina 2013/14

mada su ova dva izraza praktino jednaka za proste ugraene tipove, za tipove koje samostalno
kreriramo principijelno je mogue (mada ne i preporuljivo) definirati operatore == i != na takav
nain da ovi izrazi budu razliiti.
Za sluaj kada je objekte odreenog tipa mogue staviti u odreeni poredak, preporuljivo je
definirati operatore <, <=, > i >=. Na primjer, za objekte tipa Student, ima smisla definirati
da je jedan student vei od drugog ako ima bolji prosjek, itd. Posebno je korisno definirati operator
< ukoliko nad primjercima klase elimo koristiti funkcije poput funkcije sort iz biblioteke
algorithm, s obzirom da ona kao i veina njoj srodnih funkcija koriste upravo operator < kao
kriterij poreenja u sluaju da korisnik ne navede eksplicitno neku drugu funkciju kriterija kao
parametar. Kao i za sve ostale operatore, i ovdje vrijedi pravilo da su svi operatori poretka ( <, <=,
> i >=) posve neovisni jedan od drugog, kao i od operatora == i !=, pa je svakom od njih
mogue dati potpuno nevezana znaenja. Meutim, posve je jasno da bi ove operatore trebalo definirati
tako da zaista odraavaju smisao poretka za objekte koji se razmatraju. Kako za vektore poredak nije
definiran, ove operatore za klasu Vektor3d neemo definirati.
Interesantno je razmotriti operatore << i >>. Mada programer i ove operatore moe definirati da
obavljaju ma kakvu funkciju, njihovo prirodno znaenje je ispis na izlazni tok i itanje sa ulaznog toka.
Stoga, kad god je objekte neke klase mogue na smislen nain ispisivati na ekran ili unositi sa tastature,
poeljno je podrati da se te radnje obavljaju pomou operatora << i >>. Na primjer, ukoliko je v
objekat klase Vektor3d, sasvim je prirodno podrati ispis vektora na ekran pomou konstrukcije
std::cout << v umjesto konstrukcije v.Ispisi() koju smo do sada koristili. Razmotrimo kako
se ovo moe uraditi. Na prvom mjestu, treha primijetiti da se izraz std::cout << v interpretira kao
operator <<(std::cout, v). Ukoliko se sjetimo da objekat cout nije nita drugo nego jedna
instanca klase ostream, lako emo zakljuiti da prvi parametar traene operatorske funkcije treba da
bude tipa ostream, a drugi tipa Vektor3d. Preciznije, prvi formalni parametar mora biti referenca
na nekonstantni objekat tipa ostream. Referenca na nekonstantni objekat je neophodna zbog
injenice da je klasa ostream veoma sloena klasa koja sadri atribute poput pozicije tekueg ispisa
koji se svakako mijenjaju tokom ispisa. Ukoliko bi se stvarni parametar cout prenosio po referenci na
konstantni objekat, izmjene ne bi bile mogue i bila bi prijavljena greka. Takoer, ukoliko bi se stvarni
parametar cout prenosio po vrijednosti, sve izmjene ostvarene tokom ispisa odrazile bi se samo na
lokalnu kopiju objekta cout, to bi onemoguilo dalji ispravan rad izlaznog toka. Sreom, takav
prenos nije ni mogu, jer je klasa ostream implementirana tako se primjerci tipa ostream ne mogu
kopirati pa samim tim in prenositi po vrijednosti u funkcije (primjenom tehnika opisanih na prethodnim
predavanjima), tako da e nam kompajler prijaviti greku ukoliko uope pokuamo tako neto.
Razmotrimo ta bi trebala da vraa kao rezultat operatorska funkcija za preklapanje operatora <<
sa ciljem podrke ispisa na izlazni tok. Na prvi pogled izgleda da ona ne treba da vraa nita, odnosno da
njen povratni tip treba biti void. Meutim, ukoliko bismo to uradili, onemoguili bismo ulanavanje
operatora << kao u konstrukciji poput std::cout << v << std::endl. Kako se ova konstrukcija
zapravo interpretira kao (std::cout << v) << std::endl, odnosno, uz preklopljeni operator
<<, kao operator <<(operator <<(std::cout, v), std::endl), jasno je da traena
operatorska funkcija treba da vrati sam objekat izlaznog toka kao rezultat (odnosno, referencu na njega,
ime se izbjegava kopiranje masivnog objekta, koje u ovom sluaju nije ni dozvoljeno). Stoga bi
implementacija traene operatorske funkcije mogla izgledati recimo ovako:
ostream &operator <<(ostream &izlazni_tok, const Vektor3d &v) {
izlazni_tok << "{" << v.x << "," << v.y << "," << v.z << "}";
return izlazni_tok;
}

Uzmemo li u obzir da operator << sa prostim ugraenim tipovima kao drugim operandom vraa kao
rezultat sam objekat izlaznog toka (tanije, referencu na njega) koji je bio dat kao prvi operand,
prethodnu funkciju moemo krae napisati ovako:
ostream &operator <<(ostream &izlazni_tok, const Vektor3d &v) {
return izlazni_tok << "{" << v.x << "," << v.y << "," << v.z << "}";
}

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_a
Akademska godina 2013/14

Treba imati u vidu da je objekat cout u sutini obina promjenljiva (iako veoma specifinog
tipa). Takoer, treba istai da podrka za ispis obinih tipova podataka konstrukcijama poput
std::cout << 3, std::cout << "Pozdrav" i slino nije ostvarena nikakvom posebnom
magijom, nego su u biblioteci iostream jednostavno definirane odgovarajue operatorske funkcije za
operator << koje obavljaju njihov ispis. Prvi parametar ovakvih operatorskih funkcija je naravno
referenca na objekat tipa ostream, dok su drugi parametri prostih ugraenih tipova poput int,
char * itd. Druga stvar je kako je realizirana implementacija tih funkcija (za tu svrhu koritene su
funkcije mnogo nieg nivoa za direktan pristup izlaznim ureajima, u ta neemo ulaziti).
Na potpuno analogan nain moemo definirati i operator >> za itanje sa ulaznog toka, samo
trebamo voditi rauna da je objekat cin instanca klase istream. Tako bi operatorska funkcija koja
bi omoguila itanje vektora sa tastature konstrukcijom poput cin >> v, pri emu bi se vektor unosio
kao obina trojka brojeva razdvojenih prazninama, mogla izgledati ovako:
istream &operator >>(istream &ulazni_tok, Vektor3d &v) {
return ulazni_tok >> v.x >> v.y >> v.z;
}

Primijetimo da se ovdje drugi parametar takoer mora prenijeti po referenci na nekonstantni objekat, jer
konstrukcija std::cin >> v, koja se zapravo interpretira kao operator >>(std::cin, v),
mora biti u stanju da promijeni sadraj objekta v.
Od binarnih operatora mogue je preklopiti jo i operatore &, ^, |, &&, ||, ->* i ,.
Meutim, ovi operatori imaju prilino specifina znaenja za standardne ugraene tipove podataka i
dosta je teko zamisliti ta bi oni trebali da rade ukoliko bi se primijenili na korisniki definirane tipove
podataka (mada nesumnjivo postoje sluajevi kada i njihovo preklapanje moe biti korisno). Stoga se
preklapanje ovih operatora ne preporuuje, osim u sluajevima kada za to postoje jaki razlozi. Isto tako,
ne preporuuje se bez velike potrebe preklapati unarne operatore * i &, pogotovo ovog drugog.
Potreba za njihovim preklapanjem moe nastati ukoliko elimo kreirati tipove podataka koji na neki
nain oponaaju ponaanje pokazivaa odnosno referenci. Recimo, preklapanje unarnog operatora * je
iskoriteno za potrebe kreiranja iteratora i pametnih pokazivaa.
Kao ilustraciju do sada izloenih koncepata, slijedi prikaz i implementacija prilino upotrebljive
klase nazvane Kompleksni, koja omoguava rad sa kompleksnim brojevima. Ova klasa je po
funkcionalnosti veoma slina generikoj klasi complex iz istoimene biblioteke koju smo ranije
koristili, samo to je neto siromanija sa funkcijama i nije generika. Kako je i funkcije prijatelje klase
takoer mogue implementirati odmah unutar deklaracije klase, bez obzira da li se radi o standardnim i
operatorskim funkcijama (to je uputno raditi samo ukoliko im je tijelo kratko), to je u ovom primjeru i
uraeno, izuzev operatorske funkcije za operator >>, ije je tijelo neto due:
class Kompleksni {
double re, im;
public:
Kompleksni(double re = 0, double im = 0) : re(re), im(im) {}
friend Kompleksni operator +(const Kompleksni &a) { return a; }
friend Kompleksni operator -(const Kompleksni &a) {
return {-a.re, -a.im};
}
friend Kompleksni operator +(const Kompleksni &a,
const Kompleksni &b) {
return {a.re + b.re, a.im + b.im};
}
friend Kompleksni operator -(const Kompleksni &a,
const Kompleksni &b) {
return Kompleksni(a.re - b.re, a.im - b.im);
}
friend Kompleksni operator *(const Kompleksni &a,
const Kompleksni &b) {
return {a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re};
}

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_a
Akademska godina 2013/14

friend Kompleksni operator /(const Kompleksni &a,


const Kompleksni &b) {
double pom(b.re * b.re + b.im * b.im);
return {(a.re * b.re + a.im * b.im) / pom,
(a.im * b.re - a.re * b.im) / pom};
}
friend bool operator ==(const Kompleksni &a, const Kompleksni &b) {
return a.re == b.re && a.im == b.im;
}
friend bool operator !=(const Kompleksni &a, const Kompleksni &b) {
return !(a == b);
}
friend Kompleksni &operator +=(Kompleksni &a, const Kompleksni &b) {
a.re += b.re; a.im += b.im; return a;
}
friend Kompleksni &operator -=(Kompleksni &a, const Kompleksni &b) {
a.re -= b.re; a.im -= b.im; return a;
}
friend Kompleksni &operator *=(Kompleksni &a, const Kompleksni &b) {
double pom(a.re * b.im + a.im * b.re);
a.re = a.re * b.re - a.im * b.im; a.im = pom; return a;
}
friend Kompleksni &operator /=(Kompleksni &a, const Kompleksni &b) {
double pom1(a.im * b.re - a.re * b.im),
pom2(b.re * b.re + b.im * b.im);
a.re = (a.re * b.re + a.im * b.im) / pom2; a.im = pom1 / pom2;
return a;
}
friend ostream &operator <<(ostream &tok, const Kompleksni &a) {
return tok << "(" << a.re << "," << a.im << ")";
}
friend istream &operator >>(istream &tok, Kompleksni &a);
friend double real(const Kompleksni &a) { return a.re; }
friend double imag(const Kompleksni &a) { return a.im; }
friend double abs(const Kompleksni &a) {
return std::sqrt(a.re * a.re + a.im * a.im);
}
friend double arg(const Kompleksni &a) {
return std::atan2(a.im, a.re);
}
friend Kompleksni conj(const Kompleksni &a) {
return {a.re, -a.im};
}
friend Kompleksni sqrt(const Kompleksni &a) {
double rho(std::sqrt(abs(a))), phi(arg(a) / 2);
return {rho * cos(phi), rho * sin(phi)};
}
};
istream &operator >>(istream &tok, Kompleksni &a) {
char znak;
tok >> std::ws;
// "Progutaj" razmake...
if(tok.peek() != '(') {
tok >> a.re;
a.im = 0;
}
else {
tok >> znak >> a.re >> znak;
if(znak != ',') tok.setstate(ios::failbit);
tok >> a.im >> znak;
if(znak != ')') tok.setstate(ios::failbit);
}
return tok;
}

10

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_a
Akademska godina 2013/14

Vidimo da su u ovoj klasi podrana etiri osnovna aritmetika operatora +, , * i /, zatim


odgovarajui pridruujui operatori +=, =, *= i /=, operatori poreenja == i !=, kao i
operatori za ulaz i izlaz >> i <<. Pri tome je predvieno da se kompleksni brojevi ispisuju kao parovi
realnih brojeva u zagradama, razdvojeni zarezima (npr. (2,3)), dok se mogu unositi bilo kao obian
realni broj (u tom sluaju se podrazumijeva da je imaginarni dio jednak nuli), bilo kao par realnih
brojeva unutar zagrada, razdvojen zarezom. Sve napisane operatorske funkcije su posve jednostavne,
tako da ih nije potrebno posebno objanjavati. Jedino je potrebno objasniti ulogu konstrukcije
tok.setstate(ios::failbit) u operatorskoj funkciji za operator >>. Metoda setstate
primijenjena na objekat ulaznog toka slui za dovoenje ulaznog toka u eljeno stanje, pri emu se
eljeno stanje zadaje parametrom. Tako, parametar ios::failbit oznaava neispravno stanje
(ios::failbit je konstanta pobrojanog tipa definirana unutar klase ios u biblioteci iostream),
tako da je svrha poziva tok.setstate(ios::failbit) upravo dovoenje ulaznog toka u neispravno
stanje. Zato ovo radimo? Primijetimo da se ovaj poziv vri u sluaju kada na ulazu detektiramo znak
koji se ne bi trebao da pojavi na tom mjestu (npr. kada na mjestu gdje oekujemo zatvorenu zagradu
zateknemo neto drugo). Ovim smo postigli da u sluaju da prilikom unosa poput std::cin >> a
gdje je a promjenljiva tipa Kompleksni ulazni tok dospije u neispravno stanje u sluaju da nismo
ispravno unijeli kompleksan broj. Na taj nain e se operator >> za tip Kompleksni ponaati na isti
nain kao i za sluaj prostih ugraenih tipova. Alternativno rjeenje bilo bi bacanje izuzetka u sluaju
neispravnog unosa, ali u tom sluaju bismo imali razliit tretman greaka pri unosu za sluaj tipa
Kompleksni i prostih ugraenih tipova, to bi svakako bilo zbunjujue.
Pored ovih operatora, definirano je i est obinih funkcija (ne funkcija lanica) real, imag,
abs, arg, conj i sqrt koje respektivno vraaju realni dio, imaginarni dio, modul, argument,
konjugovano kompleksnu vrijednost i kvadratni korijen kompleksnog broja koji im se proslijedi kao
argument (injenica da funkcije abs i sqrt ve postoje nije problem, jer je dozvoljeno imati
funkcije istih imena koje se razlikuju po tipovima argumenata). Ovo sve zajedno ini sasvim solidnu
podrku radu sa kompleksnim brojevima. Primijetimo da je unutar funkcije arg elegantno iskoritena
funkcija atan2 iz standardne matematike biblioteke, koja radi upravo ono to nam ovdje treba. Ne
treba zaboraviti da je u program koji implementira ovu klasu obavezno ukljuiti i matematiku
biblioteku cmath, zbog upotrebe funkcija kao to su sqrt, atan2, sin itd.
Zbog ve opisanih razloga vezanih za efikasnost, operatorske funkcije za operatore poput +=
nismo implementirali na trivijalan nain konstrukcijama poput return a = a + b. Ako po svaku
cijenu elimo izbjei dupliranje koda, onda je bolje direktno implementirati samo operatore poput +=,
a operatore poput + implementirati preko njih, na ve opisani nain.
Ukoliko bismo testirali gore napisanu klasu Kompleksni, mogli bismo uoiti da su automatski
podrane i mjeovite operacije sa realnim i kompleksnim brojevima, iako takve operatore nismo
eksplicitno definirali. Na primjer, izrazi 3 * a ili a + 2 gdje je a objekat tipa Kompleksni
sasvim su legalni. Ovo je posljedica injenice da klasa Kompleksni ima konstruktor sa jednim
parametrom (zapravo, ima konstruktor sa podrazumijevanim parametrima, koji se po potrebi moe
protumaiti i kao konstruktor sa jednim parametrom) koji omoguava automatsku pretvorbu tipa
double u tip Kompleksni. Poto se izrazi 3 * a i a + 2 zapravo interpretiraju kao
operator *(3, a) odnosno operator +(a, 2), konverzija koju obavlja konstruktor dovodi do
toga da se ovi izrazi dalje interpretiraju kao operator *(Kompleksni(3), a) odnosno kao
operator +(a, Kompleksni(2)) to ujedno objanjava zato su ovi izrazi legalni.
Napomenimo da prilikom interpretacije izraza sa operatorima, kompajler prvo pokuava pronai
operatorsku funkciju koja po tipu parametara tano odgovara operandima upotrijebljenog operatora. Tek
ukoliko se takva funkcija ne pronae, pokuavaju se pretvorbe tipova. U sluaju da je mogue izvriti
vie razliitih pretvorbi, prvo se probavaju prirodnije pretvorbe (npr. ugraene pretvorbe smatraju se
prirodnijim od korisniki definiranih pretvorbi, kao to se i pretvorbe izmeu jednog u drugi cjelobrojni
tip smatraju prirodnijim od pretvorbe nekog cjelobrojnog u neki realni tip). Na primjer, pretpostavimo da
smo definirali i sljedeu operatorsku funkciju:
Kompleksni operator *(double d, const Kompleksni &a) {
return Kompleksni(d * a.re, d * a.im);
}

11

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_a
Akademska godina 2013/14

U tom sluaju, prilikom izvravanja izraza poput 2.5 * a bie direktno pozvana navedena
operatorska funkcija, umjesto da se izvri pretvorba realnog broja 2.5 u kompleksni i pozove
operatorska funkcija za mnoenje dva kompleksna broja (to je nedvojbeno mnogo efikasnije). Ista
operatorska funkcija e se pozvati i prilikom izvravanja izraza 3 * a, iako je 3 cijeli, a ne realni
broj. Naime, pretvorba cijelog broja 3 u realni broj je prirodnija od pretvorbe u kompleksni broj, s
obzirom da je to ugraena, a ne korisniki definirana pretvorba. U navedenim primjerima, rezultat bi bio
isti koja god da se funkcija pozove (mada nee ista biti efikasnost postupka kojim se dolo do tog
rezultata). Meutim, pravila kako se razrjeavaju ovakvi pozivi neophodno je znati, s obzirom da
programer ima pravo da definira razliite akcije u razliitim operatorskim funkcijama.
Razmotrimo sada preklapanje operatora ++ i . Njihova specifinost u odnosu na ostale
unarne operatore je u tome to oni imaju i prefiksni i postfiksni (sufiksni) oblik, npr. moe se pisati
++a ili a++. Prefiksni oblik ovih operatora preklapa se na isti nain kao i svi ostali unarni operatori,
odnosno izraz ++a interpretira se kao operator ++(a). Definirajmo, na primjer, operator ++ za
klasu Vektor3d sa znaenjem poveavanja svih koordinata vektora za jedinicu (korist od ovakvog
operatora je diskutabilna, meutim ovdje ga samo definiramo kao primjer). Kako je za ugraene tipove
definirano da je rezultat operatora ++ vrijednost objekta nakon izmjene, uiniemo da isto vrijedi i za
operator ++ koji definiramo za klasu Vektor3d. Stoga ovaj operator moemo definirati pomou
sljedee operatorske funkcije:
Vektor3d &operator ++(Vektor3d &v) {
v.x++; v.y++; v.z++;
return v;
}

Vjerovatno vam je jasno zato je formalni parametar v deklariran kao referenca na nekonstantni
objekat. Takoer, kao rezultat je vraena referenca na modificirani objekat, ime ne samo da spreavamo
nepotrebno kopiranje, nego omoguavamo i da vraena vrijednost bude l-vrijednost, to i treba da bude.
Naime, kako se operator ++ moe primjenjivati samo na l-vrijednosti, legalni i smisleni izrazi poput
++(++a) odnosno ++++a ne bi bili mogui kada izraz ++a ne bi bio l-vrijednost.
Vano je napomenuti da definiranjem prefiksne verzije operatora ++ odnosno nije
automatski definirana i njihova postfiksna verzija. Na primjer, ukoliko je v objekat tipa Vektor3d,
prethodna definicija uinila je izraz ++v legalnim, ali izraz v++ jo uvijek nema smisla. Da bismo
definirali i postfiksnu verziju operatora ++, treba znati da se izrazi poput a++ gdje je a neki
korisniki definirani tip podataka interpretiraju kao operator ++(a, 0). Drugim rijeima, postfiksni
operatori ++ i tretiraju se tehniki kao binarni operatori, ali iji je drugi operand uvijek cijeli
broj 0. Stoga odgovarajua operatorska funkcija uvijek dobija nulu kao drugi parametar (zapravo, ona
teoretski moe dobiti i neku drugu vrijednost kao parametar, ali jedino ukoliko operatorsku funkciju
eksplicitno pozovemo kao funkciju, recimo konstrukcijom poput operator ++(a, 5), to se gotovo
nikada ne ini). Zbog toga bismo postfiksnu verziju operatora ++ za klasu Vektor3d mogli
definirati na sljedei nain (jezik C++ uvijek dozvoljava da izostavimo ime formalnog parametra ukoliko
nam njegova vrijednost nigdje ne treba, isto kao to je dozvoljeno u prototipovima funkcija, to smo
ovdje uradili sa drugim parametrom operatorske funkcije):
Vektor3d operator ++(Vektor3d &v, int) {
Vektor3d pomocni(v);
v.x++; v.y++; v.z++;
return pomocni;
}

Kako za proste ugraene tipove prefiksne i postfiksne verzije operatora ++ i imaju isto
dejstvo na operand, a razlikuje se samo vraena vrijednost (postfiksne verzije ovih operatora vraaju
vrijednost operanda kakva je bila prije izmjene), istu funkcionalnost smo simulirali i u prikazanoj
izvedbi postfiksne verzije operatora ++ za klasu Vektor3d. Primijetimo da u ovom sluaju ne
smijemo vratiti referencu kao rezultat, s obzirom da se rezultat nalazi u lokalnom objektu pomocni
koji prestaje postojati po zavretku funkcije (ukoliko bismo to uradili, kreirali bismo viseu referencu).
Samim tim, izraz a++, za razliku od izraza ++a, nije l-vrijednost. Radi konzistencije, u jeziku C++

12

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_a
Akademska godina 2013/14

je usvojeno da izrazi poput a++ nisu l-vrijednosti ni u sluaju prostih ugraenih tipova. Tako, mada su
izrazi poput ++(++a) odnosno ++++a sasvim legalni i smisleni, izrazi poput (a++)++ odnosno
a++++ nisu (legalan je i izraz (++a)++, ali izraz ++a++ nije, jer se interpretira kao ++(a++)).
Operatorske funkcije se mogu definirati i za pobrojane tipove. Na primjer, neka je data sljedea
deklaracija pobrojanog tipa Dani:
enum Dani {Ponedjeljak, Utorak, Srijeda, Cetvrtak, Petak, Subota,
Nedjelja};

Kao to znamo, za ovakav tip operator ++ podrazumijevano nije definiran ni u prefiksnoj ni u


postfiksnoj verziji, dok se sabiranje sa cijelim brojem podrazumijevano izvodi pretvorbom objekta tipa
Dani u cijeli broj. Ove konvencije moemo promijeniti definiranjem vlastitih operatorskih funkcija za
ove operatore nad tipom Dani, kao u primjeru koji slijedi:
Dani &operator ++(Dani &d) {
return d = Dani((int(d) + 1) % 7);
}
Dani operator ++(Dani &d, int) {
Dani pomocni(d); ++d; return pomocni;
}
Dani operator +(Dani d, int n) {
return Dani((int(d) + n) % 7);
}

U navedenom primjeru, definirali smo operatorske funkcije za prefiksnu i postfiksnu verziju operatora
++ primjenjenog nad objektom tipa Dani, kao i operatorsku funkciju za sabiranje objekta tipa
Dani sa cijelim brojem. Operator ++ zamiljen je da djeluje tako to e transformirati objekat na
koji je primijenjen da sadri sljedei dan u sedmici (uvaavajui injenicu da iza nedjelje slijedi
ponedjeljak). Implementacija postfiksne verzije operatora ++ oslanja se na prethodno definiranu
prefiksnu verziju, u emu nema nita loe, osim malog gubitka na efikasnosti. Operator + zamiljen je
da djeluje tako da izraz oblika d + n, gdje je d objekat tipa Dani a n cijeli broj, predstavlja dan
koji slijedi n dana nakon dana d (mogunost sabiranja dva objekta tipa Dani nije podrana, s
obzirom da je teko zamisliti ta bi takav zbir trebao da predstavlja). Obratite panju kako je u
implementaciji ovih operatorskih funkcija vjeto iskoriten operator %. Takoer, vano je uoiti da je
eksplicitna konverzija u tip int u izrazu poput int(d) + n, koja bi se u tipinim situacijama
izvrila automatski, u ovom sluaju neophodna. Naime, izraz poput d + n u definiciji operatorske
funkcije za operator + bio bi shvaen kao rekurzivni poziv!
Primijetimo takoer da se u prikazanoj implementaciji operatorske funkcije za operator +,
parametar d prenosi po vrijednosti. S obzirom da su objekti tipa Dani posve mali (tipino iste
veliine kao i objekti tipa int), njih se ne isplati prenositi kao reference na konstantne objekte.
Naime, njihovo kopiranje nije nita zahtjevnije nego kreiranje reference (koja je takoer tipino iste
veliine kao i objekti tipa int), a upotreba referenci unosi i dodatnu indirekciju. Stoga deklariranjem
parametra d kao reference na konstantni objekat tipa Dani nita ne bismo dobili na efikasnosti, ve
bismo naprotiv imali i neznatan gubitak.

13

You might also like